{"id":24930440,"url":"https://github.com/motoki317/sc","last_synced_at":"2025-04-09T21:51:23.268Z","repository":{"id":48129601,"uuid":"477054263","full_name":"motoki317/sc","owner":"motoki317","description":"A simple generic in-memory caching layer","archived":false,"fork":false,"pushed_at":"2025-03-14T14:35:42.000Z","size":138,"stargazers_count":25,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-09T21:51:17.669Z","etag":null,"topics":["cache","caching","generics","go","golang","memoization","memoize"],"latest_commit_sha":null,"homepage":"","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/motoki317.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-02T12:54:38.000Z","updated_at":"2025-03-14T14:29:52.000Z","dependencies_parsed_at":"2024-04-18T22:17:16.291Z","dependency_job_id":null,"html_url":"https://github.com/motoki317/sc","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motoki317%2Fsc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motoki317%2Fsc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motoki317%2Fsc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/motoki317%2Fsc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/motoki317","download_url":"https://codeload.github.com/motoki317/sc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248119402,"owners_count":21050754,"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","caching","generics","go","golang","memoization","memoize"],"created_at":"2025-02-02T13:54:30.889Z","updated_at":"2025-04-09T21:51:23.240Z","avatar_url":"https://github.com/motoki317.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sc\n\n[![GitHub release](https://img.shields.io/github/release/motoki317/sc.svg)](https://github.com/motoki317/sc/releases/)\n![CI main](https://github.com/motoki317/sc/actions/workflows/main.yaml/badge.svg)\n[![codecov](https://codecov.io/gh/motoki317/sc/branch/master/graph/badge.svg)](https://codecov.io/gh/motoki317/sc)\n[![Go Reference](https://pkg.go.dev/badge/github.com/motoki317/sc.svg)](https://pkg.go.dev/github.com/motoki317/sc)\n\nsc is a simple in-memory caching layer for golang.\n\n[Introduction slide](https://speakerdeck.com/motoki317/effective-generic-cache-in-golang) (Japanese)\n\n## Usage\n\nWrap your function with sc - it will automatically cache the values for specified amount of time, with minimal overhead.\n\n```go\ntype HeavyData struct {\n\tData string\n\t// and all the gazillion fields you may have in your data\n}\n\nfunc retrieveHeavyData(_ context.Context, name string) (*HeavyData, error) {\n\t// Query to database or something...\n\treturn \u0026HeavyData{\n\t\tData: \"my-data-\" + name,\n\t}, nil\n}\n\nfunc main() {\n\t// Wrap your data retrieval function.\n\tcache, _ := sc.New[string, *HeavyData](retrieveHeavyData, 1*time.Minute, 2*time.Minute, sc.WithLRUBackend(500))\n\t// It will automatically call the given function if value is missing.\n\tfoo, _ := cache.Get(context.Background(), \"foo\")\n}\n```\n\nFor a more detailed guide, see [reference](https://pkg.go.dev/github.com/motoki317/sc).\n\n## Notable Features\n\n- Simple to use: wrap your function with `New()` and just call `Get()`.\n    - There is no `Set()` method. Calling `Get()` will automatically retrieve the value for you.\n    - This prevents [cache stampede](https://en.wikipedia.org/wiki/Cache_stampede) problem idiomatically (see below).\n- Supports 1.18 generics - both key and value are generic.\n    - No `interface{}` or `any` used other than in type parameters, even in internal implementations.\n- All methods are safe to be called from multiple goroutines.\n- Ensures only a single goroutine is launched per key to retrieve value.\n- Allows 'graceful cache replacement' (if `freshFor` \u003c `ttl`) - a single goroutine is launched in the background to\n  re-fetch a fresh value while serving stale value to readers.\n- Allows strict request coalescing (`EnableStrictCoalescing()` option) - ensures that all returned values are fresh (a\n  niche use-case).\n\n## Supported cache backends (cache replacement policy)\n\n- Built-in map (default)\n  - Note: This backend cannot have max number of items configured. It holds all values in memory until expiration. For more, see the [documentation](https://pkg.go.dev/github.com/motoki317/sc#WithMapBackend).\n- LRU (Least Recently Used)\n- 2Q (Two Queue Cache)\n\n## The design\n\n### Why no Set() method? / Why cannot I dynamically provide load function to Get() method?\n\nShort answer: sc is designed as a foolproof 'cache layer', not an overly complicated 'cache library'.\n\nLong answer:\n\nsc is designed as a simple, foolproof 'cache layer'.\nUsers of sc simply wrap data-retrieving functions and retrieve values via the cache.\nBy doing so, sc automatically reuses retrieved values and minimizes load on your data-store.\n\nNow, let's imagine how users would use a more standard cache library with `Set()` method.\nOne could use `Get()` and `Set()` method to build the following logic:\n\n1. `Get()` from the cache.\n2. If the value is not in the cache, retrieve it from the source.\n3. `Set()` the value.\n\nThis is probably the most common use-case, and it is fine for most applications.\nBut if you do not write it properly, the following problems may occur:\n\n- If data flow is large, cache stampede might occur.\n- Accidentally using different keys for `Get()` and `Set()`.\n- Over-caching or under-caching by using inappropriate keys.\n\nsc solves the problems mentioned above by acting as a 'cache layer'.\n\n- sc will manage the requests for you - no risk of accidentally writing a bad caching logic and overloading your data-store with cache stampede.\n- No manual `Set()` needed - no risk of accidentally using different keys.\n- Only the cache key is passed to the pre-provided replacement function - no risk of over-caching or under-caching.\n\nThis is why sc does not have a `Set()` method, and forces you to provide replacement function on setup.\nIn this way, there is no risk of cache stampede and possible bugs described above -\nsc will handle it for you.\n\n### But I still want to manually `Set()` value on update!\n\nBy the nature of the design, sc is a no-write-allocate type cache.\nYou update the value on the data-store, and then call `Forget()` to clear the value on the cache.\nsc will automatically load the value next time `Get()` is called.\n\nOne could design another cache layer library with `Set()` method which automatically calls the pre-provided\nupdate function which updates the data-store, then updates the value on the cache.\nBut that would add whole another level of complexity - sc aims to be a simple cache layer.\n\n## Inspirations from\n\nI would like to thank the following libraries for giving me ideas:\n\n- [go-chi/stampede: Function and HTTP request coalescer](https://github.com/go-chi/stampede)\n  - For \"request coalescing\" and \"cache layer\" idea\n- [singleflight package - golang.org/x/sync/singleflight - pkg.go.dev](https://pkg.go.dev/golang.org/x/sync/singleflight)\n  - For internal implementation\n- [Songmu/smartcache](https://github.com/Songmu/smartcache)\n  - For \"graceful replacement\" idea\n    - The term \"graceful\" comes from the [varnish](https://varnish-cache.org/) configuration.\n- [methane/zerotimecache](https://github.com/methane/zerotimecache)\n  - For \"zero-time cache\" idea\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmotoki317%2Fsc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmotoki317%2Fsc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmotoki317%2Fsc/lists"}