cache

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 7, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package cache is a small, typed caching layer with two implementations and one rule: a cache failure is never a request failure.

Every method here degrades to a miss. A Redis that is down makes the service slower; it must not make it broken. That is the single most important property of a cache wrapper and the one most hand-rolled ones get wrong, by returning the Redis error up the stack and turning a latency problem into a 500.

Example

GetOrSet is the usual shape: return the cached value, or load it, store it and return it.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/IncepTools/go/src/platform/cache"
)

type product struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

func main() {
	c := cache.NewMemory(time.Minute)
	defer c.Close() //nolint:errcheck

	ctx := context.Background()
	loads := 0
	load := func(context.Context) (product, error) {
		loads++
		return product{ID: "9c1e", Name: "Desk lamp"}, nil
	}

	key := cache.Key("catalog", "acct-1a3c", "product", "9c1e")
	for range 3 {
		p, err := cache.GetOrSet(ctx, c, key, time.Minute, load)
		if err != nil {
			panic(err)
		}
		_ = p
	}
	fmt.Println("loads:", loads)
}
Output:
loads: 1

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrMiss = errors.New("cache: miss")

ErrMiss is returned by Get when the key is absent. It is the only error a caller normally handles; everything else is logged and reported as a miss.

Functions

func Get

func Get[T any](ctx context.Context, c Cache, key string) (T, bool)

Get reads and decodes a value.

func GetOrSet

func GetOrSet[T any](ctx context.Context, c Cache, key string, ttl time.Duration, load func(ctx context.Context) (T, error)) (T, error)

GetOrSet returns the cached value, or calls load, stores the result and returns it.

load's error is returned to the caller UNCACHED. Caching a failure is occasionally what you want — see Negative below — but it must be a decision, never a default: a cached 500 outlives the incident that caused it.

Example (DoesNotCacheFailures)

A failure is never cached. Caching one means a transient outage outlives the incident that caused it.

package main

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/IncepTools/go/src/platform/cache"
)

type product struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

func main() {
	c := cache.NewMemory(time.Minute)
	defer c.Close() //nolint:errcheck

	loads := 0
	load := func(context.Context) (product, error) {
		loads++
		return product{}, errors.New("upstream down")
	}

	for range 3 {
		_, _ = cache.GetOrSet(context.Background(), c, "k", time.Minute, load)
	}
	fmt.Println("loads:", loads)
}
Output:
loads: 3

func Key

func Key(parts ...string) string

Key builds a namespaced cache key from parts: Key("catalog", "product", id) gives "catalog:product:<id>".

Always namespace. An un-namespaced key collides across modules, and the collision presents as one module reading another's value with the right type and the wrong meaning — which is far harder to find than a crash.

Example

Always namespace a key, and include the account where the data is per-account. A key without it is one customer's data served to another.

package main

import (
	"fmt"

	"github.com/IncepTools/go/src/platform/cache"
)

func main() {
	fmt.Println(cache.Key("catalog", "acct-1a3c", "product", "9c1e"))
}
Output:
catalog:acct-1a3c:product:9c1e

func Set

func Set[T any](ctx context.Context, c Cache, key string, value T, ttl time.Duration)

Set encodes and stores a value. A nil cache or a failing backend is a no-op.

Types

type Cache

type Cache interface {
	Get(ctx context.Context, key string) ([]byte, error)
	Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
	Delete(ctx context.Context, keys ...string) error
	// DeletePrefix removes every key under a prefix. On Redis this is a SCAN,
	// not a KEYS: KEYS blocks the server for the length of the keyspace, and
	// on a production instance that is an outage.
	DeletePrefix(ctx context.Context, prefix string) error
	Ping(ctx context.Context) error
}

Cache is the storage contract.

An implementation MUST treat a backend failure on Get as a miss, and a backend failure on Set as a no-op. It should log; it must not propagate.

type Memory

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

Memory is an in-process cache with TTL expiry.

It is correct for a single instance, for development, and for data that is genuinely per-process. It is NOT shared: with N replicas you have N caches that invalidate independently, so a Delete on one leaves the other N-1 serving the old value until it expires. Where that matters, use Redis.

func NewMemory

func NewMemory(interval time.Duration) *Memory

NewMemory returns an in-process cache that sweeps expired entries every interval (default 1 minute). Call Close to stop the sweeper.

func (*Memory) Close

func (m *Memory) Close() error

Close stops the sweeper. Safe to call more than once.

func (*Memory) Delete

func (m *Memory) Delete(_ context.Context, keys ...string) error

Delete implements Cache.

func (*Memory) DeletePrefix

func (m *Memory) DeletePrefix(_ context.Context, prefix string) error

DeletePrefix implements Cache.

func (*Memory) Get

func (m *Memory) Get(_ context.Context, key string) ([]byte, error)

Get implements Cache.

func (*Memory) Ping

func (m *Memory) Ping(context.Context) error

Ping implements Cache.

func (*Memory) Set

func (m *Memory) Set(_ context.Context, key string, value []byte, ttl time.Duration) error

Set implements Cache.

type Noop

type Noop struct{}

Noop is a cache that stores nothing. Inject it to disable caching without making every call site nil-check.

func (Noop) Delete

func (Noop) Delete(context.Context, ...string) error

Delete implements Cache.

func (Noop) DeletePrefix

func (Noop) DeletePrefix(context.Context, string) error

DeletePrefix implements Cache.

func (Noop) Get

func (Noop) Get(context.Context, string) ([]byte, error)

Get implements Cache.

func (Noop) Ping

func (Noop) Ping(context.Context) error

Ping implements Cache.

func (Noop) Set

Set implements Cache.

type Redis

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

Redis is a shared cache backed by a Redis server.

func NewRedis

func NewRedis(client redis.UniversalClient, prefix string) *Redis

NewRedis wraps a client. prefix is prepended to every key.

func (*Redis) Close

func (r *Redis) Close() error

Close releases the client.

func (*Redis) Delete

func (r *Redis) Delete(ctx context.Context, keys ...string) error

Delete implements Cache.

Unlike Get and Set, a delete failure is RETURNED. An invalidation that silently did not happen leaves the cache serving data the database no longer has, and the caller may want to refuse the write rather than accept that.

func (*Redis) DeletePrefix

func (r *Redis) DeletePrefix(ctx context.Context, prefix string) error

DeletePrefix removes every key under a prefix using SCAN in batches.

SCAN, never KEYS: KEYS is O(keyspace) and blocks the single-threaded server for its duration, which on a production instance is an outage caused by a cache invalidation.

func (*Redis) Get

func (r *Redis) Get(ctx context.Context, key string) ([]byte, error)

Get implements Cache. Every failure other than a genuine miss is logged and reported as a miss, so a Redis outage degrades latency rather than availability.

func (*Redis) Ping

func (r *Redis) Ping(ctx context.Context) error

Ping implements Cache.

func (*Redis) Set

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

Set implements Cache. A failure is logged and swallowed.

Jump to

Keyboard shortcuts

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