A Go cache library with probabilistic revalidation and optional singleflight loading. It smooths refreshes near TTL expiry while deduplicating concurrent loads.
- Smooth probabilistic revalidation near expiry
- Built-in singleflight loader (can be disabled)
- Zero external dependencies in the core module
- Pluggable storage (
CacheProvider) and storage codecs (CacheStorageCodec)
Core functionality is covered by a high level of automated tests.
Within the revalidation window, the cache reloads with probability
where t is the remaining time and w the configured revalidation window. The
steepness k is set so that
How widely reloads actually spread depends on how often the key is requested: because the probability is drawn per request, frequently requested keys tend to reload earlier in the window than rarely requested ones.
This design is inspired by the following references:
- Cache Stampede: Avoiding Hot Spots in Distributed Caching Systems
- Sometimes I Cache | The Cloudflare Blog
go get github.com/abema/cremaProviders and codecs under ext/ are separate Go modules. Add only the modules
your application uses. For example:
go get github.com/abema/crema/ext/golang-lruGo 1.25 or newer is required.
package main
import (
"context"
"fmt"
"time"
"github.com/abema/crema"
golanglru "github.com/abema/crema/ext/golang-lru"
)
func main() {
provider := golanglru.NewCacheProvider[crema.CacheObject[int]](128, time.Minute)
cache := crema.NewCache(provider, crema.NoopCacheStorageCodec[int]{})
value, err := cache.GetOrLoad(
context.Background(),
"answer",
time.Minute,
func(context.Context) (int, error) {
// Database or computation logic here.
return 42, nil
},
)
if err != nil {
panic(err)
}
fmt.Println(value)
}- CacheProvider: Responsible for persistence with TTL handling. Works with Redis/Memcached, files, or databases.
- CacheStorageCodec: Encodes/decodes cached objects. Swap in JSON, protobuf, or your own codec.
- CacheObject: A thin wrapper holding
Valueand absolute expiry (ExpireAtMillis). - Revalidation fallback: Revalidation failures return a still-valid cached value by default. Missing or expired values still return the loader error.
WithRevalidationWindow(duration): Set how long before expiry probabilistic reloads may start (0disables them)WithDirectLoader(): Disable singleflight and call loaders directlyWithMaxLoadTimeout(duration): Set max duration for singleflight loaders and their synchronous cache writebacks (ignored withWithDirectLoader())WithRevalidationFallback(enabled): Enable or disable revalidation fallback (enabled by default)WithLogger(logger): Override warning logger for get/set failuresWithMetricsProvider(metrics): Record cache and loader eventsWithNegativeCacheProvider(provider, ttl, isNegative): Cache absent loader results through a separate providerWithLoadErrorCacheProvider(provider, ttl, shouldCache): Cache selected load errors through a separate provider
The default singleflight load path runs in an internal goroutine. crema does
not recover panics from that path, so application-provided loaders, callbacks,
metrics providers, and cache providers must avoid panics or recover them at
their own boundary. WithDirectLoader runs the loader in the caller's
goroutine.
Load results are not cached by default. Use WithNegativeCacheProvider when a
loader result means that the value does not exist. This includes a successful
empty result and errors such as sql.ErrNoRows or an application-specific
ErrNotFound.
cache := crema.NewCache(
provider,
crema.NoopCacheStorageCodec[int]{},
crema.WithNegativeCacheProvider(negativeProvider, 500*time.Millisecond, func(value int, err error) bool {
return value == 0 && err == nil || errors.Is(err, ErrNotFound)
}),
)Getdoes not use this cache. After a value-cache miss,GetOrLoadreturns a live cached negative result without invoking the loader.- The negative provider is a
CacheProvider[CacheLoadResult[V]]; its TTL is independent of the value TTL passed toGetOrLoad. - A still-valid value wins over a negative result during revalidation.
- Successful
SetandDeleteinvalidate the negative result. - An in-process provider such as Ristretto preserves error identity for
errors.Isanderrors.As. - A
NegativeCacheMetricsProvidercan record negative-cache hits and stores.
WithLoadErrorCacheProvider is for errors that do not mean absence, such as a
short-lived rate-limit or backend-unavailable error. Its provider is a
CacheProvider[error]. It can be configured together with the negative cache;
negative results take precedence when both predicates match.
| Name | Package | Notes | Example |
|---|---|---|---|
| RistrettoCacheProvider | github.com/abema/crema/ext/ristretto |
dgraph-io/ristretto/v2 backend with TTL support. | ✅ |
| RedisCacheProvider | github.com/abema/crema/ext/rueidis |
Redis backend using rueidis. | ✅ |
| ValkeyCacheProvider | github.com/abema/crema/ext/valkey-go |
Valkey (Redis protocol) backend. | ✅ |
| MemcachedCacheProvider | github.com/abema/crema/ext/gomemcache |
Memcached backend with long-TTL handling. | - |
| CacheProvider | github.com/abema/crema/ext/golang-lru |
hashicorp/golang-lru backend with a provider-wide TTL. | Quick Start |
| Provider | github.com/abema/crema/ext/madvfree |
Best-effort anonymous-mmap byte cache for 64-bit Linux and macOS. | ✅ |
| Name | Package | Notes | Example |
|---|---|---|---|
| NoopCacheStorageCodec | github.com/abema/crema |
Pass-through codec for in-memory cache objects. | Quick Start |
| JSONByteStringCodec | github.com/abema/crema |
Standard library JSON encoding to []byte. |
✅ |
| JSONByteStringCodec | github.com/abema/crema/ext/go-json |
goccy/go-json encoding to []byte. |
- |
| ProtobufCodec | github.com/abema/crema/ext/protobuf |
Protobuf encoding to []byte. |
✅ |
| BinaryCompressionCodec | github.com/abema/crema |
Wraps another codec and zlib-compresses encoded bytes above a threshold. | ✅ |
NewBinaryCompressionCodec does not limit decompressed size by default. Use
WithMaxDecompressedBytes when compressed values may come from an untrusted
or shared backend.
| Name | Package | Notes | Example |
|---|---|---|---|
| BaseMetricsProvider | github.com/abema/crema |
Embeddable no-op base for custom metrics providers. | - |
| NoopMetricsProvider | github.com/abema/crema |
Default metrics provider; records nothing. | - |
MetricsProvider records cache operations and loader starts, reasons
(miss, expired, revalidation, or get_error), errors, and concurrency.
Direct loads always report a concurrency of 1. Embed BaseMetricsProvider and
override only the callbacks you need.
Cache is goroutine-safe as long as its CacheProvider and
CacheStorageCodec implementations are goroutine-safe.
go generate
go test ./... $(find ./ext/ -type d -mindepth 1 -maxdepth 1 | sed 's|$|/...|') ./ext/madvfree/bench/...Examples that use Redis or Valkey require a server on 127.0.0.1:6379; CI
runs them separately with the required service.
cmd/plot-revalidation: SVG plot generator for revalidation curves
Crema is the golden foam that forms on top of a freshly pulled espresso coffee shot. Like crema that gradually dissipates over time, this cache library probabilistically refreshes entries, ensuring your data stays fresh without the overhead of deterministic expiration checks.