Skip to content
This repository was archived by the owner on Jan 9, 2026. It is now read-only.

Latest commit

 

History

History

A Cache Component

In this part, you'll implement a cache to speed up repeated requests for the same query. Review the documentation on component semantics. Then, in a file called cache.go, implement a Cache component with the following interface:

// Cache caches query results.
type Cache interface {
    // Get returns the cached emojis produced by the provided query. On cache
    // miss, Get returns nil, nil.
    Get(context.Context, string) ([]string, error)

    // Put stores a query and its corresponding emojis in the cache.
    Put(context.Context, string, []string) error
}
Solution.

workshops/07/cache.go

Lines 15 to 60 in 4eca79e

package main
import (
"context"
"sync"
"github.com/ServiceWeaver/weaver"
)
// Cache caches emoji query results.
type Cache interface {
// Get returns the cached emojis produced by the provided query. On cache
// miss, Get returns nil, nil.
Get(context.Context, string) ([]string, error)
// Put stores a query and its corresponding emojis in the cache.
Put(context.Context, string, []string) error
}
// cache implements the Cache component.
type cache struct {
weaver.Implements[Cache]
mu sync.Mutex
emojis map[string][]string
}
func (c *cache) Init(context.Context) error {
c.emojis = map[string][]string{}
return nil
}
func (c *cache) Get(ctx context.Context, query string) ([]string, error) {
c.mu.Lock()
defer c.mu.Unlock()
c.Logger(ctx).Debug("Get", "query", query)
return c.emojis[query], nil
}
func (c *cache) Put(ctx context.Context, query string, emojis []string) error {
c.mu.Lock()
defer c.mu.Unlock()
c.Logger(ctx).Debug("Put", "query", query)
c.emojis[query] = emojis
return nil
}

Next, update your Searcher component to use the cache. When the Search method is called on a query, it should first see if the matching emojis for query are in the cache by calling Get. If they are, Search should return the emojis immediately. Otherwise, Search should find the emojis and store them in the cache by calling Put.

Solution.

workshops/07/searcher.go

Lines 32 to 36 in 4eca79e

// searcher is the implementation of the Searcher component.
type searcher struct {
weaver.Implements[Searcher]
cache weaver.Ref[Cache]
}

workshops/07/searcher.go

Lines 41 to 47 in 4eca79e

// Try to get the emojis from the cache, but continue if it's not found or
// there is an error.
if emojis, err := s.cache.Get().Get(ctx, query); err != nil {
s.Logger(ctx).Error("cache.Get", "query", query, "err", err)
} else if emojis != nil {
return emojis, nil
}

workshops/07/searcher.go

Lines 69 to 72 in 4eca79e

// Try to cache the results, but continue if there is an error.
if err := s.cache.Get().Put(ctx, query, results); err != nil {
s.Logger(ctx).Error("cache.Put", "query", query, "err", err)
}

Because our basic search algorithm is already quite fast, it's hard to notice the speedup from caching. To simulate the slowdown of implementing a more advanced search algorithm, temporarily add time.Sleep(time.Second) to the Search method right after checking the cache and right before performing the search.

Now, run your application using go run . (don't use weaver multi deploy).

$ weaver generate .
$ SERVICEWEAVER_CONFIG=config.toml go run .

In a separate terminal, curl the application with query "pig".

$ curl "localhost:9000/search?q=pig"
["🐖","🐗","🐷","🐽"]

Your application should return ["🐖","🐗","🐷","🐽"] after a one second delay. Then, re-run the same curl command. This time, the request should return nearly instantly, as the results of query "pig" are now in the cache.

$ curl "localhost:9000/search?q=pig"
["🐖","🐗","🐷","🐽"]

Now, run your application using weaver multi deploy:

$ go build .
$ weaver multi deploy config.toml

And again in a separate terminal, repeatedly curl the application with query "pig":

$ curl "localhost:9000/search?q=pig"
["🐖","🐗","🐷","🐽"]
$ curl "localhost:9000/search?q=pig"
["🐖","🐗","🐷","🐽"]
$ curl "localhost:9000/search?q=pig"
["🐖","🐗","🐷","🐽"]
$ curl "localhost:9000/search?q=pig"
["🐖","🐗","🐷","🐽"]

Surprisingly, every request is slow! Add some logs to the Get and Put methods of the Cache component and see if you can figure out why this surprising behavior is happening. Keep reading for an explanation.

Here are the logs produced by our application after curling it four times with query "pig":

emojis.Searcher a3b14619 searcher.go:53] Search query="pig"
emojis.Cache    7545ca78 cache.go:51   ] Get query="pig"
emojis.Cache    ef518f6b cache.go:58   ] Put query="pig"
emojis.Searcher a3b14619 searcher.go:53] Search query="pig"
emojis.Cache    7545ca78 cache.go:51   ] Get query="pig"
emojis.Cache    ef518f6b cache.go:58   ] Put query="pig"
emojis.Searcher 7ed120c4 searcher.go:53] Search query="pig"
emojis.Cache    7545ca78 cache.go:51   ] Get query="pig"
emojis.Cache    ef518f6b cache.go:58   ] Put query="pig"
emojis.Searcher a3b14619 searcher.go:53] Search query="pig"
emojis.Cache    7545ca78 cache.go:51   ] Get query="pig"
emojis.Cache    ef518f6b cache.go:58   ] Put query="pig"

You'll notice that there are two replicas of the Cache component: 7545ca78 and ef518f6b. Method calls to the Cache component are being routed round robin across these two replicas.

When Search is first called on "pig", it calls Get to see if the matching emojis are in the cache. This Get is routed to replica 7545ca78. The emojis are not in replica 7545ca78's cache, so Search computes the matching emojis and stores them in the cache by calling Put. This Put is routed to the other replica, ef518f6b, where the emojis are stored in the cache.

When we call Search on "pig" again, it again calls Get to see if the matching emojis are in the cache, and this Get is again routed to replica 7545ca78. The emojis have been cached at replica ef518f6b but not at replica 7545ca78, so Search computes the emojis and stores them in the cache by calling Put. This Put is again routed to the replica ef518f6b where it is redundantly cached.

This repeats for every call to Search. Because Gets and Puts are routed to two different replicas, every request results in a cache miss.

In the next part, we'll see how to prevent this surprising behavior from happening.

⬅️ Previous Part     ⚫     Next Part ➡️