Help us shape the future of Go! Take the 2026 Go Developer Survey

cache

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 14 Imported by: 0

README

Cache

pkg/cache provides raw byte operations and JSON convenience methods through one cache.Store contract. Memory, database, and Redis drivers implement it. Business modules receive cache.Store directly; they do not open connections or look up the app instance. internal/app owns configured connections and cleanup. Connection setup stays in pkg/database and pkg/redis.

Use a cache

if err := store.Set(ctx, "product:42", encodedProduct, 5*time.Minute); err != nil {
    return err
}
value, found, err := store.Get(ctx, "product:42")
if err != nil {
    return err // The backend failed; this is not a cache miss.
}
if found {
    // Decode value. It belongs to the caller and may be changed safely.
}
err = store.Delete(ctx, "product:42") // Missing keys are fine.

Get and Set use raw bytes and keep their existing encoding contract. Nil and empty values are valid cached entries; found distinguishes them from a miss. Keys must contain 1–255 UTF-8 bytes without NUL, consistently across every driver. Keys are case-sensitive and trailing spaces are significant. Include tenant and schema/version identifiers in business keys when needed.

Set replaces both value and expiry atomically. TTL must be positive: zero does not mean forever. Remote drivers round TTL up to milliseconds; memory uses Go's clock precision. Expired values are misses. Get does not extend TTL. All stores support concurrent use and copy any data they retain. Do not mutate an input slice while its call is running.

The common errors are ErrInvalidKey, ErrInvalidTTL, ErrTooLarge, memory's ErrFull, memory's ErrClosed, and ErrInvalidValue for invalid JSON. Use errors.Is. Other backend errors retain their causes; a timeout on a write can leave an uncertain result. Set/Delete do not implement compare-and-swap, counters, locks, or distributed uniqueness.

Convenience operations

Drivers provide storage operations through deps.Cache. Typed loading is a package function, with the result type inferred from the loader. Product below is your application's JSON-compatible type.

exists, err := store.Exists(ctx, "product:42")

err = cache.SetMulti(ctx, store, map[string]any{
    "product:42": map[string]any{"name": "Notebook", "price": 1200},
    "feature:enabled": true,
}, 5*time.Minute)
values, err := cache.GetMulti[any](ctx, store, []string{"product:42", "feature:enabled"})

value, err := cache.Remember(ctx, store, "product:42", 5*time.Minute,
    func(ctx context.Context) (Product, error) {
        return loadProduct(ctx)
    })


// Administrative invalidation: clears this store/namespace, keeps it usable.
err = store.Clear(ctx)

Handle each error before using its result. Exists checks presence without copying or decoding the value; expired entries are absent. It is only a snapshot, not a lock or an atomic check-and-set operation.

Encoding: Store.GetMulti/SetMulti read and write raw bytes. The package functions cache.GetMulti[T]/SetMulti[T] handle JSON separately. For T=any, objects are map[string]any, arrays are []any, and numbers are json.Number. Typed cache.Remember decodes both the loaded and cached value into the loader's return type, so structs and integer precision are preserved. Remember[any] retains the dynamic behavior. JSON null follows the destination type's normal decoding rules. Use pointer types when absence must remain distinct from a zero value. Values must round-trip through JSON; use raw Get/Set for other encodings.

Raw Get can read the encoded bytes written by these helpers. Raw Set values used with a JSON helper must contain one valid JSON value; malformed data returns ErrInvalidValue rather than triggering a reload. Existing raw data needs no migration unless you start accessing those same keys with a JSON helper.

Bulk calls: up to 1,000 input keys/items and 8 MiB of encoded key/value data per call, also subject to each driver's per-entry limit. GetMulti omits misses, retains empty-byte hits (or JSON null through the helper), collapses duplicate keys, and returns an empty non-nil map for no hits. On error it returns no result. Results are not a cross-key snapshot. The byte limit is a logical payload limit, not an allocation or RSS guarantee; JSON decoding and remote-client buffers need additional memory.

The JSON SetMulti helper encodes the complete batch before calling the driver. The raw driver validates all keys, sizes and TTL before any writes. Invalid keys, TTLs, unsupported values and size violations therefore write nothing. After validation, runtime errors can leave partial writes: memory admits sorted keys in order, Redis pipelines atomic per-key SET-with-TTL commands, and SQL uses one upsert statement through the transaction executor. There is no portable batch transaction or rollback promise. Empty batches are no-ops after validation.

Loading: Remember calls the closure only on a miss, passes its context, and starts the TTL when the result is stored. Cache errors never trigger the loader. Loader errors, invalid JSON values, cancellation and cache-write errors propagate; failed loads are not cached. Closures run synchronously and must honor cancellation. Concurrent misses may invoke the closure more than once: this is not singleflight or a distributed lock. A fill racing Delete/Clear can repopulate a key; use a module-specific consistency policy or versioned keys where that matters.

Hits retain the existing TTL. Every write requires an explicit positive TTL; no-expiry entries are unsupported. cache.MaxTTL is a finite upper bound, not permanent storage. The misleading Forever convenience method was removed. Clear, Delete, memory restarts and remote eviction can still remove entries.

Clear: memory clears one instance atomically; SQL deletes one namespace using the caller's transaction; Redis scans the escaped namespace prefix and unlinks keys in batches across all cluster primaries. Redis never uses FLUSHDB/FLUSHALL. Remote operations have the configured timeout; errors can leave an uncertain or partial result. Stop writers for a complete administrative clear. Concurrent writes, loads or Redis topology changes can survive the traversal. A retry is safe for cache data but may also remove newly written entries.

Adding these methods expands cache.Store; custom implementations and test fakes must implement them. Driver constructors and raw Get/Set/Delete signatures remain unchanged. Telemetry covers Exists/Clear/bulk operations and the Get/Set calls inside loading helpers without recording keys or payloads.

Application lifecycle

The app already registers each resource immediately after acquisition. Its shutdown drains app work, then closes resources once in reverse order and returns cleanup errors. Memory's original store is registered even when telemetry wraps it. The app-owned Redis client is closed once; the SQL cache borrows the app's Bun pool, which the app closes once after all its users stop. Initialization failures use the same cleanup path. Clear never closes any resource.

Modules receive a borrowed cache.Store and must not close its backing client. Standalone callers own the memory store or the remote client they opened, as in the example. No loading or cleanup goroutine is started by the cache, so there is no extra lifecycle component to register.

Choose a driver

Driver Constructor Ownership and expiry
memory memory.New(capacity, maxEntryBytes) One process; caller closes the store; lazy expiry
database cachedb.New(db, cachedb.Config{}) Caller owns *bun.DB; expiry checked by server; explicit cleanup
redis cacheredis.New(client, cacheredis.Config{}) Caller owns redis.UniversalClient; native Redis TTL

Use aliases cachedb and cacheredis to distinguish the cache drivers from the connection packages. Constructors start no goroutines, create no SQL schema, and perform no connectivity check. Open a ready client through the connection package before constructing a remote store. Remote stores have no Close method because they own no connections or background tasks. Stop their users before closing clients.

The memory driver limits entry count, key-plus-value bytes per entry, and total retained key-plus-value bytes. New(capacity, maxEntryBytes) keeps usage simple and applies a 64 MiB total byte budget. For a different budget:

store, err := memory.NewWithConfig(memory.Config{
    Capacity: 10000,
    MaxEntryBytes: 1 << 20,
    MaxBytes: 128 << 20,
})

This introduces a tighter default than the previous entry-count-only limit. Applications retaining more than 64 MiB must explicitly raise CACHE_MEMORY_MAX_BYTES (or Config.MaxBytes). A single entry exceeding either byte limit returns ErrTooLarge; exhausting total bytes or entry count returns ErrFull. A failed replacement preserves the previous value and TTL. Updates at entry capacity remain allowed if the byte budget permits them. Live entries are never evicted. Treat admission failures as a caching decision, not evidence that authoritative data was saved or lost.

The byte budget counts logical key/value lengths, not total heap or RSS. Map metadata, allocator overhead, in-flight reads, and caller buffers require memory headroom. Keys are copied as well as values, preventing short substring keys from retaining larger caller allocations. Reads share a read lock and copy immutable values outside it. Ordinary Get still allocates caller-owned bytes on nonempty hits; an optional concrete-memory API supports buffer reuse:

buf, found, err := store.GetInto(ctx, key, buf[:0])

GetInto appends to the supplied buffer and never retains it. Misses and errors leave it unchanged; check found before using the contents. Each concurrent caller must own its buffer. This method is not part of cache.Store and direct calls do not pass through the optional telemetry wrapper.

Expiry is checked on reads. Admission reclaims expired entries when needed, but skips the scan if the earliest possible expiry is still in the future. No background goroutine starts. For idle caches, an explicitly owned maintenance job can call store.PruneExpired(ctx, 1000). It removes at most that many expired entries, checks cancellation during scanning, and may return partial progress with an error. A scan can inspect the whole map under the write lock: use modest capacities and measure latency if expirations or admission failures are frequent.

store.Usage() reports retained entries, logical bytes, configured limits, and closed state without exposing keys. Expired entries awaiting cleanup count toward usage. Existing telemetry reports operation outcomes and durations. Each instance is isolated. Close(ctx) is idempotent and releases its map and values; stop users first if in-flight reads must finish before shutdown.

Remote Config fields:

Field Default Meaning
Namespace default Store isolation; same key rules, with colon additionally forbidden
MaxEntryBytes 1048576 (1 MiB) Maximum key + value bytes accepted by Set
OperationTimeout 5s Upper bound for each operation; caller deadlines can be earlier
Table cache_entries Database only; lowercase bare identifier up to 40 characters

Namespace does not provide access control. Choose distinct namespaces per app and environment. Remote caches have no per-store entry-count limit; configure backend capacity and monitor usage. The byte limit bounds writes through this store, not records written directly by other tools.

Database: PostgreSQL and MySQL

db, err := database.Open(ctx, cfg.Database.ConnectionConfig()) // github.com/wisalgroup/labena/pkg/database
if err != nil { return err }
defer db.Close()
store, err := cachedb.New(db, cachedb.Config{Namespace: "catalog"})

The driver uses Bun's PostgreSQL or MySQL dialect automatically. MySQL requires 8.4+ with InnoDB and the UTC connection settings supplied by pkg/database. SQLite is not supported by this cache driver. SQL uses the context logging hooks already installed by pkg/database; query values are not included in those logs.

Publish with go run ./cmd/cli cache tables, then apply the central Goose migrations before using the driver. SQL is selected by DATABASE_DRIVER and published directly into internal/modules/shared/migrations/. The cache_entries table has four columns: namespace, cache_key, value, and expires_at. Its primary key is (namespace, cache_key); its expiry index supports bounded namespace cleanup. PostgreSQL uses BYTEA and TIMESTAMPTZ; MySQL uses binary keys, LONGBLOB and UTC DATETIME(6). Values can contain arbitrary bytes. This table belongs to cache. Asynq jobs use Redis and have no SQL queue schema.

All SQL reads, writes and Clear use database.Executor(ctx, db). They automatically participate in a transaction for the exact same Bun instance. Rollback undoes cache writes and deletes. TTL starts when Set executes, not at commit; a long transaction can commit an already-expired entry. This SQL-only feature is not a Redis/memory guarantee.

Expired rows stay in storage until cleanup; they cannot be read as hits. Run this explicitly from a maintenance command or scheduled job, outside business transactions:

removed, err := store.PruneExpired(ctx, 1000)

Each call removes at most the requested limit (1–10000), in the store's namespace. It uses a bounded read-committed transaction and FOR UPDATE SKIP LOCKED, so it can run alongside other cleaners and writers without deleting a refreshed value. Locked rows can remain for the next pass; zero deletions do not prove the entire table is empty. No cleanup worker starts automatically. cache tables respects CACHE_DATABASE_TABLE and preserves existing published files. Changing the setting after deployment does not rename or migrate an existing table.

Redis

client, err := redis.OpenURL(ctx, cfg.Cache.RedisURL, 5*time.Second) // github.com/wisalgroup/labena/pkg/redis
if err != nil { return err }
defer client.Close()
store, err := cacheredis.New(client, cacheredis.Config{Namespace: "catalog"})

Redis keys are cache:<namespace>:<key>. Set uses one SET ... PX command to store value and TTL together; no separate expiry write can fail and leave an immortal key. Only redis.Nil becomes a miss. Authentication, timeouts, wrong-type errors, and connection failures stay errors. Ordinary reads and writes do not scan or use Lua scripts. Explicit Clear uses SCAN and UNLINK; there are no global flushes or background goroutines.

pkg/redis.Open accepts native standalone, Sentinel, or Cluster options; OpenURL is the standalone URL helper used by app configuration. Its clients enable context-aware I/O. A directly supplied client must enable equivalent deadline handling. Bulk operations pipeline single-key commands and work without cross-slot transactions. Clear traverses each cluster primary; verify ACLs allow SCAN and UNLINK. Configure Redis maxmemory and eviction policy for cache workloads; avoid evicting queue/event data when sharing a Redis deployment. Redis may evict an entry before its TTL, depending on that policy.

Application configuration

The central env-tag schema owns every setting; no driver reads environment variables.

CACHE_DRIVER=disabled
CACHE_NAMESPACE=default
CACHE_MAX_ENTRY_BYTES=1048576
CACHE_MEMORY_CAPACITY=10000
CACHE_MEMORY_MAX_BYTES=67108864
CACHE_REDIS_URL=redis://127.0.0.1:6379/0
CACHE_DATABASE_TABLE=cache_entries
CACHE_OPERATION_TIMEOUT=5s

Select memory, database, or redis to enable it. Disabled leaves a.cache nil and opens no cache connection. Database cache borrows the application's database; choose PostgreSQL/MySQL with DATABASE_DRIVER and DATABASE_URL. Redis cache uses its own caller-owned client. All entry points, including console, get the same configured dependency. Pass resources.Cache to module assembly and specific cache collaborators to services only where needed; modules requiring a cache should reject nil explicitly. No example business module is forced to cache data it does not need.

For portable business workflows, commit authoritative data before invalidating Redis/memory entries. Transactional database cache updates can share the commit. This package does not solve stale fill/invalidation races or cache stampedes. Pick a consistency policy in the module; do not use a cache as the only source of truth. Log failures once at the responsible application boundary with pkg/logging and the request context; avoid logging cached payloads or sensitive keys.

Example and verification

One-file example:

CACHE_DRIVER=memory go run ./examples/cache
CACHE_DRIVER=redis go run ./examples/cache
# Set DATABASE_URL and DATABASE_DRIVER first for the database example.
CACHE_DRIVER=database go run ./cmd/cli cache tables
CACHE_DRIVER=database go run ./cmd/cli migrate up
CACHE_DRIVER=database go run ./examples/cache

Use disposable PostgreSQL, MySQL and Redis services, then set the test URLs:

export DATABASE_TEST_POSTGRES='postgres://queues:queues@127.0.0.1:15432/queues?sslmode=disable'
export DATABASE_TEST_MYSQL='root:queues@tcp(127.0.0.1:13306)/queues'
# Optional comma-separated Redis Cluster addresses:
# export CACHE_TEST_REDIS_CLUSTER=127.0.0.1:26380,127.0.0.1:26381,127.0.0.1:26382
export CACHE_TEST_REDIS='redis://127.0.0.1:16379/0'
go test -race ./pkg/cache/... ./internal/app ./pkg/database/migrate ./internal/platform/testdb

Backend tests skip when the corresponding variable is absent. SQL fixtures create and remove isolated schemas/databases; use disposable services and test accounts. The shared contract covers binary/empty values, expiry, refresh, key identity, copy ownership, invalid input, cancellation, and atomic concurrent writes. Driver checks cover native TTL, error-vs-miss behavior, namespaces, transaction rollback, locked-row cleanup and operation deadlines.

Native references: Redis SCAN semantics, Redis pipelining, Redis SET and Bun drivers.

Optional telemetry

WithTelemetry(store, tracerProvider, meterProvider) borrows the store and providers. App applies it when tracing or metrics are enabled; no-op providers disable either signal independently. It records fixed operations and hit/miss/error outcomes without keys or values. Close the original store/client after users stop. See observability.

Memory performance verification

Run the fixed 1 KiB payload benchmarks on the deployment toolchain and hardware:

go test ./pkg/cache/memory -run '^$' -bench BenchmarkStore -benchmem -count=5

Recorded measurements and tradeoffs include the baseline. The benchmarks cover reads, parallel reads, buffer reuse, replacement, and rejected writes at 100 and 10,000 entries. These are closed-loop microbenchmarks, not service p99 or a comparison against community caches. For deployment sizing, measure realistic payloads, concurrency, expiration rate, hit ratio, GC CPU, live/scannable heap and request latency. This driver remains a Go map, not a pointer-free arena cache. No global GC tuning or pooled payload retention is introduced.

Backend contract and database integration tests live in internal/platform/testdb because they use application migration fixtures. The cache packages and their package-local tests have no dependency on internal.

Store and typed loading

Use store.Remember(ctx, key, ttl, load) for dynamic JSON values, or cache.Remember(ctx, store, key, ttl, load) with a typed loader for a typed result. Go methods cannot introduce type parameters, so the store method accepts func(context.Context) (any, error) and returns (any, error). Both forms share one implementation and the same TTL, validation and error behavior.

value, err := store.Remember(ctx, "settings:v1", 5*time.Minute,
    func(ctx context.Context) (any, error) {
        return map[string]any{"theme": "dark"}, nil
    })

Objects return as map[string]any, arrays as []any, and numbers as json.Number on both hits and misses. Use the typed helper to retain a struct type. Remove calls to store.Forever and choose an explicit retention duration.

The JSON encoding and existing cached bytes are unchanged. Version keys when changing a value's type or representation. Cache corruption and type mismatches return errors without invoking the loader. Loading through a telemetry-wrapped store still records its Get/Set operations; driver methods delegate to the shared helper.

Raw and typed batches

The store contract uses map[string][]byte for both batch reads and writes:

err := store.SetMulti(ctx, map[string][]byte{
    "binary:v1": {0, 255, 128},
    "empty:v1": nil,
}, time.Minute)
raw, err := store.GetMulti(ctx, []string{"binary:v1", "empty:v1"})

An empty or nil byte value is a present empty value, never deletion. Missing keys are omitted. Returned bytes belong to the caller; keep input maps and slices unchanged until writes return.

For JSON, use package functions rather than store methods:

err := cache.SetMulti(ctx, store, map[string]Product{"product:v1:42": product}, time.Minute)
products, err := cache.GetMulti[Product](ctx, store, []string{"product:v1:42"})

Here Product and product are your application type/value. Both helpers use the driver's native batch operation. Errors return no partial read result, and JSON decoding errors do not mutate stored data. Changing to these helpers preserves the previous JSON encoding; no existing data rewrite is needed. Raw methods do not JSON-encode byte slices (unlike passing bytes to a JSON helper).

This is a source-breaking method-signature change: callers wanting the former dynamic JSON behavior use cache.GetMulti[any] and cache.SetMulti with their existing map[string]any. Driver interfaces contain no JSON loading or encoding methods. Telemetry describes raw storage operations; serialization failures are returned to the caller, outside storage-operation spans.

Redis read budgets

Redis Get checks the entry size atomically with the read using a single-key Lua script. GetMulti reads bounded pipelines and stops when the aggregate result would exceed 8 MiB. A pipeline returns at most another 8 MiB of value data while previously accepted results remain retained; protocol overhead, byte copies and Go allocations are additional. Oversized legacy/external values return ErrTooLarge without fetching their bodies. Redis credentials must permit EVAL, GET and STRLEN as well as the other cache commands. Scripts use one key each and retain cluster-slot compatibility.

Documentation

Overview

Package cache defines an optional cache with raw bytes and JSON convenience operations. Misses are not errors.

Index

Constants

View Source
const (
	// MaxTTL is the largest positive time.Duration (approximately 292 years).
	// It is finite. Stores require a positive TTL; no-expiry entries are unsupported.
	MaxTTL time.Duration = 1<<63 - 1
	// MaxBatchKeys and MaxBatchBytes bound convenience-operation inputs/results.
	MaxBatchKeys  = 1000
	MaxBatchBytes = 8 << 20
)

Variables

View Source
var (
	ErrInvalidValue = errors.New("cache value is not valid JSON")
	ErrClosed       = errors.New("cache is closed")
	ErrFull         = errors.New("cache capacity exhausted")
	ErrTooLarge     = errors.New("cache entry exceeds size limit")
	ErrInvalidTTL   = errors.New("cache TTL must be positive")
	ErrInvalidKey   = errors.New("cache key must contain 1 to 255 UTF-8 bytes without NUL")
)

Stable errors allow callers to handle admission failures with errors.Is.

Functions

func DecodeValue

func DecodeValue(value []byte) (any, error)

DecodeValue decodes one complete JSON value. Objects are map[string]any, arrays are []any, and numbers are json.Number to preserve integer precision. Null is nil and is distinct from a cache miss. The result is caller-owned. Invalid raw bytes are errors; they are not silently treated as misses.

func GetMulti

func GetMulti[T any](ctx context.Context, store Store, keys []string) (map[string]T, error)

GetMulti decodes JSON hits into T using one native batch read. Missing keys are omitted; malformed or incompatible values fail the entire result, not the cache. For T=any, numeric values use json.Number. Results belong to the caller.

func Remember

func Remember[T any](ctx context.Context, store Store, key string, ttl time.Duration, load func(context.Context) (T, error)) (T, error)

Remember returns a typed JSON value on a hit, or loads, encodes and caches it on a miss. It never loads on a cache error. Loader/encoding/write errors propagate; failures are not cached. Both hit and miss results are JSON-decoded into T. T must round-trip through JSON. For T=any, numbers remain json.Number. A type/encoding change requires a new key or namespace version. The TTL starts at Set, after loading. Concurrent misses may each call load, and a fill racing Delete/Clear may repopulate the key. Use versioned keys when that race matters. The caller owns the loader and must join its work.

func SetMulti

func SetMulti[T any](ctx context.Context, store Store, items map[string]T, ttl time.Duration) error

SetMulti encodes the entire JSON batch before passing it to the native store. Invalid input writes nothing. Backend failures can leave partial writes. Callers must not mutate items while this synchronous operation is running.

func ValidateItems

func ValidateItems(ctx context.Context, items map[string][]byte, ttl time.Duration, maxEntryBytes int) ([]string, error)

ValidateItems checks raw batch bounds before a driver writes anything. It returns sorted keys for deterministic admission. Size limits include keys. Callers must not mutate items or their byte slices during the operation.

func ValidateKey

func ValidateKey(key string) error

ValidateKey checks the portable key format shared by all cache drivers.

func ValidateKeys

func ValidateKeys(keys []string) error

ValidateKeys validates a bounded batch before any backend operation.

Types

type Driver

type Driver string

Driver identifies a supported cache implementation. Validate at configuration boundaries.

const (
	DriverDisabled Driver = "disabled"
	DriverMemory   Driver = "memory"
	DriverDatabase Driver = "database"
	DriverRedis    Driver = "redis"
)

func (Driver) Validate

func (d Driver) Validate() error

type Store

type Store interface {
	// Remember loads and caches a JSON value on a miss, with the same semantics
	// as Remember[any]. Objects become map[string]any and numbers json.Number.
	// Use the package-level Remember[T] helper when a typed result is needed.
	Remember(context.Context, string, time.Duration, func(context.Context) (any, error)) (any, error)
	Get(context.Context, string) ([]byte, bool, error)
	Set(context.Context, string, []byte, time.Duration) error
	Delete(context.Context, string) error
	// Exists checks presence without loading/decoding the value; expired is false.
	Exists(context.Context, string) (bool, error)
	// Clear removes this store's entries, never another namespace or connection.
	// Remote clears may be partial on error; concurrent writers may survive.
	Clear(context.Context) error
	// GetMulti returns caller-owned bytes for hits; missing keys are omitted.
	// Empty values remain hits. Duplicate keys collapse into one result.
	// Errors return no result. Reads are not a cross-key snapshot.
	GetMulti(context.Context, []string) (map[string][]byte, error)
	// SetMulti validates the full batch, then writes bytes with the same TTL.
	// Inputs must remain unchanged during the call; retained values are copied.
	// Nil and empty bytes represent an empty value, not deletion. Runtime errors
	// may leave a partial batch; there is no portable all-or-nothing guarantee.
	SetMulti(context.Context, map[string][]byte, time.Duration) error
}

Store is safe for concurrent use. Keys contain 1–255 UTF-8 bytes without NUL. Get returns caller-owned bytes; found distinguishes empty values from misses. Set copies retained data and atomically replaces the value and its expiry. TTL must be positive; remote drivers round it up to whole milliseconds. Delete succeeds for missing keys. Backend errors are never treated as misses. Connections belong to the caller; Store does not expose connection lifecycle.

func WithTelemetry

func WithTelemetry(store Store, tp trace.TracerProvider, mp metric.MeterProvider) (Store, error)

WithTelemetry borrows a store and native providers. It preserves Get's miss semantics and records only fixed operations/outcomes, never keys or values. Close the original store/client after consumers stop, not this wrapper.

Directories

Path Synopsis
Package database caches bytes in PostgreSQL or MySQL through a caller-owned Bun DB.
Package database caches bytes in PostgreSQL or MySQL through a caller-owned Bun DB.
Package memory provides a process-local byte cache with synchronous writes.
Package memory provides a process-local byte cache with synchronous writes.
Package migrations provides SQL templates for opt-in application migrations.
Package migrations provides SQL templates for opt-in application migrations.
Package redis caches bytes using native Redis expiration.
Package redis caches bytes using native Redis expiration.

Jump to

Keyboard shortcuts

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