tiered

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package tiered composes two cache.Cache backends into a single two-level (L1 + L2) read-through / write-through cache.

The canonical shape is a fast in-process L1 (drops/cache/memory) in front of a shared, network L2 (drops/cache/redis or drops/cache/memcached):

l1 := memory.New(memory.Options{MaxEntries: 10_000})
l2, _ := redis.New(redis.Options{Addr: "redis:6379"})
c := tiered.New(tiered.Options{L1: l1, L2: l2, L1TTL: 30 * time.Second})

Reads check L1 first; on a miss they fall through to L2 and, on an L2 hit, backfill L1 so the next read is local. Writes and deletes fan out to both tiers. GetOrLoad adds read-through population from an origin function with singleflight stampede protection, so a burst of concurrent misses for the same key triggers exactly one load.

The zero value is not usable; construct with New. Both tiers must be supplied. Tiered is itself a cache.Cache (and a cache.MultiCache), so it nests: an L2 can itself be another tiered cache.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache is the two-level cache. It satisfies cache.Cache and cache.MultiCache.

func New

func New(opts Options) *Cache

New builds a tiered cache. It panics if either tier is nil — a misconfiguration that can only be a programming error at wiring time.

func (*Cache) Close

func (c *Cache) Close() error

Close closes both tiers, returning the first error.

func (*Cache) Delete

func (c *Cache) Delete(ctx context.Context, keys ...string) (n int, err error)

Delete removes the keys from both tiers and returns the count reported by L2 (the source of truth). L1 is always cleared even if L2 delete fails, so a stale local copy is never left behind.

func (*Cache) Exists

func (c *Cache) Exists(ctx context.Context, key string) (_ bool, err error)

Exists reports whether key is live in either tier.

func (*Cache) Get

func (c *Cache) Get(ctx context.Context, key string) (_ []byte, err error)

Get returns the value for key, checking L1 then L2. On an L2 hit the entry is backfilled into L1. Returns cache.ErrNotFound when neither tier has it.

func (*Cache) GetMulti

func (c *Cache) GetMulti(ctx context.Context, keys ...string) (_ map[string][]byte, err error)

GetMulti fetches keys from L1, then fills the gaps from L2 in a single batched call, backfilling every L2 hit into L1. Missing keys are simply absent from the result.

func (*Cache) GetOrLoad

func (c *Cache) GetOrLoad(ctx context.Context, key string, ttl time.Duration, load Loader) (_ []byte, err error)

GetOrLoad returns key's cached value, or — on a full miss — calls load exactly once (even under concurrent callers for the same key, via singleflight), stores the result in both tiers with ttl, and returns it. This is the read-through / stampede-protection entry point: a thundering herd of requests for a cold key results in a single origin load, not one per request.

load is not called for keys already present in L1 or L2. A load error is propagated and nothing is cached.

func (*Cache) Ping

func (c *Cache) Ping(ctx context.Context) (err error)

Ping succeeds only when both tiers are reachable.

func (*Cache) Set

func (c *Cache) Set(ctx context.Context, key string, value []byte, ttl time.Duration) (err error)

Set writes value into both tiers. L2 gets the full ttl; L1 gets the smaller of ttl and L1TTL (when L1TTL is set) so a local copy can't outlive the shared one. An L2 write failure is returned without touching L1, so the tiers can't diverge on a failed write.

func (*Cache) SetMulti

func (c *Cache) SetMulti(ctx context.Context, items map[string][]byte, ttl time.Duration) (err error)

SetMulti writes every item to L2 then L1, with the same TTL rules as Set. An L2 failure is returned before L1 is touched.

func (*Cache) TTL

func (c *Cache) TTL(ctx context.Context, key string) (_ time.Duration, err error)

TTL returns the remaining lifetime from L2, the source of truth.

type Loader

type Loader func(ctx context.Context) ([]byte, error)

Loader produces the authoritative value for a key on a full cache miss.

type Options

type Options struct {
	// L1 is the fast, near tier (typically in-process memory). Required.
	L1 cache.Cache

	// L2 is the slow, shared tier (typically network Redis/Memcached),
	// and the source of truth for TTL. Required.
	L2 cache.Cache

	// L1TTL caps how long a value backfilled into L1 (on an L2 hit) or
	// written into L1 (on Set) may live, bounding how stale a local copy
	// can get relative to L2. 0 means "mirror the L2 TTL" — the value
	// lives in L1 for L2's remaining lifetime (which requires an extra
	// L2.TTL round-trip on backfill). A small non-zero L1TTL avoids that
	// round-trip and is the recommended production setting.
	L1TTL time.Duration

	// Hook fires after every tiered operation with kind = "cache.get",
	// "cache.set", "cache.del", "cache.exists", "cache.ttl",
	// "cache.ping", "cache.load". It observes the tiered call as a whole;
	// the underlying L1/L2 caches fire their own hooks independently.
	Hook drops.Hook

	// Clock is injectable for tests; defaults to time.Now.
	Clock func() time.Time
}

Options configures a tiered cache.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL