Documentation
¶
Overview ¶
Package nimbus is a Cloud Run-first cache for Go.
It combines a fast in-process L1, a shared versioned L2 (the source of truth), and a Pub/Sub invalidation bus that keeps per-instance caches coherent across ephemeral, autoscaling Cloud Run instances.
The correctness backbone is the fill invariant: no value enters L1 except stamped with a version minted by the authoritative L2 store, decided atomically against concurrent invalidations. The bus is a latency optimization, not the sole coherence mechanism; an instance that misses a broadcast still converges on its next L2 read. See DESIGN.md for details.
Example ¶
Example shows the simplest use: an L1-only cache with read-through and stampede protection. The loader runs once; subsequent reads hit L1.
package main
import (
"context"
"fmt"
"time"
"github.com/ant-caor/nimbus"
)
func main() {
loader := func(_ context.Context, key string) (int, error) {
return len(key), nil // pretend this is an expensive lookup
}
cache, err := nimbus.NewBuilder[string, int](loader).
TTL(time.Minute, 0).
Build()
if err != nil {
panic(err)
}
defer func() { _ = cache.Close() }()
v, _ := cache.GetOrLoad(context.Background(), "hello")
fmt.Println(v)
}
Output: 5
Index ¶
- Variables
- type Builder
- func (b *Builder[K, V]) BackgroundRefresh(workers int) *Builder[K, V]
- func (b *Builder[K, V]) Build() (Cache[K, V], error)
- func (b *Builder[K, V]) Bus(bus invalidation.Bus) *Builder[K, V]
- func (b *Builder[K, V]) Clock(c clock.Clock) *Builder[K, V]
- func (b *Builder[K, V]) Jitter(frac float64) *Builder[K, V]
- func (b *Builder[K, V]) KeyString(fn func(K) string) *Builder[K, V]
- func (b *Builder[K, V]) L1(s store.Store[V]) *Builder[K, V]
- func (b *Builder[K, V]) L2(s store.VersionedStore[V]) *Builder[K, V]
- func (b *Builder[K, V]) MaxConcurrentRefresh(n int) *Builder[K, V]
- func (b *Builder[K, V]) MaxTTL(d time.Duration) *Builder[K, V]
- func (b *Builder[K, V]) NegativeTTL(d time.Duration) *Builder[K, V]
- func (b *Builder[K, V]) RefreshMode(m RefreshMode) *Builder[K, V]
- func (b *Builder[K, V]) RefreshTimeout(d time.Duration) *Builder[K, V]
- func (b *Builder[K, V]) TTL(fresh, staleWindow time.Duration) *Builder[K, V]
- type Cache
- type EntryOption
- type Loader
- type RefreshMode
- type Stats
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrClosed = errors.New("nimbus: cache closed")
ErrClosed is returned by cache operations invoked after Close.
var ErrNotFound = errors.New("nimbus: not found")
ErrNotFound is the negative-cache sentinel. A Loader returns ErrNotFound to signal that a key is known-absent, which makes nimbus store a negative entry (subject to the negative TTL). GetOrLoad returns ErrNotFound when it serves such a negative hit. It is distinct from a transient load failure, which is never cached.
Functions ¶
This section is empty.
Types ¶
type Builder ¶
type Builder[K comparable, V any] struct { // contains filtered or unexported fields }
Builder constructs a Cache. The user names the key and value types once, on NewBuilder; every configuration method is a plain (non-generic) method, so there is no per-option type-annotation tax.
func NewBuilder ¶
func NewBuilder[K comparable, V any](loader Loader[K, V]) *Builder[K, V]
NewBuilder starts building a Cache around loader. Return ErrNotFound from the loader to request negative-caching of a key.
func (*Builder[K, V]) BackgroundRefresh ¶
BackgroundRefresh selects background revalidation with the given worker count. This requires Cloud Run always-on CPU; see RefreshBackground.
func (*Builder[K, V]) Bus ¶
func (b *Builder[K, V]) Bus(bus invalidation.Bus) *Builder[K, V]
Bus sets the cross-instance invalidation bus. Defaults to a no-op bus.
func (*Builder[K, V]) Jitter ¶
Jitter applies +/- frac randomization to the fresh TTL to avoid synchronized expiry. frac must be in [0, 1].
func (*Builder[K, V]) KeyString ¶
KeyString overrides how a key K is rendered to the string used by L1, L2, and the bus. The default renders string keys directly and integer keys via strconv (both allocation-light on the hot path) and falls back to fmt for any other type; supply KeyString to keep non-string, non-integer keys zero-allocation.
func (*Builder[K, V]) L2 ¶
func (b *Builder[K, V]) L2(s store.VersionedStore[V]) *Builder[K, V]
L2 sets the shared, authoritative tier (the source of truth).
func (*Builder[K, V]) MaxConcurrentRefresh ¶
MaxConcurrentRefresh caps how many request-bound revalidations may run at once, so a synchronized stale wave cannot fan out into an unbounded burst of loader calls against the origin. On saturation a refresh is dropped (the entry stays stale and re-triggers on the next request); stale-while-revalidate keeps serving meanwhile. n <= 0 selects a CPU-scaled default. Has no effect in background refresh mode, whose concurrency is its worker count.
func (*Builder[K, V]) MaxTTL ¶
MaxTTL caps the absolute lifetime of an entry so stale-while-revalidate cannot renew it indefinitely without reconciling against L2.
func (*Builder[K, V]) NegativeTTL ¶
NegativeTTL sets how long a known-absent key is negatively cached. If unset, it falls back to the fresh TTL; if that is also unset, negative entries persist until evicted. Negative entries are L1-only and converge across instances via the bus or this TTL (not via an L2 read), so keep it modest.
func (*Builder[K, V]) RefreshMode ¶
func (b *Builder[K, V]) RefreshMode(m RefreshMode) *Builder[K, V]
RefreshMode selects request-bound (default) or background revalidation.
func (*Builder[K, V]) RefreshTimeout ¶
RefreshTimeout bounds how long a single stale-while-revalidate refresh may run. Defaults to 5s.
type Cache ¶
type Cache[K comparable, V any] interface { // Get returns a cached value if present and servable (fresh or stale). It is // a read-only peek: it never invokes the loader, never schedules a // revalidation, and does not update Stats. A negative entry reports false. Get(ctx context.Context, key K) (V, bool, error) // GetOrLoad returns the value, loading it through the loader on a miss with // stampede protection. It returns ErrNotFound on a negative hit. GetOrLoad(ctx context.Context, key K) (V, error) // Set writes a value and broadcasts an invalidation so other instances drop // any stale or negative entry for the key. Set(ctx context.Context, key K, val V, opts ...EntryOption) error // Invalidate evicts a key locally and broadcasts the eviction. Invalidate(ctx context.Context, key K) error // InvalidateTag evicts every key carrying tag and broadcasts the eviction. InvalidateTag(ctx context.Context, tag string) error // Stats returns a counter snapshot. Stats() Stats // Close stops background work and the bus subscription. It does not close // stores or clients passed in by the caller. Close() error }
Cache is a Cloud Run-first cache keyed by a user type K with values of type V.
type EntryOption ¶
type EntryOption interface {
// contains filtered or unexported methods
}
EntryOption customizes a single Set. It is an opaque, sealed interface: options are produced only by the With* constructors in this package, which keeps the set of options extensible without exposing the entry's internals or widening the public API surface.
func WithTags ¶
func WithTags(tags ...string) EntryOption
WithTags associates the written key with one or more tags so it can later be invalidated via InvalidateTag.
type Loader ¶
type Loader[K comparable, V any] func(ctx context.Context, key K) (V, error)
Loader fetches the authoritative value for key on a cold miss or on revalidation. Return ErrNotFound to request negative-caching of key; any other error is treated as transient and is not cached.
type RefreshMode ¶
type RefreshMode int
RefreshMode selects how stale-while-revalidate revalidation is executed.
The distinction matters on Cloud Run: with request-only CPU allocation, background goroutines are throttled to near-zero between requests, so a detached refresh can stall. See DESIGN.md.
const ( // RefreshRequestBound runs revalidation within the lifecycle of the request // that observed the stale entry, so CPU is allocated while that request is // in flight. It is the default and is safe under Cloud Run request-only CPU. RefreshRequestBound RefreshMode = iota // RefreshBackground runs revalidation on a long-lived worker pool off the // request path. It REQUIRES Cloud Run always-on CPU (instance-based // billing); otherwise refreshes stall between requests. RefreshBackground )
type Stats ¶
type Stats struct {
Hits uint64
StaleHits uint64 // served stale while a revalidation was scheduled
Misses uint64
Loads uint64
LoadErrors uint64
NegativeHits uint64
Refreshes uint64 // stale-while-revalidate refreshes scheduled
BusEvicts uint64 // L1 entries evicted by cross-instance invalidation events
L2Errors uint64 // fills that degraded to an origin response because L2 was unreachable
Evictions uint64
L1Len int
}
Stats is a snapshot of cache counters. The counters are monotonic and read independently, so a snapshot is eventually-consistent rather than a single consistent instant (Hits and Misses may be sampled a few operations apart).
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
basic
command
Command basic is the smallest Nimbus example: an L1-only cache with stampede protection.
|
Command basic is the smallest Nimbus example: an L1-only cache with stampede protection. |
|
internal
|
|
|
clock
Package clock provides an injectable time source so TTL behavior is deterministic in tests and clock skew can be simulated per instance.
|
Package clock provides an injectable time source so TTL behavior is deterministic in tests and clock skew can be simulated per instance. |
|
singleflight
Package singleflight is a thin generic wrapper over golang.org/x/sync/singleflight.
|
Package singleflight is a thin generic wrapper over golang.org/x/sync/singleflight. |
|
Package invalidation defines the cross-instance eviction bus contracts.
|
Package invalidation defines the cross-instance eviction bus contracts. |
|
redispubsub
Package redispubsub implements the nimbus invalidation bus over Redis Pub/Sub (via rueidis).
|
Package redispubsub implements the nimbus invalidation bus over Redis Pub/Sub (via rueidis). |
|
gcppubsub
module
|
|
|
metrics
module
|
|
|
Package redisstore is nimbus's shared, authoritative L2 tier, backed by Redis or Memorystore via rueidis.
|
Package redisstore is nimbus's shared, authoritative L2 tier, backed by Redis or Memorystore via rueidis. |
|
Package refresh defines how stale-while-revalidate revalidation is scheduled.
|
Package refresh defines how stale-while-revalidate revalidation is scheduled. |
|
Package store defines the storage contracts for nimbus tiers.
|
Package store defines the storage contracts for nimbus tiers. |
|
memory
Package memory is nimbus's own in-process L1 store: a sharded LRU with TTL.
|
Package memory is nimbus's own in-process L1 store: a sharded LRU with TTL. |