Schema based, typed Redis caching/memoize framework for Go

cacheme - Redis Caching Framework For Go

example workflow Go Report Card

English | 中文

  • Statically Typed - 100% statically typed using code generation.
  • Scale Efficiently - thundering herd protection via pub/sub.
  • Cluster Support - same API for redis & redis cluster.
  • Memoize - dynamic key generation based on code generation.
  • Versioning - cache versioning for better management.
  • Pipeline - reduce io cost by redis pipeline.

Read this first: Caches, Promises and Locks. This is how caching part works in cacheme.

Installation

go get github.com/Yiling-J/cacheme-go/cmd

After installing cacheme-go codegen, go to the root directory of your project, and run:

go run github.com/Yiling-J/cacheme-go/cmd init

The command above will generate cacheme directory under root directory:

└── cacheme
    ├── fetcher
    │   └── fetcher.go
    └── schema
        └── schema.go

Add Schema

Edit schema.go and add some schemas:

package schema

import (
	"time"

	cacheme "github.com/Yiling-J/cacheme-go"
)

var (
	// default prefix for redis keys
	Prefix = "cacheme"

	// store schemas
	Stores = []*cacheme.StoreSchema{
		{
			Name:    "Simple",
			Key:     "simple:{{.ID}}",
			To:      "",
			Version: 1,
			TTL:     5 * time.Minute,
		},
	}
)

More details here

Store Generation

Run code generation from the root directory of the project as follows:

go run github.com/Yiling-J/cacheme-go/cmd generate

This produces the following files:

└── cacheme
    ├── fetcher
    │   └── fetcher.go
    ├── schema
    │   └── schema.go
    └── store.go

store.go is generated based on schemas in schema.go. Adding more schemas and run generate again.

Add Fetcher

Each cache store should provide a fetch function in fetcher.go:

func Setup() {
	cacheme.SimpleCacheStore.Fetch = func(ctx context.Context, ID string) (string, error) {
		return ID, nil
	}
}

Use Your Stores

Create a client and get data, fetch function will be called if cache not exist:

import (
	"your_project/cacheme"
	"your_project/cacheme/fetcher"
)

func example() (string, error) {
	ctx := context.TODO()
	fetcher.Setup()
	client := cacheme.New(
		redis.NewClient(&redis.Options{
			Addr:     "localhost:6379",
			Password: "",
			DB:       0,
		}),
	)
	store := client.SimpleCacheStore
	result, err := store.Get(ctx, "foo")
	if err != nil {
		return "", err
	}
	return result, err
}

Redis Cluster:

import (
	"your_project/cacheme"
	"your_project/cacheme/fetcher"
)

func example() (string, error) {
	ctx := context.TODO()
	fetcher.Setup()
	client := cacheme.NewCluster(
		redis.NewClusterClient(&redis.ClusterOptions{
			Addrs: []string{
				":7000",
				":7001",
				":7002"},
		}),
	)
	store := client.SimpleCacheStore
	result, err := store.Get(ctx, "foo")
	if err != nil {
		return "", err
	}
	return result, err
}

Invalid your cache:

err := store.Invalid(ctx, "foo")

Update your cache:

err := store.Update(ctx, "foo")

Redis pipeline(using same client, skip client creation here):

import cachemego "github.com/Yiling-J/cacheme-go"

...
pipeline := cachemego.NewPipeline(client.Redis())
ids := []string{"1", "2", "3", "4"}
var ps []*cacheme.SimplePromise
for _, i := range ids {
	promise, err := client.SimpleCacheStore.GetP(ctx, pipeline, i)
	ps = append(ps, promise)
}
err = pipeline.Execute(ctx)
fmt.Println(err)

for _, promise := range ps {
	r, err := promise.Result()
	fmt.Println(r, err)
}

Mixed pipeline:

import cachemego "github.com/Yiling-J/cacheme-go"

...
// same pipeline for different stores
pipeline := cachemego.NewPipeline(client.Redis())

ids := []string{"1", "2", "3", "4"}
var ps []*cacheme.SimplePromise // cache string
var psf []*cacheme.FooPromise // cache model.Foo struct
for _, i := range ids {
	promise, err := client.SimpleCacheStore.GetP(ctx, pipeline, i)
	ps = append(ps, promise)
}
for _, i := range ids {
	promise, err := client.FooCacheStore.GetP(ctx, pipeline, i)
	psf = append(psf, promise)
}
// execute only once
err = pipeline.Execute(ctx)
fmt.Println(err)
// simple store results
for _, promise := range ps {
	r, err := promise.Result()
	fmt.Println(r, err)
}
// foo store results
for _, promise := range psf {
	r, err := promise.Result()
	fmt.Println(r, err)
}

Invalid all cache with version:

// invalid all version 1 simple cache
client.SimpleCacheStore.InvalidAll(ctx, 1)

Schema Definition

Each schema has 5 fields:

  • Name - store name, will be struct name in generated code, capital first.
  • Key - key with variable using go template syntax, Variable name will be used in code generation.
  • To - cached value, type of value will be used in code generation. Examples:
    • string: ""
    • int: 1
    • struct: model.Foo{}
    • struct pointer: &model.Foo{}
    • slice: []model.Foo{}
    • map: map[model.Foo]model.Bar{}
  • Version - version number, for schema change.
  • TTL - redis ttl using go time.
  • Singleflight - bool, if true, concurrent requests to same key on same machine will call Redis only once

Notes:

  • Duplicate name/key is not allowed.

  • If set Singleflight to true, Redis GET command will be wrapped in a singleflight, so concurrent requests to same key will call Redis only once. Let's use some example to explain this:

    • you have some products to sell, and thousands people will view the detail at same time, so the product key product:1:info may be hit 100000 times per second. Now you should turn on singleflight, and the actually redis hit may reduce to 5000.
    • you have cache for user shopping cart user:123:cart, only the user himself can see that. Now no need to use singleflight, becauese there should't be concurrent requests to that key.
    • you are using serverless platform, AWS Lambda or similar. So each request runs in isolated environment, can't talk to each other through channels. Then singleflight make no sense.
  • Full redis key has 3 parts: prefix + schema key + version. Schema Keycategory:{{.categoryID}}:book:{{.bookID}} with prefix cacheme, version 1 will generate key:

     cacheme:category:1:book:3:v1
    

    Also you will see categoryID and bookID in generated code, as fetch func params.

Similar Resources

godis - an old Redis client for Go

godis Implements a few database clients for Redis. There is a stable client and an experimental client, redis and exp, respectively. To use any of the

Apr 16, 2022

Google Go Client and Connectors for Redis

Go-Redis Go Clients and Connectors for Redis. The initial release provides the interface and implementation supporting the (~) full set of current Red

Oct 25, 2022

Redis client library for Go

go-redis go-redis is a Redis client library for the Go programming language. It's built on the skeleton of gomemcache. It is safe to use by multiple g

Nov 8, 2022

Type-safe Redis client for Golang

Redis client for Golang ❤️ Uptrace.dev - distributed traces, logs, and errors in one place Join Discord to ask questions. Documentation Reference Exam

Jan 4, 2023

Redis Sorted Sets Benchmark

redis-zbench-go Redis Sorted Sets Benchmark Overview This repo contains code to trigger load ( ZADD ) or query (ZRANGEBYLEX key min max) benchmarks, w

May 18, 2021

Use Redis' MONITOR to draw things in a terminal

Use Redis' MONITOR to draw things in a terminal

Redis Top Redistop uses MONITOR to watch Redis commands and shows per command and per host statistics. Because MONITOR streams back all commands, its

Aug 30, 2022

Examples and code to assign a name to your MongoDB, MySQL, PostgreSQL, RabbitMQ, and redis connection.

Examples and code to assign a name to your MongoDB, MySQL, PostgreSQL, RabbitMQ, and redis connection.

your connection deserves a name 👀 When your app interacts with an external system, assign a name to the connection. An external system in this contex

Dec 14, 2022

redis protocol server for go.

redigosrv what redis server侧的golang版本实现。 why 作为后端RD,你开发的,维护的最多的应该就是你的server,作为服务的提供方来说,想要和客户端进行有效的交互,首先要要能理解信息的含义,因此一套通信协议是必须的。 为什么选择redis协议,应用层有很多成熟的

Nov 30, 2021

A Golang implemented Redis Server and Cluster.

A Golang implemented Redis Server and Cluster.

Godis is a golang implementation of Redis Server, which intents to provide an example of writing a high concurrent middleware using golang.

Dec 28, 2022
Comments
  • Add GetMany method to store

    Add GetMany method to store

    The pipeline and GetP method are generic, one pipeline can include multiple stores and multiple keys, so we can't apply store's Singleflight config here. So I will add another method GetMany to each store, this method can get multiple keys from same store with 1 redis hit. And benefit from Singleflight option. Also this method should be unordered. That means ["a", "b"] and ["b", "a"] should be in same singleflight call, because they ask for same elements, just in different order.

    Two options in my mind:

    • param struct:
    // FooParams is created by code generation
    FooStore.GetMany(ctx, []FooParams{{ID: "a", Name: "a"}, {ID: "b", Name: "b"}})
    // will return []model.Foo{...}
    

    OR

    • call chain:
    FooStore.GetM("a", "a").GetM("b", "b").Do(ctx)
    // will return []model.Foo{...}
    

    Both options should return reuslts directly, same order as input.

  • add store package

    add store package

    This update will split stores to a sub package, code regeneration is required after update. Also some old import path won't work, need to fix fetcher.go manually

  • Add get many method

    Add get many method

    Add GetM API:

    qs, err := FooStore.GetM("a").GetM("b").GetM("c").Do(ctx)
    // get results as slice, same order as inputs
    qs.GetSlice() // return slice {"a", "b", "c"}
    
    qs, err := FooStore.GetM("b").GetM("c").GetM("a").Do(ctx)
    // get results as slice, same order as inputs
    qs.GetSlice() // return slice {"b", "c", "a"}
    
    // get single result
    v, err := qs.Get("a")
    v, err := qs.Get("b")
    // not exist, err != nil
    v, err := qs.Get("q")
    
  • Support schema version function

    Support schema version function

    So we can update version dynamically:

    Example: model.go

    // when you change Foo struct, also update version here.
    // with version function, no need to generate cache store again.
    const FooCacheVersion = 1
    type Foo struct {}
    

    schema.go

    		{
    			Name:    "Foo",
    			Key:     "foo:{{.ID}}:info",
    			To:      model.Foo{},
    			Version: func() int {return model.FooCacheVersion},
    			TTL:     5 * time.Minute,
    		},
    
Redis client Mock Provide mock test for redis query

Redis client Mock Provide mock test for redis query, Compatible with github.com/go-redis/redis/v8 Install Confirm that you are using redis.Client the

Dec 27, 2022
GoBigdis is a persistent database that implements the Redis server protocol. Any Redis client can interface with it and start to use it right away.

GoBigdis GoBigdis is a persistent database that implements the Redis server protocol. Any Redis client can interface with it and start to use it right

Apr 27, 2022
Bxd redis benchmark - Redis benchmark tool for golang

使用 redis benchmark 工具, 测试 10 20 50 100 200 1k 5k 字节 value 大小,redis get set 性能。 r

Jan 22, 2022
Priority queue with message-group based partitioning and equal attention guarantee for each message group based on Redis

redis-ordered-queue-go Priority queue with message-group based partitioning and equal attention guarantee for each message group based on Redis What i

Oct 21, 2022
High-performance framework for building redis-protocol compatible TCP servers/services

Redeo The high-performance Swiss Army Knife for building redis-protocol compatible servers/services. Parts This repository is organised into multiple

Jan 4, 2023
redis client implement by golang, inspired by jedis.

godis redis client implement by golang, refers to jedis. this library implements most of redis command, include normal redis command, cluster command,

Dec 6, 2022
Go client for Redis

Redigo Redigo is a Go client for the Redis database. Features A Print-like API with support for all Redis commands. Pipelining, including pipelined tr

Jan 1, 2023
Type-safe Redis client for Golang

Redis client for Golang ❤️ Uptrace.dev - distributed traces, logs, and errors in one place Join Discord to ask questions. Documentation Reference Exam

Jan 1, 2023
Go Redis Client

xredis Built on top of github.com/garyburd/redigo with the idea to simplify creating a Redis client, provide type safe calls and encapsulate the low l

Sep 26, 2022
Simple key-value store abstraction and implementations for Go (Redis, Consul, etcd, bbolt, BadgerDB, LevelDB, Memcached, DynamoDB, S3, PostgreSQL, MongoDB, CockroachDB and many more)

gokv Simple key-value store abstraction and implementations for Go Contents Features Simple interface Implementations Value types Marshal formats Road

Dec 24, 2022