Documentation
¶
Overview ¶
Package smartcache is a small, type-safe caching library.
It provides a generic read-through / write-through cache (Cache[T]) over a pluggable byte key-value backend (the CacheStore interface). Cache[T] owns serialization (JSON by default), enforces a positive TTL backstop so a missed or failed eviction can only cause bounded staleness, applies downward-only TTL jitter by default so keys written together don't expire in sync, optionally negative-caches "not found" results, and de-duplicates concurrent cold loads with singleflight.
Every Cache[T] is built through a Manager: NewManager creates one over a CacheStore and a set of defaults, and the package-level generic function Register creates a named, type-safe Cache[T] on it (Go methods cannot have type parameters, so registration cannot be a method on Manager). GetMany batches a read-through lookup for several keys in one round trip, calling the caller's batch loader at most once for whatever keys are not already cached.
The library performs no logging itself: Get, GetMany, and Put return an Outcome so the caller can meter cache-hit rate and detect populate failures without any of the library's own log lines. A Manager can optionally be configured with WithOTLP to export per-cache metrics (hit/miss/load/evict counters and a load-latency histogram) over OpenTelemetry OTLP, fire-and- forget on a background interval.
The backend is injected as an interface, so Redis (see subpackage redisstore) can be swapped for any other store (see subpackage memstore) without touching call sites.
Index ¶
- Variables
- type AliasCacheStore
- type AliasMode
- type AliasOps
- type AliasRef
- type BatchCacheStore
- type Cache
- func (c *Cache[T]) Evict(ctx context.Context, val *T) error
- func (c *Cache[T]) EvictByAlias(ctx context.Context, alias AliasRef) error
- func (c *Cache[T]) EvictByKey(ctx context.Context, key string) error
- func (c *Cache[T]) EvictByKeyPrefix(ctx context.Context, keyPrefix string) (int64, error)
- func (c *Cache[T]) EvictManyByKey(ctx context.Context, keys ...string) error
- func (c *Cache[T]) GetByAlias(ctx context.Context, alias AliasRef, loader Loader[T]) (*T, Outcome, error)
- func (c *Cache[T]) GetByKey(ctx context.Context, key string, loader Loader[T]) (*T, Outcome, error)
- func (c *Cache[T]) GetManyByKey(ctx context.Context, keys []string, ...) (map[string]*T, error)
- func (c *Cache[T]) Put(ctx context.Context, writer Writer[T]) (*T, Outcome, error)
- func (c *Cache[T]) PutAliased(ctx context.Context, primaryKey string, alias AliasRef, writer Writer[T]) (*T, Outcome, error)
- func (c *Cache[T]) PutAliasedValue(ctx context.Context, primaryKey string, alias AliasRef, val *T) error
- func (c *Cache[T]) PutByKey(ctx context.Context, key string, writer Writer[T]) (*T, Outcome, error)
- func (c *Cache[T]) PutValue(ctx context.Context, val *T) error
- func (c *Cache[T]) PutValueByKey(ctx context.Context, key string, val *T) error
- type CacheStore
- type Codec
- type EntityOptions
- type Loader
- type Manager
- type ManagerOption
- func WithDefaultCodec(c Codec) ManagerOption
- func WithDefaultDisableSingleflight(b bool) ManagerOption
- func WithDefaultJitterFraction(f float64) ManagerOption
- func WithDefaultNegativeTTL(d time.Duration) ManagerOption
- func WithDefaultTTL(d time.Duration) ManagerOption
- func WithOTLP(cfg OTLPConfig) ManagerOption
- type OTLPConfig
- type Options
- type Outcome
- type PrefixAliasOps
- type PrefixCacheStore
- type UniqueKeyed
- type Writer
Constants ¶
This section is empty.
Variables ¶
var ( // ErrStoreMiss is returned by CacheStore.Get when the key is absent. It is an // internal miss signal that Cache[T] handles; it is never surfaced to callers. ErrStoreMiss = errors.New("smartcache: store miss") // ErrNotFound is the sentinel a Loader returns (or wraps) to signal a cacheable // "does not exist". When negative caching is enabled Cache[T] remembers it // briefly and returns it from Get. ErrNotFound = errors.New("smartcache: not found") // ErrInvalidTTL is returned by Register when the resolved TTL <= 0 and // AllowInfinite is false. ErrInvalidTTL = errors.New("smartcache: TTL must be > 0 (set AllowInfinite for no expiry)") // ErrPointerType is the value Register panics with when T is itself a pointer // type. Cache[T] already returns *T from every method; T being a pointer // too makes that a double pointer (e.g. **User), which opens a hole where // a non-nil outer pointer can wrap a nil inner one — silently violating // the "a successful Get/Put never returns nil" guarantee the rest of this // package relies on. This is a programming error at the call site // (Register[*User] instead of Register[User]), not a runtime condition, so // Register panics with it instead of returning it. ErrPointerType = errors.New("smartcache: T must not itself be a pointer type") // ErrNilWrite is returned by Put when writer succeeds (nil error) but // returns a nil value. The cache is never set to nil, so this is treated // as a contract violation, not a cacheable state. ErrNilWrite = errors.New("smartcache: writer returned a nil value with no error") // ErrNilStore is returned by NewManager when the CacheStore is nil. ErrNilStore = errors.New("smartcache: store must not be nil") // ErrEmptyName is returned by Register when the cache name is empty. The name // is required: it is both the metric name and the default key prefix. ErrEmptyName = errors.New("smartcache: cache name must not be empty") // ErrDuplicateName is returned by Register when a cache name is already // registered on the manager. Each registered cache must have a unique name. ErrDuplicateName = errors.New("smartcache: cache name already registered") // ErrEmptyPrefix is returned by Register when EntityOptions.Prefix is explicitly // set to an empty string. Prefix namespaces every key this cache stores; an empty // prefix would collide with any other cache that also opts out of namespacing. ErrEmptyPrefix = errors.New("smartcache: prefix must not be empty") // ErrInvalidJitterFraction is returned by Register when the resolved jitter // fraction is outside [0, 1). Zero disables jitter. ErrInvalidJitterFraction = errors.New("smartcache: jitter fraction must be in [0, 1)") // ErrNotAliasGroup is returned when an alias-only method (GetByAlias, PutAliased, // PutAliasedValue, EvictByAlias) is called on a cache that was not created with // RegisterAliasGroup. ErrNotAliasGroup = errors.New("smartcache: cache is not an alias group") // ErrAliasingNotSupported is used in the RegisterAliasGroup panic when the manager's // store does not implement AliasCacheStore. ErrAliasingNotSupported = errors.New("smartcache: store does not implement AliasCacheStore") // ErrNotUniqueKeyed is returned by the value-derived methods (Put/PutValue/Evict that take a // value instead of a key) when the cached type T does not implement UniqueKeyed. The *ByKey // variants (GetByKey/PutByKey/PutValueByKey/EvictByKey/EvictManyByKey/GetManyByKey) work for any T. ErrNotUniqueKeyed = errors.New("smartcache: value type does not implement UniqueKeyed (CacheUniqueKey() string)") // ErrEmptyKeyPrefix is returned by EvictByKeyPrefix when keyPrefix is empty. An empty prefix // would match every entry of the cache, so it is rejected instead of silently flushing it. ErrEmptyKeyPrefix = errors.New("smartcache: key prefix must not be empty") // ErrPrefixEvictNotSupported is returned by EvictByKeyPrefix when the manager's store (or, for an // alias-group cache, its AliasOps) does not implement prefix eviction, or when the store's // connection cannot scan keys. ErrPrefixEvictNotSupported = errors.New("smartcache: store does not support prefix eviction") // ErrWildcardKeyPrefix is returned by EvictByKeyPrefix when keyPrefix contains the wildcard '*'. A key prefix is always literal; rejecting '*' means a caller can never ask for a pattern. ErrWildcardKeyPrefix = errors.New("smartcache: key prefix must not contain the wildcard '*'") )
Functions ¶
This section is empty.
Types ¶
type AliasCacheStore ¶
type AliasCacheStore interface {
CacheStore
AliasGroup(ns string, mode AliasMode) AliasOps
}
AliasCacheStore is the optional CacheStore extension for backends that support alias groups. It is a FACTORY: given an entity namespace and slot mode it returns an AliasOps bound to them. RegisterAliasGroup calls AliasGroup once per cache. redisstore returns a Colocated or Sharded implementation; memstore returns its single mode-agnostic implementation (one process has no slots, so both modes are behaviourally identical there). Detected via a comma-ok type assertion, mirroring BatchCacheStore.
type AliasMode ¶
type AliasMode int
AliasMode selects the Redis-Cluster slot-placement strategy for an alias-group cache.
const ( // AliasColocated tags every key of an entity with {ns}: all of the entity's keys share one // Cluster slot and every op is a single atomic Lua cascade. One slot per entity (hotspot risk // on Cluster with a hot entity). Default. AliasColocated AliasMode = iota // AliasSharded tags value+members with {ns:pk} (one slot per record) and reverse pointers with // {ns:field:value} (one slot per alias), distributing load. GetByAlias is a two-hop resolve with // validate-on-read; writes/evicts are a record-slot atomic Lua plus best-effort reverse-pointer // ops. No hotspot, no stale reads, weaker cross-key atomicity (self-healing). AliasSharded )
type AliasOps ¶
type AliasOps interface {
GetValue(ctx context.Context, primary string) ([]byte, error)
PutValue(ctx context.Context, primary string, val []byte, ttl time.Duration) error
EvictByPrimary(ctx context.Context, primary string) error
GetByAlias(ctx context.Context, ref AliasRef) ([]byte, error)
PutByAlias(ctx context.Context, primary string, ref AliasRef, val []byte, ttl time.Duration) error
EvictByAlias(ctx context.Context, ref AliasRef) error
}
AliasOps is the per-(namespace, mode) strategy handle for an alias-group cache. It OWNS all key-math and operation sequencing; Cache[T] delegates to it with logical identifiers (never a pre-built key) and keeps codec, the negative-marker convention, metrics, singleflight, and UniqueKeyed. All methods are byte-level: the value bytes may be the internal negative marker, which Cache[T] (not the store) interprets. A miss (absent, or a Sharded validate-on-read mismatch) is signalled by returning ErrStoreMiss.
type AliasRef ¶
AliasRef names one secondary lookup key for an alias-group cache: a field ("email") and a value ("foo@bar.com").
type BatchCacheStore ¶
type BatchCacheStore interface {
CacheStore
// GetMany returns the raw bytes for the keys that are present. Keys that are
// absent (or expired) are omitted from the returned map — a miss is never an
// error here.
GetMany(ctx context.Context, keys []string) (map[string][]byte, error)
}
BatchCacheStore is an optional extension of CacheStore for backends that can read many keys in one round trip. Cache[T].GetMany uses it when the injected store implements it (redisstore does, via MGET); stores that do not are handled transparently by GetMany's per-key fallback.
type Cache ¶
type Cache[T any] struct { // contains filtered or unexported fields }
Cache is a generic, type-safe read-through / delete-on-write cache over a CacheStore. It is constructed only via Register on a Manager, never directly.
func Register ¶
Register creates a Cache[T] on m under name (required, unique; it doubles as the metric name and the default key prefix). It panics with ErrPointerType if T is a pointer type. It returns ErrEmptyName, ErrEmptyPrefix, ErrDuplicateName, ErrInvalidTTL, or ErrInvalidJitterFraction on invalid input. A failed Register never consumes the name.
func RegisterAliasGroup ¶
RegisterAliasGroup registers an alias-group cache: one cached value reachable by several alias keys. It behaves like Register but additionally (a) requires the manager's store to implement AliasCacheStore, (b) requires T to implement UniqueKeyed, and (c) resolves the slot mode from opts.AliasMode (default AliasColocated) and binds an AliasOps strategy handle via the store's AliasGroup factory. Like Register's pointer-type check, it panics on a misconfiguration that must fail at init: a pointer T, a store without AliasCacheStore, or a T that is not UniqueKeyed.
func (*Cache[T]) Evict ¶
Evict is the value-derived delete: it derives the key from val.CacheUniqueKey().
func (*Cache[T]) EvictByAlias ¶
EvictByAlias deletes the whole group reachable through alias. Only valid on an alias-group cache.
func (*Cache[T]) EvictByKey ¶
EvictByKey deletes key (delete-on-write). It returns the delete error so the caller can retry or alarm; the TTL backstop bounds staleness if it fails.
func (*Cache[T]) EvictByKeyPrefix ¶
EvictByKeyPrefix deletes every cache entry whose logical key starts with keyPrefix, including cached not-found (negative) markers. For an alias-group cache, keyPrefix is matched against the primary key, and each matching record is evicted together with its members hash and alias pointers. It returns the number removed: keys for a plain cache, records for an alias group.
keyPrefix is literal. A keyPrefix that contains '*' returns ErrWildcardKeyPrefix, so a caller can never ask for a pattern; the other glob characters ('?', '[', ']' and '\') are matched literally. An empty keyPrefix returns ErrEmptyKeyPrefix, because it would match every entry. A store that cannot prefix-delete returns ErrPrefixEvictNotSupported. If the store fails partway through, the count removed so far is returned together with the joined error. Entries that a failed call left behind expire when their TTL backstop elapses, which bounds how stale they can get.
Prefix eviction scans the keyspace on Redis. It is a rare bulk operation (for example, erasing one tenant's data), never for the request hot path.
func (*Cache[T]) EvictManyByKey ¶
EvictManyByKey deletes several keys, joining any errors.
func (*Cache[T]) GetByAlias ¶
func (c *Cache[T]) GetByAlias(ctx context.Context, alias AliasRef, loader Loader[T]) (*T, Outcome, error)
GetByAlias reads a value by one of its alias keys (read-through: on a miss it runs loader, reads the value's CacheUniqueKey, and rebuilds the group). Only valid on an alias-group cache.
func (*Cache[T]) GetByKey ¶
GetByKey reads through to loader on a cache miss. See Outcome for the result modes.
Sharing note: when singleflight is enabled (the default), a Loaded or LoadedNotCached result may be the exact same *T handed to every concurrent caller deduped onto the same loader call — that is singleflight.Do's own contract. Treat a Loaded/LoadedNotCached result as read-only; copy it before mutating. A Hit result is always freshly unmarshaled per call and is never shared with another caller.
func (*Cache[T]) GetManyByKey ¶
func (c *Cache[T]) GetManyByKey( ctx context.Context, keys []string, loadMissing func(ctx context.Context, missing []string) (map[string]*T, error), ) (map[string]*T, error)
GetManyByKey reads several keys in one batch: cache hits (and warm negative hits) are served without touching loadMissing; keys not found in the cache are collected and loaded in ONE loadMissing call, then populated back into the cache (with per-key downward jitter, on both the positive TTL and NegativeTTL). Keys loadMissing does not return are negative-cached (when NegativeTTL > 0) and omitted from the result map. Unlike GetByKey, GetManyByKey is never deduplicated via singleflight.
func (*Cache[T]) Put ¶
Put is the value-derived write-through: it runs writer, derives the key from the returned value's CacheUniqueKey(), then caches it. If the writer committed but the key is underivable (T is not UniqueKeyed, or CacheUniqueKey() is empty), it returns the value with WrittenNotCached and ErrNotUniqueKeyed — the committed write is never reported as a failed write to the caller.
func (*Cache[T]) PutAliased ¶
func (c *Cache[T]) PutAliased(ctx context.Context, primaryKey string, alias AliasRef, writer Writer[T]) (*T, Outcome, error)
PutAliased write-throughs writer's value under primaryKey and registers alias (one-alias-per- field: re-registering a field replaces its old value). Only valid on an alias-group cache.
func (*Cache[T]) PutAliasedValue ¶
func (c *Cache[T]) PutAliasedValue(ctx context.Context, primaryKey string, alias AliasRef, val *T) error
PutAliasedValue caches a value you already hold under primaryKey and registers alias, with no writer. Only valid on an alias-group cache.
func (*Cache[T]) PutByKey ¶
PutByKey performs a write-through: it calls writer to persist the value to your source of truth, then caches exactly the value writer returned. See Outcome for the result modes.
If writer fails, its error is returned unchanged and the cache is untouched. If writer succeeds but returns a nil value, PutByKey returns ErrNilWrite and the cache is untouched. If the cache-side write fails after writer succeeded, PutByKey still returns the value with Outcome == WrittenNotCached — the real write already happened; only caching it failed, and that must never look like a failed write to the caller.
writer is never deduplicated the way GetByKey's loader is: two concurrent PutByKey calls for the same key are two distinct writes, and singleflight would silently drop one of them.
func (*Cache[T]) PutValue ¶
PutValue is the value-derived direct write: it derives the key from val.CacheUniqueKey().
func (*Cache[T]) PutValueByKey ¶
PutValueByKey writes a value you already hold directly into the cache — no external write happens. Use this when you performed the real write yourself and only need the cache updated to match it.
type CacheStore ¶
type CacheStore interface {
// Get returns the raw bytes for key, or ErrStoreMiss if the key is absent.
Get(ctx context.Context, key string) ([]byte, error)
// Set stores val under key with the given ttl. A ttl <= 0 means no expiry.
Set(ctx context.Context, key string, val []byte, ttl time.Duration) error
// Delete removes key. Deleting an absent key is not an error.
Delete(ctx context.Context, key string) error
// Exists reports whether key is present (and not expired).
Exists(ctx context.Context, key string) (bool, error)
}
CacheStore is the backend abstraction Cache[T] depends on: a byte key-value cache store — never the application's own database. Cache[T] owns all (de)serialization, so CacheStore never sees the cached type T. Swapping the backend (Redis, in-memory, anything) means providing a different CacheStore implementation; the Cache[T] API is unchanged.
type Codec ¶
Codec serializes cached values to and from bytes. The default (when Options.Codec is nil) is encoding/json.
type EntityOptions ¶
type EntityOptions struct {
Prefix *string
TTL *time.Duration
AllowInfinite *bool
JitterFraction *float64
NegativeTTL *time.Duration
DisableSingleflight *bool
Codec Codec
AliasMode *AliasMode // nil => AliasColocated; honored only by RegisterAliasGroup.
}
EntityOptions overrides manager defaults for one registered cache. Every field is an optional pointer: nil inherits the manager default, non-nil overrides it.
type Loader ¶
Loader loads a value from the source of truth on a cache miss.
Contract: return (val, nil) on success, (nil, ErrNotFound) for a cacheable not-found, or (nil, err) for a transient error (which is never cached).
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns the shared backend store, global defaults, and (optionally) the OTLP meter provider. Caches are created against it with Register.
func NewManager ¶
func NewManager(store CacheStore, opts ...ManagerOption) (*Manager, error)
NewManager builds a Manager over store. It returns ErrNilStore if store is nil. When WithOTLP was supplied (and no meter was injected for tests), it stands up the OTLP exporter + periodic reader + meter provider.
type ManagerOption ¶
ManagerOption configures a Manager in NewManager.
func WithDefaultCodec ¶
func WithDefaultCodec(c Codec) ManagerOption
WithDefaultCodec sets the default codec (caches fall back to JSON when nil).
func WithDefaultDisableSingleflight ¶
func WithDefaultDisableSingleflight(b bool) ManagerOption
WithDefaultDisableSingleflight sets the default singleflight toggle.
func WithDefaultJitterFraction ¶
func WithDefaultJitterFraction(f float64) ManagerOption
WithDefaultJitterFraction sets the default downward-jitter fraction (0 disables).
func WithDefaultNegativeTTL ¶
func WithDefaultNegativeTTL(d time.Duration) ManagerOption
WithDefaultNegativeTTL sets the default negative-cache TTL (0 disables negative caching).
func WithDefaultTTL ¶
func WithDefaultTTL(d time.Duration) ManagerOption
WithDefaultTTL sets the default positive TTL inherited by caches that do not override EntityOptions.TTL.
func WithOTLP ¶
func WithOTLP(cfg OTLPConfig) ManagerOption
WithOTLP enables OpenTelemetry OTLP metric export. It returns an error if the config's URL is nil.
type OTLPConfig ¶
type OTLPConfig struct {
// URL is the OTLP gRPC endpoint as host:port (e.g. "localhost:4317"). Required.
URL *string
// FlushInterval is how often the background PeriodicReader exports. Default 15s.
FlushInterval *time.Duration
// Timeout bounds a single export. Default 10s.
Timeout *time.Duration
// Insecure uses plaintext gRPC when true. Default false.
Insecure *bool
// ServiceName becomes the resource service.name attribute. Default "smartcache".
ServiceName *string
}
OTLPConfig configures the optional OpenTelemetry OTLP metric exporter on a Manager (via WithOTLP). Every field is an optional pointer; a nil field falls back to its documented default. URL is required — WithOTLP returns an error if it is nil.
type Options ¶
type Options struct {
// Prefix namespaces keys: the stored key is bc:<Prefix>:<key> for a normal
// cache and bc:{<Prefix>}:<key> for an alias-group cache (built in keyspace.go).
Prefix string
// TTL is the positive backstop expiry applied to cached values. Required unless
// AllowInfinite is true.
TTL time.Duration
// AllowInfinite opts in to TTL <= 0 (entries never expire).
AllowInfinite bool
// NegativeTTL, when > 0, enables negative caching of ErrNotFound for that
// duration. Zero disables negative caching.
NegativeTTL time.Duration
// Codec overrides serialization. Nil means JSON.
Codec Codec
// DisableSingleflight turns off de-duplication of concurrent cold loads.
DisableSingleflight bool
}
Options is the resolved configuration for a Cache[T], produced by Register.
type Outcome ¶
type Outcome int
Outcome describes how Cache[T].Get or Cache[T].Put served a request, so the caller can meter hit rate and alarm on populate failures without the library logging anything. Hit, Loaded, LoadedNotCached, and NegativeHit are Get-only (they name a load that happened on a read). Written and WrittenNotCached are Put-only (they name a write that happened, not a load) — Put never returns a Get-only value and Get never returns a Put-only value.
const ( // Hit means Get served the value from cache. Hit Outcome = iota // Loaded means Get missed, the loader ran, and the value was cached. Loaded // LoadedNotCached means Get missed, the loader ran, but writing the value // back to the store failed. The value is still returned; the read never fails. LoadedNotCached // NegativeHit means Get served a cached "not found" marker. NegativeHit // Written means Put's writer succeeded and the value was cached. Written // WrittenNotCached means Put's writer succeeded, but writing the value to // the store failed. The value is still returned; the write never fails on // account of the cache. WrittenNotCached )
type PrefixAliasOps ¶
type PrefixAliasOps interface {
AliasOps
// EvictByPrimaryPrefix removes every record whose primary key starts with primaryPrefix and
// cascades to its members hash and alias pointers exactly as EvictByPrimary does for one record.
// primaryPrefix is literal. It returns the number of records removed.
EvictByPrimaryPrefix(ctx context.Context, primaryPrefix string) (int64, error)
}
PrefixAliasOps is an optional extension of AliasOps for alias-group backends that can evict every record whose primary key starts with a prefix. Detected by a comma-ok assertion on the AliasOps returned by AliasCacheStore.AliasGroup.
type PrefixCacheStore ¶
type PrefixCacheStore interface {
CacheStore
// DeleteByPrefix removes every key that starts with keyPrefix and returns how many it removed.
DeleteByPrefix(ctx context.Context, keyPrefix string) (int64, error)
}
PrefixCacheStore is an optional extension of CacheStore for backends that can delete every key that starts with a given prefix. Cache.EvictByKeyPrefix uses it for NON-alias caches. keyPrefix is a complete, literal storage-key prefix built by Cache from internal/keyspace. It is never a glob pattern: a store must treat every character in it literally. DeleteByPrefix returns the number of entries it removed. It may use a full key scan, so callers use it for rare bulk operations only.
type UniqueKeyed ¶
type UniqueKeyed interface {
CacheUniqueKey() string
}
UniqueKeyed is implemented by a value type T cached with a unique key. The library uses CacheUniqueKey() to (a) learn a value's primary key on a GetByAlias read-through miss (alias groups), and (b) derive the storage/evict key in the value-derived methods Put/PutValue/Evict on any cache. The unique key is a SYMBOLIC identity for the cached structure — it need not equal the DB primary key and MAY be a composite (e.g. "provider:subscriptionID").
type Writer ¶
Writer persists a value to the source of truth and returns exactly what was written, so Put can cache that same value.
Contract: return (val, nil) with val != nil on success. Any non-nil error is returned to the caller unchanged and nothing is cached. Returning (nil, nil) violates the contract — Put returns ErrNilWrite, because the cache is never set to a nil value.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
keyspace
Package keyspace builds every smartcache Redis key string, so the key layout has a single source of truth shared by the smartcache core and its backends.
|
Package keyspace builds every smartcache Redis key string, so the key layout has a single source of truth shared by the smartcache core and its backends. |
|
Package memstore provides an in-memory smartcache.CacheStore for unit tests and light use.
|
Package memstore provides an in-memory smartcache.CacheStore for unit tests and light use. |
|
Package redisstore provides a go-redis-backed smartcache.CacheStore.
|
Package redisstore provides a go-redis-backed smartcache.CacheStore. |