GitHub - Yiling-J/cacheme-go: πŸš€ Schema based, typed Redis caching/memoize framework for Go (original) (raw)

cacheme - Redis Caching Framework For Go

example workflow Go Report Card Mentioned in Awesome Go

English | δΈ­ζ–‡

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

πŸŒ€ Real world example with Echo and Ent: https://github.com/Yiling-J/echo-ent-cacheme-example

// old id, err := strconv.ParseInt(c.Param("id"), 10, 64) comment, err := ent.Comment.Get(context.Background(), int(id))

// new comment, err := cacheme.CommentCacheStore.Get(c.Request().Context(), c.Param("id"))

Installation

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

After installing cacheme-go codegen, go to the root directory(or the directory you think cacheme should stay) of your project, and run:

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

The command above will generate cacheme directory under current directory:

└── cacheme β”œβ”€β”€ fetcher Β Β  β”‚Β Β  └── fetcher.go └── schema └── schema.go

It's up to you where the cacheme directory should be, just remember to use the right directory in Store Generation step.

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, Singleflight: false, MetaData: false, }, } )

More details here

Store Generation

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

this will use default schema path ./cacheme/schema

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

Or you can use custom schema path:

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

This produces the following files:

└── cacheme Β Β  β”œβ”€β”€ fetcher Β Β  β”‚Β Β  └── fetcher.go β”œβ”€β”€ schema β”‚Β Β  └── schema.go β”œβ”€β”€ store β”‚ β”œβ”€β”€ base.go β”‚ └── simple.go └── store.go

If you update schema, just run generate again.

Add Fetcher

Each cache store can provide a fetch function in fetcher.go, you should call this Setup function before create client:

import "your/cacheme/store"

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

You can setup fetch functions in any place, using any pattern, not restricted to this file. Just make sure you have a fetch function when using store.

Use Your Stores

Create client and setup fetcher

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

func main() { // setup fetcher fetcher.Setup() // create client client := cacheme.New( redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }), ) // or cluster client client := cacheme.NewCluster( redis.NewClusterClient(&redis.ClusterOptions{ Addrs: []string{ ":7000", ":7001", ":7002"}, }), ) }

Store API

Get single result: Get

Get cached result. If not in cache, call fetch function and store data to Redis.

// "foo" is the {{.ID}} part of the schema result, err := client.SimpleCacheStore.Get(ctx, "foo")

Get pipeline results: GetP

Get multiple keys from multiple stores using pipeline. For each key, if not in cache, call fetch function and store data to Redis.

pipeline := client.NewPipeline() ids := []string{"1", "2", "3", "4"} var ps []*store.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) }

Consider using GetM API for single store, see GetM example below.

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

ids := []string{"1", "2", "3", "4"} var ps []*store.SimplePromise // cache string var psf []*store.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) // 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) }

Get multiple results from single store: GetM

Get multiple keys from same store, also using Redis pipeline. For each key, if not in cache, call fetch function and store data to Redis.

qs, err := client.SimpleCacheStore.GetM("foo").GetM("bar").GetM("xyz").Do(ctx) // qs is a queryset struct, support two methods: GetSlice and Get // GetSlice return ordered results slice r, err := qs.GetSlice() // r: {foo_result, bar_result, xyz_result} // Get return result of given param r, err := qs.Get("foo") // r: foo_result r, err := qs.Get("bar") // r: bar_result r, err := qs.Get("fake") // error, because "fake" not in queryset

You can also initialize a getter using MGetter

getter := client.SimpleCacheStore.MGetter() for _, id := range ids { getter.GetM(id) } qs, err := getter.Do(c.Request().Context())

Invalid single cache: Invalid

err := client.SimpleCacheStore.Invalid(ctx, "foo")

Update single cache: Update

err := client.SimpleCacheStore.Update(ctx, "foo")

Invalid all keys: InvalidAll

Only works when you enable MetaData option in schema.

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

Schema Definition

Each schema has 5 fields:

Notes:

 cacheme:category:1:book:3:v1  

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

Logger

You can use custom logger with cacheme, your logger should implement cacheme logger interface:

type Logger interface { Log(store string, key string, op string) }

Here store is the store tag, key is cache key without prefix, op is operation type. Default logger is NOPLogger, just return and do nothing.

Set client logger:

logger := &YourCustomLogger{} client.SetLogger(logger)

Operation Types:

Performance

Parallel benchmarks of Cacheme

cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkCachemeGetParallel-12    	   10000	    198082 ns/op
BenchmarkCachemeGetParallel-12    	 1000000	      9501 ns/op