Documentation
¶
Overview ¶
Package cache is a small, explicit cache for expensive reads that are safe to recompute. A Store holds bytes under a key for a bounded time; the code that uses it receives one and calls it where it wants caching. There is no ambient configuration, no automatic caching of database queries and no invalidation from writes: a cache only ever makes a read cheaper, and the database stays the source of truth.
A miss is not an error: Get reports it as ok == false with a nil error, and an error always means the backend failed, so a hit, a miss and a failure are three different outcomes. The Store, the stores in cache/local, cache/redis and cache/memcache, and the helpers here never log; they return errors and the host decides what is worth recording.
Keys follow one rule every backend can store (see ValidKey), and a TTL has one portable range (see CheckTTL). Values are bytes, whose format is the host's. The conformance suite in package cachetest holds every Store to this contract.
Index ¶
- Constants
- Variables
- func CheckTTL(ttl time.Duration) error
- func FetchJSON[T any](ctx context.Context, store Store, key string, ttl time.Duration, ...) (T, error)
- func GetJSON[T any](ctx context.Context, store Store, key string) (T, bool, error)
- func SetJSON[T any](ctx context.Context, store Store, key string, value T, ttl time.Duration) error
- func ValidKey(key string) bool
- type CorruptError
- type FetchOption
- type Store
Constants ¶
const ( // OpGet is a Store.Get failure. OpGet = "get" // OpDecode is a stored value that does not decode (ErrCorrupt). OpDecode = "decode" // OpEncode is a loaded value that cannot be encoded as JSON. OpEncode = "encode" // OpSet is a Store.Set failure. OpSet = "set" )
The operations a FetchJSON cache failure is reported under.
const ( // MaxKeyLength is the longest key, in bytes. It is below Memcached's 250 // byte limit so a Prefix has room to grow. MaxKeyLength = 200 // MaxTTL is the longest portable TTL. A longer retention belongs in your // database or object storage, not in a cache. MaxTTL = 30 * 24 * time.Hour )
Variables ¶
var ( // ErrInvalidKey is returned for a key that is not 1 to 200 bytes of // printable ASCII without spaces, before any request is made. ErrInvalidKey = errors.New("cache: invalid key") // ErrInvalidTTL is returned for a TTL that is not greater than zero and // at most MaxTTL, before any request is made. ErrInvalidTTL = errors.New("cache: invalid ttl") // ErrTooLarge is returned when a Store refuses a value for its size. ErrTooLarge = errors.New("cache: value is too large") )
var ErrCorrupt = errors.New("cache: corrupt value")
ErrCorrupt is returned by GetJSON for a stored value that cannot be decoded into the requested type. Match it with errors.Is; errors.As with *CorruptError gives the key and the decode error.
Functions ¶
func FetchJSON ¶
func FetchJSON[T any](ctx context.Context, store Store, key string, ttl time.Duration, load func(context.Context) (T, error), opts ...FetchOption) (T, error)
FetchJSON returns the value cached under key, or calls load, caches what it returns for ttl and returns that. It is deliberately fail-open: the cache is an optimization, so
- if reading or decoding fails, FetchJSON reports it, calls load, and returns the loaded value if loading succeeds;
- if writing the loaded value fails, FetchJSON reports it and still returns the value.
A key or ttl that is not valid (ErrInvalidKey, ErrInvalidTTL) is a programming error, not a cache failure: FetchJSON returns it before calling load, and never works around it. That includes a key the store itself refuses, such as one a Prefix made too long.
A load failure is returned as is (it is not a cache error). Cache failures go to the OnError hook when one is supplied. Without a hook they are not returned when loading succeeds, since observability is the hook's job, but when both the cache and load fail the errors are returned joined.
FetchJSON does not coalesce concurrent calls for the same key: when a hot key expires, every caller loads. Wrap load with singleflight to avoid it.
func GetJSON ¶
GetJSON reads the value under key and decodes it as JSON into a T. A miss is ok == false with a nil error. Bytes that do not decode into T are an error matching ErrCorrupt, never a zero-value hit. Any other error is the Store's own.
Types ¶
type CorruptError ¶
type CorruptError struct {
// Key is the key whose value could not be decoded.
Key string
// Err is the underlying decode error.
Err error
}
CorruptError is the typed form of ErrCorrupt.
func (*CorruptError) Error ¶
func (e *CorruptError) Error() string
func (*CorruptError) Unwrap ¶
func (e *CorruptError) Unwrap() error
Unwrap returns the underlying decode error.
type FetchOption ¶
type FetchOption func(*fetchConfig)
FetchOption configures FetchJSON.
func OnError ¶
func OnError(fn func(op string, err error)) FetchOption
OnError reports each cache failure FetchJSON works around. fn is called synchronously, once per failed cache operation, with the operation (OpGet, OpDecode, OpEncode or OpSet) and the error. It cannot change what FetchJSON does, and a loader failure is never reported to it. A panic in fn is the host's and propagates.
type Store ¶
type Store interface {
// Get returns the value stored under key. A miss, including an expired
// value, is ok == false with a nil error; an error means the backend
// failed. The returned slice belongs to the caller.
Get(ctx context.Context, key string) (value []byte, ok bool, err error)
// Set stores value under key for ttl, replacing any existing value. The
// Store does not retain value after Set returns. It fails with
// ErrInvalidKey, or ErrInvalidTTL unless 0 < ttl <= MaxTTL, before any
// request, and with ErrTooLarge for a value the backend refuses for size.
// ttl is the longest the value may be served: a Store never treats it as
// elapsed sooner than asked (a backend with coarser time units rounds
// up). It is not a promise of retention. A bounded or remote cache may
// evict the value, lose it in a restart, or have it deleted earlier, so
// a later Get can miss; store only what can be recomputed.
Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
// Delete removes key. Deleting a missing key is not an error.
Delete(ctx context.Context, key string) error
}
Store is a cache. Implementations are safe for concurrent use and pass the suite in package cachetest.
func Prefix ¶
Prefix returns a Store that puts prefix in front of every key before delegating to store. It validates the final prefixed key, so an unusual prefix (even an empty one) is accepted as long as every resulting key is valid, and a combination that is too long or has a space is refused with ErrInvalidKey.
Versioning is yours: change "profile:v2:" to "profile:v3:" and every older entry becomes unreachable and expires by its TTL. That is the way to invalidate in bulk; there is no Clear.