Caching objects by tags in Go

A short tutorial on how one can cache objects and tag them to make invalidation easier.

Caching has an important role to play in a system design. It is primarily used to speed up information retrieval from a service and reduce the load on a database engine, the primary source of truth for a service. Data that is frequently requested by users are stored in a in-memory key-value store, like Redis, to speed up subsequent look up of the same data. In this post, I want to specifically talk about cache invalidation and provide an implementation in Go for invalidating cache using tags.

Cache Invalidation

When frequently accessed data which is stored in a cache is updated, it is necesssary for the application to evict the stale data from the cache. So when subsequent requests for the same data is received by the application, it will retrieve fresh data from the database and store it in the cache. This way, users will never receive stale data from the application. The process of cache invalidation can be made easier by tagging data with specific tags when storing in the cache. This way, when the data needs to be invalidated, we can use the tag names to evict them from the cache.

Here is an implementation of caching records by tags. We will use Redis and github.com/go-redis/redis package.

package cache

import (
    "github.com/go-redis/redis/v7"
)

type cache struct {
    client redis.Client
}

// SetByTags to cache objects and then tag them.
func (c *cache) SetByTags(ctx context.Context, key string, val interface{}, tags []string, expiry time.Duration) error {
	pipe := c.client.TxPipeline()
	for _, tag := range tags {
		pipe.SAdd(tag, key)
		pipe.Expire(tag, expiry)
	}

	pipe.Set(key, b, expiry)

	_, errExec := pipe.Exec()
	return errExec
}

// Invalidate to invalidate cached objects with given tags.
func (c *cache) Invalidate(ctx context.Context, tags []string) {
	keys := make([]string, 0)
	for _, tag := range tags {
		k, _ := c.client.SMembers(tag).Result()
		keys = append(keys, tag)
		keys = append(keys, k...)
	}
	c.client.Del(keys...)
}

In SetByTags method, we maintain a set data structure for each tag provided. We then add the given cache key into these sets. When invalidating cache by tags, we get all the members, i.e. cache keys, for the given tags and delete them from Redis.

Example usage:


tags := []string{"post1", "post2"}
value := "blog data"

key1 := "blog:one:post1"
key2 := "blog:one:post2"

// Set cache by tags here:
cache.SetByTags(ctx, key1, value, tags, 24 * time.Hour)
cache.SetByTags(ctx, key2, value, tags, 24 * time.Hour)


// Invalidate cache by tag "post1" here.
// Both key1 and key2 will be evicted, since both were tagged with "post1".
cache.Invalidate(ctx, []string{"post1"})

References

Subscribe now to immediately get notified when more such articles are published.