Documentation
¶
Overview ¶
Package patterns provides small, optional helpers for common Valkey usage patterns. It is not a Redis ORM: there are no struct tags, no HASH repositories, and no replacement for valkey-go's command API. The chassis (cf_valkey.CFValkey) owns lifecycle, health, reload, key prefix, and metrics; this package adds a few sharp, prefix-aware helpers on top.
Allowlisted helpers:
- Mutex: a single-instance distributed lock (SET NX PX + Lua unlock). Not Redlock. See the Mutex godoc for failure modes. Lock traffic is counted on the component's LockMeter and exposed via /metrics as lock_*_total counters.
- GetJSON / SetJSON: marshal/unmarshal helpers with prefix-aware keys.
- GetOrLoad: in-process singleflight cache-aside (one loader per key per process). Process-local only; other pods still stampede.
All helpers take a *cf_valkey.CFValkey (or a minimal interface) and use Client() + Key() so they are prefix-aware. Apps that only need a client never import this package.
Index ¶
- Variables
- func GetJSON(ctx context.Context, vk ClientKeyer, dst any, parts ...string) error
- func GetOrLoad(ctx context.Context, vk ClientKeyer, g *singleflight.Group, key string, ...) (val []byte, shared bool, err error)
- func SetJSON(ctx context.Context, vk ClientKeyer, v any, ttl time.Duration, parts ...string) error
- type ClientKeyer
- type LoadFunc
- type Mutex
Constants ¶
This section is empty.
Variables ¶
var ErrLocked = errors.New("patterns: lock is held by another owner")
ErrLocked is returned by WithLock when TryLock finds the lock already held.
Functions ¶
func GetJSON ¶
GetJSON retrieves the value at vk.Key(parts...) and unmarshals it from JSON into dst. Returns nil and no error when the key does not exist (Valkey nil reply). A non-nil error is returned for transport failures or JSON decode errors.
func GetOrLoad ¶
func GetOrLoad( ctx context.Context, vk ClientKeyer, g *singleflight.Group, key string, ttl time.Duration, load LoadFunc, ) (val []byte, shared bool, err error)
GetOrLoad returns cached bytes for key (via vk.Key), or runs load once for concurrent callers in this process, then stores the result with ttl.
WARNING: process-local singleflight only. Other pods still stampede on a cold key. For cross-pod coalescing, compose with a Mutex.
shared=true when this caller waited on another goroutine's load.
Failure modes:
- Many goroutines, one process, cold key: one load, others share.
- Many pods, cold key: each pod may call load.
- load fails: error returned to waiters; nothing cached.
- Valkey down on GET: error returned (no silent bypass).
- Valkey down on SET after load: loaded value returned along with the set error so callers can log; the successful load is not lost.
Types ¶
type ClientKeyer ¶
ClientKeyer is the minimal interface patterns requires from a Valkey component. *cf_valkey.CFValkey satisfies it.
type LoadFunc ¶
LoadFunc fetches the canonical value on cache miss. Called at most once per key per in-flight wave inside this process.
type Mutex ¶
type Mutex struct {
// contains filtered or unexported fields
}
Mutex is a small, single-instance distributed lock backed by Valkey. It uses SET NX PX for acquisition and a Lua script for ownership-checked deletion, so Unlock never removes another holder's lock.
Failure modes (read before use):
- Single Valkey primary: at most one holder per key among clients talking to that instance. This is the default deployment assumption.
- Holder crashes: lock expires after TTL; another pod may acquire. Size TTL >> expected critical section; << how long "double work" is acceptable.
- Critical section outlives TTL: another pod can take the lock while the first still runs. Keep work << TTL.
- Multi-master / split brain / Redlock: out of scope. Use a stronger coordination system if you need that.
This is not Redlock. It is for "only one pod should run this reconcile / refresh", not for substituting a consensus log.
func NewMutex ¶
func NewMutex(vk ClientKeyer, name string, ttl time.Duration) *Mutex
NewMutex creates a distributed mutex. The lock key is always built through vk.Key("lock", name) so it sits in the component's prefix-aware key space. TTL is mandatory; there is no "lock forever" mode. When vk exposes a LockMeter (the *cf_valkey.CFValkey chassis does), lock traffic is counted and exposed on /metrics as *total counters.
func (*Mutex) TryLock ¶
TryLock attempts to acquire the lock. It returns ok=true if the lock was acquired, ok=false if another holder holds it. An error is returned only for transport-level failures (Valkey down, context cancelled).
func (*Mutex) Unlock ¶
Unlock releases the lock only if the caller still owns it (token match). If the lock has expired or been stolen, Unlock returns nil without error (the lock is no longer ours to release). The outcome is counted on the component's lock meter (unlock_ok vs unlock_mismatch).