{"id":13411796,"url":"https://github.com/eko/gocache","last_synced_at":"2025-12-15T16:16:30.697Z","repository":{"id":37561510,"uuid":"212965919","full_name":"eko/gocache","owner":"eko","description":"☔️ A complete Go cache library that brings you multiple ways of managing your caches","archived":false,"fork":false,"pushed_at":"2025-04-26T19:24:38.000Z","size":3554,"stargazers_count":2638,"open_issues_count":38,"forks_count":207,"subscribers_count":21,"default_branch":"master","last_synced_at":"2025-05-05T22:50:13.718Z","etag":null,"topics":["bigcache","cache","chain","go","golang","hacktoberfest","memcache","memory","redis","ristretto"],"latest_commit_sha":null,"homepage":"https://vincent.composieux.fr/article/i-wrote-gocache-a-complete-and-extensible-go-cache-library/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["eko"]}},"created_at":"2019-10-05T08:13:54.000Z","updated_at":"2025-05-02T12:03:40.000Z","dependencies_parsed_at":"2024-03-16T15:49:03.025Z","dependency_job_id":"28af275f-82d6-49cf-9379-69ef757ad143","html_url":"https://github.com/eko/gocache","commit_stats":{"total_commits":220,"total_committers":48,"mean_commits":4.583333333333333,"dds":0.7772727272727273,"last_synced_commit":"6d59150fcc3e43921ac5fc4e41d66cce701963e3"},"previous_names":["eko/gache"],"tags_count":79,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eko%2Fgocache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eko%2Fgocache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eko%2Fgocache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eko%2Fgocache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eko","download_url":"https://codeload.github.com/eko/gocache/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252590528,"owners_count":21772935,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bigcache","cache","chain","go","golang","hacktoberfest","memcache","memory","redis","ristretto"],"created_at":"2024-07-30T20:01:17.007Z","updated_at":"2025-12-15T16:16:30.594Z","avatar_url":"https://github.com/eko.png","language":"Go","funding_links":["https://github.com/sponsors/eko"],"categories":["Go","Data Structures","Database","语言资源库","Uncategorized","数据库","Generators","Data Integration Frameworks","Repositories","数据结构`go语言实现的数据结构与算法`","数据结构"],"sub_categories":["Advanced Console UIs","Standard CLI","Caches","go","缓存","Uncategorized","标准 CLI"],"readme":"[![Test](https://github.com/eko/gocache/actions/workflows/all.yml/badge.svg?branch=master)](https://github.com/eko/gocache/actions/workflows/all.yml)\n[![GoDoc](https://godoc.org/github.com/eko/gocache?status.png)](https://pkg.go.dev/github.com/eko/gocache/lib/v4)\n[![GoReportCard](https://goreportcard.com/badge/github.com/eko/gocache)](https://goreportcard.com/report/github.com/eko/gocache)\n[![codecov](https://codecov.io/gh/eko/gocache/branch/master/graph/badge.svg)](https://codecov.io/gh/eko/gocache)\n\nGocache\n=======\n\nGuess what is Gocache? a Go cache library.\nThis is an extendable cache library that brings you a lot of features for caching data.\n\n## Overview\n\nHere is what it brings in detail:\n\n* ✅ Multiple cache stores: actually in memory, redis, or your own custom store\n* ✅ A chain cache: use multiple cache with a priority order (memory then fallback to a redis shared cache for instance)\n* ✅ A loadable cache: allow you to call a callback function to put your data back in cache\n* ✅ A metric cache to let you store metrics about your caches usage (hits, miss, set success, set error, ...)\n* ✅ A marshaler to automatically marshal/unmarshal your cache values as a struct\n* ✅ Define default values in stores and override them when setting data\n* ✅ Cache invalidation by expiration time and/or using tags\n* ✅ Use of Generics\n\n## Built-in stores\n\n* [Memory (bigcache)](https://github.com/allegro/bigcache) (allegro/bigcache)\n* [Memory (ristretto)](https://github.com/dgraph-io/ristretto) (dgraph-io/ristretto)\n* [Memory (go-cache)](https://github.com/patrickmn/go-cache) (patrickmn/go-cache)\n* [Memcache](https://github.com/bradfitz/gomemcache) (bradfitz/memcache)\n* [Redis](https://github.com/go-redis/redis) (go-redis/redis)\n* [Redis (rueidis)](https://github.com/redis/rueidis) (redis/rueidis)\n* [Freecache](https://github.com/coocood/freecache) (coocood/freecache)\n* [Pegasus](https://pegasus.apache.org/) ([apache/incubator-pegasus](https://github.com/apache/incubator-pegasus)) [benchmark](https://pegasus.apache.org/overview/benchmark/)\n* [Hazelcast](https://github.com/hazelcast/hazelcast-go-client) (hazelcast-go-client/hazelcast)\n* More to come soon\n\n## Built-in metrics providers\n\n* [Prometheus](https://github.com/prometheus/client_golang)\n\n## Installation\n\nTo begin working with the latest version of gocache, you can import the library in your project:\n\n```go\ngo get github.com/eko/gocache/lib/v4\n```\n\nand then, import the store(s) you want to use between all available ones:\n\n```go\ngo get github.com/eko/gocache/store/bigcache/v4\ngo get github.com/eko/gocache/store/freecache/v4\ngo get github.com/eko/gocache/store/go_cache/v4\ngo get github.com/eko/gocache/store/hazelcast/v4\ngo get github.com/eko/gocache/store/memcache/v4\ngo get github.com/eko/gocache/store/pegasus/v4\ngo get github.com/eko/gocache/store/redis/v4\ngo get github.com/eko/gocache/store/rediscluster/v4\ngo get github.com/eko/gocache/store/rueidis/v4\ngo get github.com/eko/gocache/store/ristretto/v4\n```\n\nThen, simply use the following import statements:\n\n```go\nimport (\n\t\"github.com/eko/gocache/lib/v4/cache\"\n\t\"github.com/eko/gocache/store/redis/v4\"\n)\n```\n\nIf you run into any errors, please be sure to run `go mod tidy` to clean your go.mod file.\n\n## Available cache features in detail\n\n### A simple cache\n\nHere is a simple cache instantiation with Redis but you can also look at other available stores:\n\n#### Memcache\n\n```go\nmemcacheStore := memcache_store.NewMemcache(\n\tmemcache.New(\"10.0.0.1:11211\", \"10.0.0.2:11211\", \"10.0.0.3:11212\"),\n\tstore.WithExpiration(10*time.Second),\n)\n\ncacheManager := cache.New[[]byte](memcacheStore)\nerr := cacheManager.Set(ctx, \"my-key\", []byte(\"my-value\"),\n\tstore.WithExpiration(15*time.Second), // Override default value of 10 seconds defined in the store\n)\nif err != nil {\n    panic(err)\n}\n\nvalue := cacheManager.Get(ctx, \"my-key\")\n\ncacheManager.Delete(ctx, \"my-key\")\n\ncacheManager.Clear(ctx) // Clears the entire cache, in case you want to flush all cache\n```\n\n#### Memory (using Bigcache)\n\n```go\nbigcacheClient, _ := bigcache.NewBigCache(bigcache.DefaultConfig(5 * time.Minute))\nbigcacheStore := bigcache_store.NewBigcache(bigcacheClient)\n\ncacheManager := cache.New[[]byte](bigcacheStore)\nerr := cacheManager.Set(ctx, \"my-key\", []byte(\"my-value\"))\nif err != nil {\n    panic(err)\n}\n\nvalue := cacheManager.Get(ctx, \"my-key\")\n```\n\n#### Memory (using Ristretto)\n\n```go\nimport (\n\t\"github.com/dgraph-io/ristretto\"\n\t\"github.com/eko/gocache/lib/v4/cache\"\n\t\"github.com/eko/gocache/lib/v4/store\"\n\tristretto_store \"github.com/eko/gocache/store/ristretto/v4\"\n)\nristrettoCache, err := ristretto.NewCache(\u0026ristretto.Config{\n\tNumCounters: 1000,\n\tMaxCost: 100,\n\tBufferItems: 64,\n})\nif err != nil {\n    panic(err)\n}\nristrettoStore := ristretto_store.NewRistretto(ristrettoCache)\n\ncacheManager := cache.New[string](ristrettoStore)\nerr := cacheManager.Set(ctx, \"my-key\", \"my-value\", store.WithCost(2))\nif err != nil {\n    panic(err)\n}\n\nvalue := cacheManager.Get(ctx, \"my-key\")\n\ncacheManager.Delete(ctx, \"my-key\")\n```\n\n#### Memory (using Go-cache)\n\n```go\ngocacheClient := gocache.New(5*time.Minute, 10*time.Minute)\ngocacheStore := gocache_store.NewGoCache(gocacheClient)\n\ncacheManager := cache.New[[]byte](gocacheStore)\nerr := cacheManager.Set(ctx, \"my-key\", []byte(\"my-value\"))\nif err != nil {\n\tpanic(err)\n}\n\nvalue, err := cacheManager.Get(ctx, \"my-key\")\nif err != nil {\n\tpanic(err)\n}\nfmt.Printf(\"%s\", value)\n```\n\n#### Redis\n\n```go\nredisStore := redis_store.NewRedis(redis.NewClient(\u0026redis.Options{\n\tAddr: \"127.0.0.1:6379\",\n}))\n\ncacheManager := cache.New[string](redisStore)\nerr := cacheManager.Set(ctx, \"my-key\", \"my-value\", store.WithExpiration(15*time.Second))\nif err != nil {\n    panic(err)\n}\n\nvalue, err := cacheManager.Get(ctx, \"my-key\")\nswitch err {\n\tcase nil:\n\t\tfmt.Printf(\"Get the key '%s' from the redis cache. Result: %s\", \"my-key\", value)\n\tcase redis.Nil:\n\t\tfmt.Printf(\"Failed to find the key '%s' from the redis cache.\", \"my-key\")\n\tdefault:\n\t    fmt.Printf(\"Failed to get the value from the redis cache with key '%s': %v\", \"my-key\", err)\n}\n```\n\n#### [Redis Client-Side Caching](https://redis.io/docs/manual/client-side-caching/) (using rueidis)\n\n```go\nclient, err := rueidis.NewClient(rueidis.ClientOption{InitAddress: []string{\"127.0.0.1:6379\"}})\nif err != nil {\n    panic(err)\n}\n\ncacheManager := cache.New[string](rueidis_store.NewRueidis(\n    client,\n    store.WithExpiration(15*time.Second),\n    store.WithClientSideCaching(15*time.Second)),\n)\n\nif err = cacheManager.Set(ctx, \"my-key\", \"my-value\"); err != nil {\n    panic(err)\n}\n\nvalue, err := cacheManager.Get(ctx, \"my-key\")\nif err != nil {\n    log.Fatalf(\"Failed to get the value from the redis cache with key '%s': %v\", \"my-key\", err)\n}\nlog.Printf(\"Get the key '%s' from the redis cache. Result: %s\", \"my-key\", value)\n```\n\n#### Freecache\n\n```go\nfreecacheStore := freecache_store.NewFreecache(freecache.NewCache(1000), store.WithExpiration(10 * time.Second))\n\ncacheManager := cache.New[[]byte](freecacheStore)\nerr := cacheManager.Set(ctx, \"by-key\", []byte(\"my-value\"), opts)\nif err != nil {\n    panic(err)\n}\n\nvalue := cacheManager.Get(ctx, \"my-key\")\n```\n\n#### Pegasus\n\n```go\npegasusStore, err := pegasus_store.NewPegasus(\u0026store.OptionsPegasus{\n    MetaServers: []string{\"127.0.0.1:34601\", \"127.0.0.1:34602\", \"127.0.0.1:34603\"},\n})\n\nif err != nil {\n    fmt.Println(err)\n    return\n}\n\ncacheManager := cache.New[string](pegasusStore)\nerr = cacheManager.Set(ctx, \"my-key\", \"my-value\", store.WithExpiration(10 * time.Second))\nif err != nil {\n    panic(err)\n}\n\nvalue, _ := cacheManager.Get(ctx, \"my-key\")\n```\n\n#### Hazelcast\n\n```go\nhzClient, err := hazelcast.StartNewClient(ctx)\nif err != nil {\n    log.Fatalf(\"Failed to start client: %v\", err)\n}\n\nhzMap, err := hzClient.GetMap(ctx, \"gocache\")\nif err != nil {\n    b.Fatalf(\"Failed to get map: %v\", err)\n}\n\nhazelcastStore := hazelcast_store.NewHazelcast(hzMap)\n\ncacheManager := cache.New[string](hazelcastStore)\nerr := cacheManager.Set(ctx, \"my-key\", \"my-value\", store.WithExpiration(15*time.Second))\nif err != nil {\n    panic(err)\n}\n\nvalue, err := cacheManager.Get(ctx, \"my-key\")\nif err != nil {\n    panic(err)\n}\nfmt.Printf(\"Get the key '%s' from the hazelcast cache. Result: %s\", \"my-key\", value)\n```\n\n### A chained cache\n\nHere, we will chain caches in the following order: first in memory with Ristretto store, then in Redis (as a fallback):\n\n```go\n// Initialize Ristretto cache and Redis client\nristrettoCache, err := ristretto.NewCache(\u0026ristretto.Config{NumCounters: 1000, MaxCost: 100, BufferItems: 64})\nif err != nil {\n    panic(err)\n}\n\nredisClient := redis.NewClient(\u0026redis.Options{Addr: \"127.0.0.1:6379\"})\n\n// Initialize stores\nristrettoStore := ristretto_store.NewRistretto(ristrettoCache)\nredisStore := redis_store.NewRedis(redisClient, store.WithExpiration(5*time.Second))\n\n// Initialize chained cache\ncacheManager := cache.NewChain[any](\n    cache.New[any](ristrettoStore),\n    cache.New[any](redisStore),\n)\n\n// ... Then, do what you want with your cache\n```\n\n`Chain` cache also put data back in previous caches when it's found so in this case, if ristretto doesn't have the data in its cache but redis have, data will also get setted back into ristretto (memory) cache.\n\n### A loadable cache\n\nThis cache will provide a load function that acts as a callable function and will set your data back in your cache in case they are not available:\n\n```go\ntype Book struct {\n\tID string\n\tName string\n}\n\n// Initialize Redis client and store\nredisClient := redis.NewClient(\u0026redis.Options{Addr: \"127.0.0.1:6379\"})\nredisStore := redis_store.NewRedis(redisClient)\n\n// Initialize a load function that loads your data from a custom source\nloadFunction := func(ctx context.Context, key any) (*Book, error) {\n    // ... retrieve value from available source\n    return \u0026Book{ID: 1, Name: \"My test amazing book\"}, nil\n}\n\n// Initialize loadable cache\ncacheManager := cache.NewLoadable[*Book](\n\tloadFunction,\n\tcache.New[*Book](redisStore),\n)\n\n// ... Then, you can get your data and your function will automatically put them in cache(s)\n```\n\nOf course, you can also pass a `Chain` cache into the `Loadable` one so if your data is not available in all caches, it will bring it back in all caches.\n\n### A metric cache to retrieve cache statistics\n\nThis cache will record metrics depending on the metric provider you pass to it. Here we give a Prometheus provider:\n\n```go\n// Initialize Redis client and store\nredisClient := redis.NewClient(\u0026redis.Options{Addr: \"127.0.0.1:6379\"})\nredisStore := redis_store.NewRedis(redisClient)\n\n// Initializes Prometheus metrics service\npromMetrics := metrics.NewPrometheus(\"my-test-app\")\n\n// Initialize metric cache\ncacheManager := cache.NewMetric[any](\n\tpromMetrics,\n\tcache.New[any](redisStore),\n)\n\n// ... Then, you can get your data and metrics will be observed by Prometheus\n```\n\n### A marshaler wrapper\n\nSome caches like Redis stores and returns the value as a string so you have to marshal/unmarshal your structs if you want to cache an object. That's why we bring a marshaler service that wraps your cache and make the work for you:\n\n```go\n// Initialize Redis client and store\nredisClient := redis.NewClient(\u0026redis.Options{Addr: \"127.0.0.1:6379\"})\nredisStore := redis_store.NewRedis(redisClient)\n\n// Initialize chained cache\ncacheManager := cache.NewMetric[any](\n\tpromMetrics,\n\tcache.New[any](redisStore),\n)\n\n// Initializes marshaler\nmarshal := marshaler.New(cacheManager)\n\nkey := BookQuery{Slug: \"my-test-amazing-book\"}\nvalue := Book{ID: 1, Name: \"My test amazing book\", Slug: \"my-test-amazing-book\"}\n\nerr = marshal.Set(ctx, key, value)\nif err != nil {\n    panic(err)\n}\n\nreturnedValue, err := marshal.Get(ctx, key, new(Book))\nif err != nil {\n    panic(err)\n}\n\n// Then, do what you want with the  value\n\nmarshal.Delete(ctx, \"my-key\")\n```\n\nThe only thing you have to do is to specify the struct in which you want your value to be un-marshalled as a second argument when calling the `.Get()` method.\n\n\n### Cache invalidation using tags\n\nYou can attach some tags to items you create so you can easily invalidate some of them later.\n\nTags are stored using the same storage you choose for your cache.\n\nHere is an example on how to use it:\n\n```go\n// Initialize Redis client and store\nredisClient := redis.NewClient(\u0026redis.Options{Addr: \"127.0.0.1:6379\"})\nredisStore := redis_store.NewRedis(redisClient)\n\n// Initialize chained cache\ncacheManager := cache.NewMetric[*Book](\n\tpromMetrics,\n\tcache.New[*Book](redisStore),\n)\n\n// Initializes marshaler\nmarshal := marshaler.New(cacheManager)\n\nkey := BookQuery{Slug: \"my-test-amazing-book\"}\nvalue := \u0026Book{ID: 1, Name: \"My test amazing book\", Slug: \"my-test-amazing-book\"}\n\n// Set an item in the cache and attach it a \"book\" tag\nerr = marshal.Set(ctx, key, value, store.WithTags([]string{\"book\"}))\nif err != nil {\n    panic(err)\n}\n\n// Remove all items that have the \"book\" tag\nerr := marshal.Invalidate(ctx, store.WithInvalidateTags([]string{\"book\"}))\nif err != nil {\n    panic(err)\n}\n\nreturnedValue, err := marshal.Get(ctx, key, new(Book))\nif err != nil {\n\t// Should be triggered because item has been deleted so it cannot be found.\n    panic(err)\n}\n```\n\nMix this with expiration times on your caches to have a fine-tuned control on how your data are cached.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/eko/gocache/lib/v4/cache\"\n\t\"github.com/eko/gocache/lib/v4/store\"\n\t\"github.com/redis/go-redis/v9\"\n)\n\nfunc main() {\n\tredisStore := redis_store.NewRedis(redis.NewClient(\u0026redis.Options{\n\t\tAddr: \"127.0.0.1:6379\",\n\t}), nil)\n\n\tcacheManager := cache.New[string](redisStore)\n\terr := cacheManager.Set(ctx, \"my-key\", \"my-value\", store.WithExpiration(15*time.Second))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tkey := \"my-key\"\n\tvalue, err := cacheManager.Get(ctx, key)\n\tif err != nil {\n\t\tlog.Fatalf(\"unable to get cache key '%s' from the cache: %v\", key, err)\n\t}\n\n\tfmt.Printf(\"%#+v\\n\", value)\n}\n\n```\n\n### Write your own custom cache\n\nCache respect the following interface so you can write your own (proprietary?) cache logic if needed by implementing the following interface:\n\n```go\ntype CacheInterface[T any] interface {\n\tGet(ctx context.Context, key any) (T, error)\n\tSet(ctx context.Context, key any, object T, options ...store.Option) error\n\tDelete(ctx context.Context, key any) error\n\tInvalidate(ctx context.Context, options ...store.InvalidateOption) error\n\tClear(ctx context.Context) error\n\tGetType() string\n}\n```\n\nOr, in case you use a setter cache, also implement the `GetCodec()` method:\n\n```go\ntype SetterCacheInterface[T any] interface {\n\tCacheInterface[T]\n\tGetWithTTL(ctx context.Context, key any) (T, time.Duration, error)\n\n\tGetCodec() codec.CodecInterface\n}\n```\n\nAs all caches available in this library implement `CacheInterface`, you will be able to mix your own caches with your own.\n\n### Write your own custom store\n\nYou also have the ability to write your own custom store by implementing the following interface:\n\n```go\ntype StoreInterface interface {\n\tGet(ctx context.Context, key any) (any, error)\n\tGetWithTTL(ctx context.Context, key any) (any, time.Duration, error)\n\tSet(ctx context.Context, key any, value any, options ...Option) error\n\tDelete(ctx context.Context, key any) error\n\tInvalidate(ctx context.Context, options ...InvalidateOption) error\n\tClear(ctx context.Context) error\n\tGetType() string\n}\n```\n\nOf course, I suggest you to have a look at current caches or stores to implement your own.\n\n### Custom cache key generator\n\nYou can implement the following interface in order to generate a custom cache key:\n\n```go\ntype CacheKeyGenerator interface {\n\tGetCacheKey() string\n}\n```\n\n### Benchmarks\n\n![Benchmarks](https://raw.githubusercontent.com/eko/gocache/master/lib/misc/benchmarks.jpeg)\n\n## Run tests\n\nTo generate mocks using mockgen library, run:\n\n```bash\n$ make mocks\n```\n\nTest suite can be run with:\n\n```bash\n$ make test # run unit test\n```\n\n## Community\n\nPlease feel free to contribute on this library and do not hesitate to open an issue if you want to discuss about a feature.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feko%2Fgocache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feko%2Fgocache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feko%2Fgocache/lists"}