gocache

package module
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Aug 12, 2026 License: MIT Imports: 5 Imported by: 0

README

go-cache

go-cache provides a generic cache contract, read-through helpers, shared serialization primitives, and multiple backend implementations.

Core contract

type Cache[K comparable, V any] interface {
    Get(ctx context.Context, key K) (V, bool, error)
    Set(ctx context.Context, key K, value V, ttl time.Duration) error
    Delete(ctx context.Context, key K) error
}

Optional capabilities:

  • Clearable
  • Purgeable
  • SetIfAbsentCache
  • SetIfPresentCache
  • TypedPrefixInvalidator[K]
  • BatchInvalidator
  • TypedTagRegistry[K]
  • PrefixInvalidator (deprecated compatibility)
  • TagRegistry (deprecated compatibility)

Read-through helper

Use GetOrFetch for basic read-through behavior, or GetOrFetchWithOptions for:

  • read/write error policy
  • optional singleflight dedupe
  • TTL parity with backend policy (ttl <= 0 is a non-expiring write, not a write skip)

Shared primitives

  • codec.Codec[V] and default codec.JSONCodec[V] for value serialization
  • deterministic key encoding via KeyEncoder[K]
  • versioned storage envelope format in internal/envelope
  • optional instrumentation via Observer and Observation

Backend selection matrix

Backend Use when Persistence Runtime dependency Notes
stores/memory Single-process, fastest local cache No None Optional hard capacity with deterministic LRU eviction
stores/valkey Shared/distributed cache across instances External Valkey Valkey server Supports namespacing and integration tests via VALKEY_ADDR
stores/pebble Embedded persistent cache without cgo Yes (disk) None (Go-native) Primary persistent backend
stores/rocksdb (-tags rocksdb) Teams requiring RocksDB specifically Yes (disk) Native RocksDB libs + cgo Optional backend behind build tag

Capability support matrix

Capability Memory Valkey Pebble RocksDB (-tags rocksdb)
Clearable Yes Yes Yes Yes
Purgeable Yes Yes Yes Yes
SetIfAbsentCache Yes Yes Yes Yes
SetIfPresentCache Yes Yes Yes Yes
BatchInvalidator Yes Yes Yes Yes
TypedPrefixInvalidator[K] Yes Yes Yes Yes
TypedTagRegistry[K] Yes Yes Yes No
PrefixInvalidator (deprecated) Yes Yes Yes Yes
TagRegistry (deprecated) Yes Yes Yes No

Behavior policy

The following behaviors are intentionally consistent across helpers and backends:

  • TTL: Set(..., ttl) with ttl <= 0 stores a non-expiring entry.
  • Helper TTL parity: GetOrFetch* does not skip writes for ttl <= 0; it writes non-expiring values.
  • Decode failures: Get returns an error and does not return stale/partial values.
  • Context cancellation: cancellable operations (Get, Set, Delete, invalidation methods) return context errors when canceled.
  • Conditional update: SetIfPresent replaces only a live entry, resets its TTL, preserves logical/tag metadata, and reports a missing or expired entry without creating it. Successful updates emit the existing set observer operation.
  • Logical-key semantics: typed invalidation/tag interfaces operate on logical keys, not encoded storage keys.

Key encoding policy

Storage keys are normalized to deterministic strings.

  • For string-like keys (K ~string), no custom encoder is required.
  • For non-string keys, a deterministic KeyEncoder[K] is required.

Example:

encoder := gocache.KeyEncoderFunc[int](func(key int) (string, error) {
    return fmt.Sprintf("user:%d", key), nil
})

store, err := memory.NewStore[int, MyValue](
    memory.WithKeyEncoder[int, MyValue](encoder),
)

Use the same key encoder strategy across services/backends to avoid cross-backend key drift.

Instrumentation observer hooks

Attach an observer with backend WithObserver(...) options.

Event operation names:

  • get_hit
  • get_miss
  • set
  • delete
  • error
  • evict (bounded memory backend; aggregate count with no key)

Observation fields:

  • Backend: backend identifier (memory, valkey, pebble, rocksdb)
  • Operation: one of the operation names above
  • Key: storage/logical key used by the backend path
  • Err: optional error for failed operations
  • Latency: operation duration
  • Count: entries affected by an aggregate operation
  • Occupancy / Capacity: bounded memory usage without exposing keys or values

Bound the memory backend with a positive maximum when cache cardinality is not intrinsically finite:

store, err := memory.NewStore[string, MyValue](
    memory.WithMaxEntries[string, MyValue](10_000),
)

New-key admission removes expired entries first. If the store is still full, it evicts the least recently used live entry; successful gets and writes update recency. Eviction observations intentionally leave Key empty.

CI verification matrix

GitHub Actions workflow: .github/workflows/ci.yml

  • unit/core tests
  • backend conformance matrix (memory, valkey, pebble)
  • valkey service-backed integration conformance
  • pebble restart durability tests
  • optional rocksdb tagged job (-tags rocksdb)
  • race checks for memory and shared core paths

Local quality checks

The taskfile pins and installs the Go quality tools when they are missing:

./taskfile go:tools:all

Use ./taskfile go:quality:pr for format, test, and change-scoped lint checks. The full local gate also applies Go formatting/fixes, runs tests and race tests, and checks new lint, vulnerability, and gosec findings:

./taskfile go:quality:all

Use ./taskfile go:lint:report to inspect the full lint backlog and ./taskfile go:quality:baseline for a non-mutating full check against the committed lint and gosec baselines. Refresh baselines only after intentionally accepting or cleaning up repository-wide findings with ./taskfile go:baseline:update:all.

Operational caveats

  • Prefix invalidation cost:
    • Valkey uses scan/delete and can be expensive on large namespaces.
    • LSM backends (Pebble/RocksDB) rely on prefix iteration and batched deletes; large keyspaces increase I/O.
  • Tag index overhead:
    • Tagging maintains additional index records (extra writes, extra storage/memory).
  • Envelope versioning strategy:
    • Stored values use a versioned envelope (version, expires_at_unix_nano, payload).
    • Current version is 1; unknown versions fail decode with an explicit error.

RocksDB notes

  • Requires native RocksDB headers and libraries (rocksdb/c.h, linked RocksDB runtime).
  • Compile/run with build tag:
go test -tags rocksdb ./stores/rocksdb

If headers/libs are missing, tagged builds fail at cgo compile time.

Scope

This package is intended for reusable cache abstractions and helpers. It is not a replacement for domain-specific operational stores (idempotency leases, queue state, chunk-upload sessions).

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNilFetch is returned when a nil fetch callback is supplied.
	ErrNilFetch = errors.New("gocache: nil fetch function")
	// ErrInvalidSingleflightValue is returned when a singleflight result cannot be type asserted to V.
	ErrInvalidSingleflightValue = errors.New("gocache: invalid singleflight value type")
)
View Source
var (
	// ErrNilKeyEncoderFunc indicates a nil KeyEncoderFunc was used.
	ErrNilKeyEncoderFunc = errors.New("gocache: nil key encoder func")
	// ErrKeyEncoderRequired indicates non-string key types must provide a key encoder.
	ErrKeyEncoderRequired = errors.New("gocache: key encoder required for non-string key type")
	// ErrInvalidStringKeyType indicates a default string encoder received a non-string value.
	ErrInvalidStringKeyType = errors.New("gocache: key is not string-compatible")
)
View Source
var (
	// ErrLogicalPrefixUnsupported indicates logical-prefix invalidation was requested on a non-string key type.
	ErrLogicalPrefixUnsupported = errors.New("gocache: logical prefix invalidation unsupported for non-string key type")
)

Functions

func EncodeStorageKey

func EncodeStorageKey[K comparable](key K, encoder KeyEncoder[K]) (string, error)

EncodeStorageKey encodes a key using the supplied encoder or default string path.

func GetOrFetch

func GetOrFetch[K comparable, V any](
	ctx context.Context,
	cache Cache[K, V],
	key K,
	ttl time.Duration,
	fetch func(context.Context) (V, error),
) (V, error)

GetOrFetch returns a cached value or fetches and stores it with the given TTL. TTL <= 0 is treated as non-expiring by compliant cache backends.

func GetOrFetchWithOptions

func GetOrFetchWithOptions[K comparable, V any](
	ctx context.Context,
	cache Cache[K, V],
	key K,
	opts GetOrFetchOptions,
	fetch func(context.Context) (V, error),
) (V, error)

GetOrFetchWithOptions is the configurable read-through helper.

func Observe

func Observe(ctx context.Context, observer Observer, event Observation)

Observe safely emits one observation if observer is not nil.

Types

type BatchInvalidator

type BatchInvalidator[K comparable] interface {
	InvalidateKeys(ctx context.Context, keys []K) error
}

BatchInvalidator is an optional capability for invalidating multiple keys at once.

type Cache

type Cache[K comparable, V any] interface {
	Get(ctx context.Context, key K) (V, bool, error)
	Set(ctx context.Context, key K, value V, ttl time.Duration) error
	Delete(ctx context.Context, key K) error
}

Cache is the minimal cross-package key/value cache contract. Implementations may be in-memory or distributed.

type Clearable

type Clearable interface {
	Clear(ctx context.Context) error
}

Clearable is an optional capability for stores that can delete all entries.

type GetOrFetchOptions

type GetOrFetchOptions struct {
	TTL            time.Duration
	ReadErrorMode  ReadErrorMode
	WriteErrorMode WriteErrorMode
	Group          SingleflightGroup
	GroupKey       string
}

GetOrFetchOptions configures read-through behavior.

type KeyEncoder

type KeyEncoder[K comparable] interface {
	EncodeKey(K) (string, error)
}

KeyEncoder encodes cache keys into deterministic storage-safe strings.

func NewStringKeyEncoder

func NewStringKeyEncoder[K ~string]() KeyEncoder[K]

NewStringKeyEncoder returns a key encoder for string-typed keys.

func ResolveKeyEncoder

func ResolveKeyEncoder[K comparable](encoder KeyEncoder[K]) (KeyEncoder[K], error)

ResolveKeyEncoder resolves a key encoder for type K. If encoder is nil and K is string-compatible, a default string encoder is used.

type KeyEncoderFunc

type KeyEncoderFunc[K comparable] func(K) (string, error)

KeyEncoderFunc adapts a function to KeyEncoder.

func (KeyEncoderFunc[K]) EncodeKey

func (f KeyEncoderFunc[K]) EncodeKey(key K) (string, error)

type Observation

type Observation struct {
	Backend   string
	Operation Operation
	Key       string
	Err       error
	Latency   time.Duration
	// Count is the number of entries affected by an aggregate operation.
	Count int
	// Occupancy and Capacity report bounded backend usage without exposing keys
	// or values. Unbounded or unsupported backends leave them at zero.
	Occupancy int
	Capacity  int
}

Observation represents a backend instrumentation event.

type Observer

type Observer interface {
	Observe(ctx context.Context, event Observation)
}

Observer consumes cache instrumentation events.

func EnsureObserver

func EnsureObserver(observer Observer) Observer

EnsureObserver returns observer when non-nil, otherwise a no-op observer.

func NopObserver

func NopObserver() Observer

NopObserver returns a no-op observer implementation.

type ObserverFunc

type ObserverFunc func(context.Context, Observation)

ObserverFunc adapts a function to Observer.

func (ObserverFunc) Observe

func (f ObserverFunc) Observe(ctx context.Context, event Observation)

type Operation

type Operation string

Operation identifies a cache operation for instrumentation events.

const (
	OperationGetHit  Operation = "get_hit"
	OperationGetMiss Operation = "get_miss"
	OperationSet     Operation = "set"
	OperationDelete  Operation = "delete"
	OperationEvict   Operation = "evict"
	OperationError   Operation = "error"
)

type PrefixInvalidator deprecated

type PrefixInvalidator interface {
	DeleteByPrefix(ctx context.Context, prefix string) error
}

PrefixInvalidator is an optional key-prefix invalidation capability.

Deprecated: prefer TypedPrefixInvalidator[K] so prefix invalidation is keyed by the logical key type. This legacy interface historically encouraged encoded-key usage and is retained for compatibility only.

type Purgeable

type Purgeable interface {
	PurgeExpired(ctx context.Context) (int, error)
}

Purgeable is an optional capability for stores that can actively evict expired entries.

type ReadErrorMode

type ReadErrorMode uint8

ReadErrorMode controls behavior when cache read operations fail. TODO: prefer string enums so they are readable and easier to understand in traces/logs

const (
	// ReadErrorFail returns cache read errors to callers.
	ReadErrorFail ReadErrorMode = iota
	// ReadErrorBypass ignores cache read errors and proceeds with fetch.
	ReadErrorBypass
)

type SetIfAbsentCache

type SetIfAbsentCache[K comparable, V any] interface {
	SetIfAbsent(ctx context.Context, key K, value V, ttl time.Duration) (bool, error)
}

SetIfAbsentCache is an optional atomic-set capability.

type SetIfPresentCache

type SetIfPresentCache[K comparable, V any] interface {
	SetIfPresent(ctx context.Context, key K, value V, ttl time.Duration) (bool, error)
}

SetIfPresentCache is an optional atomic-update capability.

SetIfPresent replaces a live entry and resets its TTL without creating a missing or expired entry. It returns true only when the replacement was applied. A non-positive TTL makes the replacement non-expiring, matching Cache.Set semantics. Implementations must leave existing logical-key and tag metadata intact and must honor context cancellation and encoding failures without partially replacing the entry.

type SingleflightGroup

type SingleflightGroup interface {
	Do(key string, fn func() (any, error)) (any, error, bool)
}

SingleflightGroup is compatible with golang.org/x/sync/singleflight.Group. The interface keeps go-cache decoupled from that dependency.

type TagRegistry deprecated

type TagRegistry interface {
	AddTags(ctx context.Context, key string, tags []string) error
	InvalidateTags(ctx context.Context, tags []string) error
}

TagRegistry is an optional capability for tag-based invalidation.

Deprecated: prefer TypedTagRegistry[K] so tag registration can be done with typed logical keys. This legacy interface uses string keys and is retained for compatibility only.

type TypedPrefixInvalidator

type TypedPrefixInvalidator[K comparable] interface {
	DeleteByKeyPrefix(ctx context.Context, prefix K) error
}

TypedPrefixInvalidator is an optional logical key-prefix invalidation capability. For string-like key types, implementations should treat prefix as logical-key prefix (not storage-key prefix).

type TypedTagRegistry

type TypedTagRegistry[K comparable] interface {
	AddTagsForKey(ctx context.Context, key K, tags []string) error
	InvalidateTags(ctx context.Context, tags []string) error
}

TypedTagRegistry is an optional capability for tag-based invalidation keyed by logical typed keys.

type WriteErrorMode

type WriteErrorMode uint8

WriteErrorMode controls behavior when cache write operations fail. TODO: prefer string enums so they are readable and easier to understand in traces/logs

const (
	// WriteErrorFail returns cache write errors to callers.
	WriteErrorFail WriteErrorMode = iota
	// WriteErrorIgnore ignores cache write errors after successful fetch.
	WriteErrorIgnore
)

Directories

Path Synopsis
internal
storelock
Package storelock coordinates mutations performed by multiple store wrappers over the same embedded database handle.
Package storelock coordinates mutations performed by multiple store wrappers over the same embedded database handle.
stores

Jump to

Keyboard shortcuts

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