{"id":17159205,"url":"https://github.com/cshum/hybridcache","last_synced_at":"2025-07-15T11:04:46.654Z","repository":{"id":57636198,"uuid":"425148391","full_name":"cshum/hybridcache","owner":"cshum","description":"A multi-level cache library with cache stampede prevention for Go","archived":false,"fork":false,"pushed_at":"2022-04-06T04:25:54.000Z","size":270,"stargazers_count":124,"open_issues_count":0,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-02T06:22:25.398Z","etag":null,"topics":["cache","go","golang","library","performance","stampede"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/cshum/hybridcache","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cshum.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-11-06T03:41:09.000Z","updated_at":"2025-03-11T16:30:30.000Z","dependencies_parsed_at":"2022-09-26T20:21:40.427Z","dependency_job_id":null,"html_url":"https://github.com/cshum/hybridcache","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/cshum/hybridcache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cshum%2Fhybridcache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cshum%2Fhybridcache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cshum%2Fhybridcache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cshum%2Fhybridcache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cshum","download_url":"https://codeload.github.com/cshum/hybridcache/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cshum%2Fhybridcache/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265430125,"owners_count":23763938,"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":["cache","go","golang","library","performance","stampede"],"created_at":"2024-10-14T22:13:39.205Z","updated_at":"2025-07-15T11:04:46.630Z","avatar_url":"https://github.com/cshum.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HybridCache\n\nA multi-level cache library with cache stampede prevention for Go\n\n```go\nimport \"github.com/cshum/hybridcache\"\n\n// Redis cache adapter based on Redigo\nredisCache := cache.NewRedis(\u0026redis.Pool{...})\n\n// Memory cache adapter based on Ristretto\nmemoryCache := cache.NewMemory(1e5, 1\u003c\u003c29, time.Hour)\n// bounded maximum ~100,000 items, ~500mb memory size, 1 hour ttl\n\n// Hybrid cache adapter with Redis upstream + Memory downstream\nhybridCache := cache.NewHybrid(redisCache, memoryCache)\n```\n\nThe hybrid combination allows Redis upstream coordinate across multiple servers, while Memory downstream ensures minimal network I/O which brings the fastest response time. \nShall the Redis upstream failed, memory downstream will still operate independently without service disruption.\n```go\n// cache function client\ncacheFunc := cache.NewFunc(hybridCache, time.Seconds*20, time.Minute, time.Hour)\n// 20 seconds execution timeout, 1 minute fresh-for timeout, 1 hour ttl\n\nvar items []*Items\nsomeKey := fmt.Sprintf(\"key-%d\", id)\n// wrap function call with hybrid cache\nif err := cacheFunc.Do(ctx, someKey, func(ctx context.Context) (interface{}, error) {\n\treturn someHeavyOperations(ctx, id)\n}, \u0026items); err != nil {\n\treturn err\n}\nfor _, item := range items {\n\t...\n}\n\n// cache http client\ncacheHTTP := cache.NewHTTP(hybridCache, time.Seconds*30, time.Minute, time.Hour*12)\n// 30 seconds request timeout, 1 minute fresh-for timeout, 12 hour ttl\n// setting a high ttl will enable \"always online\" in case of service disruption.\n// content will lazy refresh in background (goroutine) after fresh-for timeout\n\nr := mux.NewRouter()\n// use as an HTTP middleware\nr.Use(cacheHTTP.Handler)\nr.Mount(\"/\", myWebServices)\nhttp.ListenAndServe(\":3001\", r)\n\n// wraps a http.RoundTripper\ncachedTransport := cacheHTTP.RoundTripper(http.DefaultTransport)\n```\nA simple function wrapper or HTTP middleware gives you under the hood:\n\n* Lazy background refresh with timeout - after fresh-for timeout exceeded, the next cache hit will trigger a refresh in goroutine, where context deadline is detached from parent and based on wait-for timeout. \n* Cache stampede prevention - uses singleflight for memory call suppression and `SEX NX` for redis.\n* Marshal and unmarshal options for function calls - default to msgpack, with options to configure your own.\n\n\nConditional caching with `cache.ErrNoCache`:\n\n```go\nvar items []*Items\nsomeKey := fmt.Sprintf(\"key-%d\", id)\nif err := cacheFunc.Do(ctx, someKey, func(ctx context.Context) (interface{}, error) {\n\tstart := time.Now()\n\titems, err := someHeavyOperations(ctx, id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif time.Since(start) \u003c time.Millisecond*300 {\n\t\t// no cache if response within 300 milliseconds\n\t\treturn items, cache.ErrNoCache\n\t}\n\treturn items, nil\n}, \u0026items); err != nil {\n\t// ErrNoCache will NOT appear outside\n\treturn err\n}\n```\nMore options:\n```go\ncacheFunc := cache.NewFunc(hybridCache, time.Seconds*20, time.Minute, time.Hour)\ncacheFunc.Marshal = json.Marshal\ncacheFunc.Unmarshal = json.Unmarshal\n// custom Marshal Unmarshal function, default msgpack\n\n\nh := cache.NewHTTP(hybridCache, time.Seconds*30, time.Minute, time.Hour*12)\nh.RequestKey = func(r *http.Request) string {\n\t// cache key by url excluding query params\n\treturn strings.Split(r.URL.String(), \"?\")[0]\n}\nh.AcceptRequest = func(r *http.Request) bool {\n\tif strings.Contains(r.URL.RawQuery, \"nocache\") {\n\t\t// no cache if nocache appears in query\n\t\treturn false\n\t}\n\treturn true\n}\nh.AcceptResponse = func(res *http.Response) bool {\n\tif res.StatusCode != http.StatusOK {\n\t\t// no cache if status code not 200\n\t\treturn false\n\t}\n\tif res.ContentLength \u003e= 1\u003c\u003c20 {\n\t\t// no cache if over 1 MB\n\t\treturn false\n\t}\n\treturn true\n}\ncacheHandler := h.Handler\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcshum%2Fhybridcache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcshum%2Fhybridcache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcshum%2Fhybridcache/lists"}