Documentation
¶
Overview ¶
Package pacecache provides a bounded, concurrent in-process cache.
Cache keys may use any comparable Go type. Entries support TTL expiration, optional sliding expiration and TTL jitter, LRU eviction, cache-aside loading with configurable default or per-call loaders, duplicate load suppression, and explicit removal.
Background expiration cleanup is optional and is not started automatically. StartCleanup runs the cleanup loop and blocks until StopCleanup is called. Callers that want background cleanup should start it in a goroutine. WithCleanupInterval configures the regular cleanup interval.
Cache mutations act as publication barriers for concurrent loads, preventing superseded loader results from overwriting newer cache state.
Cache statistics are collected locally and exposed through Cache.Stats. Optional metrics integrations register when a cache is created and observe those snapshots without adding telemetry calls to the cache request path.
The cache is local to one application process. It does not provide distributed cache coherence between application instances.
Index ¶
- Constants
- Variables
- type Cache
- func (cache *Cache[K, V]) Clear()
- func (cache *Cache[K, V]) Delete(keys ...K)
- func (cache *Cache[K, V]) DeleteExpired() int64
- func (cache *Cache[K, V]) Exists(key K) bool
- func (cache *Cache[K, V]) Get(key K) (V, bool)
- func (cache *Cache[K, V]) GetAndDelete(key K) (V, bool)
- func (cache *Cache[K, V]) GetEntry(key K) (Entry[V], bool)
- func (cache *Cache[K, V]) GetOrLoad(ctx context.Context, key K) (V, bool, error)
- func (cache *Cache[K, V]) GetOrLoadEntry(ctx context.Context, key K) (Entry[V], bool, error)
- func (cache *Cache[K, V]) GetOrLoadEntryFunc(ctx context.Context, key K, loader Loader[K, V]) (Entry[V], bool, error)
- func (cache *Cache[K, V]) GetOrLoadFunc(ctx context.Context, key K, loader Loader[K, V]) (V, bool, error)
- func (cache *Cache[K, V]) GetOrSet(key K, value V, expiration time.Duration) (V, bool)
- func (cache *Cache[K, V]) GetOrSetEntry(key K, value V, expiration time.Duration) (Entry[V], bool)
- func (cache *Cache[K, V]) RefreshTTL(key K) bool
- func (cache *Cache[K, V]) Set(key K, value V, expiration time.Duration)
- func (cache *Cache[K, V]) StartCleanup()
- func (cache *Cache[K, V]) Stats() Stats
- func (cache *Cache[K, V]) StopCleanup()
- type Entry
- type Loader
- type Metrics
- type MetricsSource
- type Option
- func WithCleanupBatchSize(size int) Option
- func WithCleanupEntryBudget(entries int) Option
- func WithCleanupInterval(interval time.Duration) Option
- func WithJitter(jitter time.Duration) Option
- func WithMaxEntries(maxEntries int) Option
- func WithMetrics(metrics Metrics) Option
- func WithName(name string) Option
- func WithSegmentCount(count int) Option
- func WithSlidingExpiration() Option
- func WithTTL(ttl time.Duration) Option
- type Stats
Constants ¶
const ( // DefaultExpiration uses the cache's configured TTL. DefaultExpiration time.Duration = 0 // NoExpiration disables time-based expiration for the entry. NoExpiration time.Duration = -1 )
Variables ¶
var ( // ErrNotInitialized indicates that an operation requires an initialized // cache but the cache is nil or has not been initialized. ErrNotInitialized = errors.New("pacecache: cache is not initialized") // ErrNoLoader indicates that an operation requires a loader but none is // available. NewWithDefaultLoader also returns ErrNoLoader when passed a nil loader. ErrNoLoader = errors.New("pacecache: loader is not configured") // ErrLoadSuperseded indicates that a successful loader result was made stale // by a newer cache mutation, such as Set, a GetOrSet or GetOrSetEntry insertion, // Delete, or Clear, before it could be published to the cache. ErrLoadSuperseded = errors.New("pacecache: load superseded by cache mutation") )
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache[K comparable, V any] struct { // contains filtered or unexported fields }
Cache is a bounded in-process cache for keys of type K and values of type V.
Cache uses exact LRU eviction within each storage segment and TTL expiration. Entries may optionally use sliding expiration. GetOrLoad, GetOrLoadFunc, GetOrLoadEntry, and GetOrLoadEntryFunc provide cache-aside loading and coalesce concurrent loads for the same key.
Cache is safe for concurrent use. A Cache must not be copied after creation.
func New ¶
func New[K comparable, V any]( options ...Option, ) (*Cache[K, V], error)
New creates a Cache.
Unless overridden by options, New uses the default cache capacity, a single storage segment, and no time-based expiration. No default loader is configured. Metrics are disabled by default, and background cleanup is not started automatically.
func NewWithDefaultLoader ¶ added in v1.2.0
func NewWithDefaultLoader[K comparable, V any]( loader Loader[K, V], options ...Option, ) (*Cache[K, V], error)
NewWithDefaultLoader creates a Cache with the given default loader.
The loader is used by GetOrLoad and GetOrLoadEntry when no live cache entry exists. Per-call loaders may be supplied through GetOrLoadFunc and GetOrLoadEntryFunc. Loader must not be nil.
Options, metrics, and background cleanup have the same semantics as New.
func (*Cache[K, V]) Clear ¶ added in v1.2.0
func (cache *Cache[K, V]) Clear()
Clear removes all entries from the cache.
Clear acts as a cache-wide publication barrier. Successful loader results already in flight are discarded with ErrLoadSuperseded if Clear wins before publication, and cannot repopulate the cache afterward.
The removed entries may include physically resident entries whose TTL has already expired but which have not yet been removed.
Clear is a no-op on an uninitialized Cache.
func (*Cache[K, V]) Delete ¶ added in v1.2.0
func (cache *Cache[K, V]) Delete(keys ...K)
Delete removes the specified keys from the cache.
Delete acts as a publication barrier for each key. A successful loader result already in flight for a deleted key is discarded with ErrLoadSuperseded if Delete wins before publication, and cannot repopulate that key afterward.
Missing keys are ignored. Duplicate keys are allowed.
Delete is a no-op when called without keys or on an uninitialized Cache.
func (*Cache[K, V]) DeleteExpired ¶ added in v1.2.0
DeleteExpired physically removes expired entries using the cache expiration index and returns the number of entries removed.
DeleteExpired is always available; background cleanup does not need to be running. Logical expiration is independent of physical cleanup: an expired entry is never returned even if it has not yet been reclaimed. Nearby expiration deadlines are grouped internally. Bucket eligibility may trail the exact TTL deadline by up to the internal bucket resolution; actual physical reclamation also depends on when cleanup runs. Manual cleanup uses the configured cleanup batch size and entry budget, yielding cooperatively between quanta until all entries due at the start of the call are drained.
func (*Cache[K, V]) Exists ¶
Exists reports whether a live entry exists for key.
Expired and missing entries return false. Exists does not update LRU recency, refresh sliding expiration, or affect lookup statistics.
An expired entry observed by Exists is removed from storage and contributes to expiration statistics.
func (*Cache[K, V]) Get ¶
Get returns the cached value for key.
The returned bool reports whether a live entry exists. A live hit updates LRU recency and contributes to lookup statistics. Expired entries are always treated as misses and are removed when observed, by DeleteExpired, or by background cleanup when it is running. When sliding expiration is enabled, a live hit refreshes the entry using the TTL with which it was stored.
func (*Cache[K, V]) GetAndDelete ¶ added in v1.2.0
GetAndDelete atomically returns the live value for key and removes it from the cache. Missing and expired entries return the zero value with found=false. Expired entries are reclaimed as expirations.
GetAndDelete acts as a publication barrier for the same key. An in-flight successful loader result is discarded with ErrLoadSuperseded if this method wins before publication, even when no resident entry exists.
The operation does not refresh sliding expiration, update LRU recency, or affect lookup hit/miss statistics. A successfully removed live entry is counted as a deleted entry.
func (*Cache[K, V]) GetEntry ¶
GetEntry returns an immutable snapshot of the cached entry for key.
GetEntry has the same lookup semantics as Get: it updates LRU recency, contributes to lookup statistics, removes an observed expired entry, and refreshes a live entry when sliding expiration is enabled.
For an entry stored with NoExpiration, Entry.ExpiresAt returns the zero time. When sliding expiration refreshes an entry, ExpiresAt reflects the refreshed deadline.
func (*Cache[K, V]) GetOrLoad ¶
GetOrLoad returns the cached value for key or obtains it from the configured default loader.
On a cache miss, GetOrLoad returns ErrNoLoader when the cache was created without a default loader. Use NewWithDefaultLoader to configure one, or GetOrLoadFunc to supply a loader for a specific operation.
A loader result with found=true is cached using the configured TTL. A result with found=false and a nil error is returned to callers but is not cached. Loader errors are never cached.
Concurrent misses for the same key are coalesced. The caller that starts the shared load executes the loader synchronously with its context. Other callers may stop waiting independently when their own contexts are canceled.
Set, GetOrSet and GetOrSetEntry insertions, Delete, and Clear act as publication barriers for the affected key or keys. If a successful loader result is superseded by a newer cache mutation before publication, GetOrLoad discards the loader result and returns ErrLoadSuperseded. Loader errors take precedence over ErrLoadSuperseded. Mutations of other keys do not affect the load, even when those keys share the same segment.
A loader must not call GetOrLoad, GetOrLoadFunc, GetOrLoadEntry, or GetOrLoadEntryFunc recursively for the same key, because the nested call would wait for the load already in progress.
When err is non-nil, the returned value is the zero value of V and found is false.
func (*Cache[K, V]) GetOrLoadEntry ¶
GetOrLoadEntry returns a read-only cache entry snapshot for key or obtains the value from the configured default loader and publishes it before returning the snapshot.
On a cache miss, GetOrLoadEntry returns ErrNoLoader when the cache was created without a default loader. Use NewWithDefaultLoader to configure one, or GetOrLoadEntryFunc to supply a loader for a specific operation.
A loader result with found=false is not cached and returns the zero Entry with found=false. GetOrLoadEntry otherwise has the same lookup, singleflight, publication-barrier, LRU, sliding-expiration, and statistics semantics as GetOrLoad.
ExpiresAt reflects the exact deadline of the cached entry observed or published by the operation, including TTL jitter. As with GetEntry, the returned Entry is a snapshot and the cache may change immediately after the operation completes.
func (*Cache[K, V]) GetOrLoadEntryFunc ¶ added in v1.2.1
func (cache *Cache[K, V]) GetOrLoadEntryFunc( ctx context.Context, key K, loader Loader[K, V], ) (Entry[V], bool, error)
GetOrLoadEntryFunc returns a read-only cache entry snapshot for key or obtains the value from loader and publishes it before returning the snapshot.
The supplied loader is used instead of the cache's configured default loader when this caller starts the shared load. If another caller already owns a same-key load, GetOrLoadEntryFunc joins that wave and the supplied loader is not invoked. Loader is only required when no live cache entry exists; a nil loader therefore returns ErrNoLoader on a miss.
GetOrLoadEntryFunc otherwise has the same semantics as GetOrLoadEntry.
func (*Cache[K, V]) GetOrLoadFunc ¶ added in v1.2.1
func (cache *Cache[K, V]) GetOrLoadFunc( ctx context.Context, key K, loader Loader[K, V], ) (V, bool, error)
GetOrLoadFunc returns the cached value for key or obtains it from loader.
The supplied loader is used instead of the cache's configured default loader when this caller starts the shared load. If another caller already owns a same-key load, GetOrLoadFunc joins that wave and the supplied loader is not invoked. Loader is only required when no live cache entry exists; a nil loader therefore returns ErrNoLoader on a miss.
GetOrLoadFunc otherwise has the same cache, singleflight, publication, and statistics semantics as GetOrLoad.
func (*Cache[K, V]) GetOrSet ¶
GetOrSet returns the live value for key or atomically stores the provided value when no live entry exists.
The returned bool reports whether an existing value was found. On a live hit, the provided value and expiration are ignored and the operation has the same LRU, sliding-expiration, and lookup-statistics semantics as Get. On a miss or expired entry, the provided value is stored using the same expiration semantics as Set and found=false is returned.
When GetOrSet stores the provided value, it acts as a publication barrier for the same key. An in-flight successful loader result cannot overwrite the value inserted by GetOrSet afterward.
func (*Cache[K, V]) GetOrSetEntry ¶
func (cache *Cache[K, V]) GetOrSetEntry( key K, value V, expiration time.Duration, ) (Entry[V], bool)
GetOrSetEntry returns the live entry for key or atomically stores the provided value when no live entry exists.
The returned bool reports whether an existing entry was found. On a live hit, the provided value and expiration are ignored and the operation has the same LRU, sliding-expiration, and lookup-statistics semantics as GetEntry. On a miss or expired entry, the provided value is stored using the same expiration semantics as Set and found=false is returned. The returned Entry always describes the resident value selected by the operation, including the actual expiration deadline assigned to a newly inserted entry.
When GetOrSetEntry stores the provided value, it acts as a publication barrier for the same key. An in-flight successful loader result cannot overwrite the value inserted by GetOrSetEntry afterward.
func (*Cache[K, V]) RefreshTTL ¶
RefreshTTL renews the expiration deadline of a live entry.
The entry is refreshed using the effective TTL with which it was stored. RefreshTTL works independently of sliding expiration. An entry without time-based expiration is considered live and returns true without changing its state.
Expired and missing entries return false. An expired entry observed by RefreshTTL is removed from storage and contributes to expiration statistics.
RefreshTTL does not update LRU recency or lookup statistics.
func (*Cache[K, V]) Set ¶
Set stores a value in the cache.
DefaultExpiration uses the cache's configured TTL. A positive expiration overrides the configured TTL for this entry. NoExpiration disables time-based expiration.
Set acts as a publication barrier. A successful loader result that was already in flight for the same key is discarded with ErrLoadSuperseded if Set wins before publication, and cannot overwrite the explicitly stored value afterward.
func (*Cache[K, V]) StartCleanup ¶ added in v1.3.0
func (cache *Cache[K, V]) StartCleanup()
StartCleanup runs periodic expiration cleanup until StopCleanup is called. StartCleanup blocks for the lifetime of the cleanup loop; callers that want background cleanup should start it in a goroutine. If cleanup is already running, StartCleanup returns immediately.
func (*Cache[K, V]) StopCleanup ¶ added in v1.3.0
func (cache *Cache[K, V]) StopCleanup()
StopCleanup stops a running cleanup loop. It blocks until the cleanup loop accepts the stop signal. Repeated calls are safe. StopCleanup is a no-op when cleanup is not running or Cache is nil.
type Entry ¶
type Entry[V any] struct { // contains filtered or unexported fields }
Entry is a read-only snapshot returned by GetEntry, GetOrLoadEntry, GetOrLoadEntryFunc, or GetOrSetEntry.
Entry captures the value and expiration metadata observed by the operation. The value is returned using normal Go value semantics and is not deep-copied.
ExpiresAt returns the entry expiration time. It returns the zero time for an entry stored with NoExpiration.
type Loader ¶
Loader obtains one value for key from the underlying data source.
found=true means the value exists and may be cached. found=false with a nil error means the value does not exist; not-found results are returned to the caller but are not stored by pacecache.
Loader errors are never cached.
type Metrics ¶
type Metrics interface {
Register(source MetricsSource) error
}
Metrics registers cache statistics with a metrics implementation.
Implementations must be safe to reuse across multiple caches. Register may be called concurrently.
If Register returns an error, the implementation must release any resources created during the registration attempt.
type MetricsSource ¶ added in v1.3.0
MetricsSource exposes cache identity and statistics to a metrics implementation.
type Option ¶
type Option func(*settings) error
Option configures a Cache created by New or NewWithDefaultLoader.
func WithCleanupBatchSize ¶
WithCleanupBatchSize configures the maximum number of expired entries removed from one storage segment in a single cleanup batch.
The setting applies to both manual and background cleanup. Larger batches can increase cleanup throughput but may hold a segment lock for longer. Values larger than a segment or the remaining cleanup budget are safe and are naturally limited by the available work. The default is 256.
func WithCleanupEntryBudget ¶
WithCleanupEntryBudget configures the maximum number of expired entries removed during one cooperative cleanup quantum.
The setting applies to both manual and background cleanup. Larger budgets allow large expiration backlogs to be drained more aggressively. Background cleanup is additionally bounded by an internal time budget. Manual cleanup yields cooperatively after exhausting the entry budget and continues until all entries due at the start of the call are drained. Values larger than the cache size are safe. The default is 16384.
func WithCleanupInterval ¶
WithCleanupInterval configures the interval between regular cleanup wakeups.
The default is one minute. While expired backlog remains, the cleaner may schedule bounded continuation work sooner. The interval does not affect logical TTL precision or the internal expiration bucket resolution.
Background cleanup must be started explicitly with StartCleanup. Manual cleanup through Cache.DeleteExpired is always available.
func WithJitter ¶
WithJitter configures random TTL spread.
Jitter adds a random duration smaller than the configured value when an expiring entry is stored, reducing synchronized expiration. With sliding expiration, the resulting effective TTL is reused on every refresh instead of selecting another jitter value. Zero disables jitter.
func WithMaxEntries ¶
WithMaxEntries configures the total cache entry budget.
With one segment, the full budget is shared by the cache. When multiple segments are configured, the budget is distributed across them and effective capacity utilization may be slightly lower because each segment enforces its own local budget.
func WithMetrics ¶
WithMetrics configures optional cache metrics.
The Metrics implementation may be reused by multiple caches. The cache does not manage the lifecycle of metrics registrations.
func WithName ¶ added in v1.3.0
WithName configures an optional logical cache name.
Metrics implementations may use the name to distinguish cache instances. An empty name leaves the cache unnamed.
func WithSegmentCount ¶
WithSegmentCount configures the number of independent cache segments.
The default is one segment. More segments can reduce lock contention under concurrent access but may reduce effective capacity utilization because each segment has its own entry budget. Benchmark segment counts against the application's actual workload.
func WithSlidingExpiration ¶
func WithSlidingExpiration() Option
WithSlidingExpiration refreshes the expiration deadline of live expiring entries whenever they are successfully read.
Each entry is refreshed using the effective TTL selected when it was stored. Entries using DefaultExpiration derive that TTL from the cache configuration, while entries with an explicit positive TTL retain their own TTL. Configured jitter is selected once when the entry is stored and reused by subsequent refreshes. Entries using NoExpiration are not refreshed.
type Stats ¶
type Stats struct {
// EntryCount is the number of entries currently resident in storage.
// Expired entries may remain resident until they are observed, evicted,
// deleted, cleared, or reclaimed by manual or background cleanup.
EntryCount int64
// MaxEntries is the configured total entry budget after applying defaults.
MaxEntries int64
// SegmentCount is the number of independent storage and coordination
// segments.
SegmentCount int64
// HitCount is the cumulative number of cache hits.
HitCount int64
// MissCount is the cumulative number of cache misses. An expired entry
// observed by a caller is counted as a miss.
MissCount int64
// LoadFoundCount is the cumulative number of actual loader invocations that
// returned found=true.
LoadFoundCount int64
// LoadNotFoundCount is the cumulative number of actual loader invocations
// that returned found=false without an error.
LoadNotFoundCount int64
// LoadErrorCount is the cumulative number of actual loader invocations that
// returned an error.
LoadErrorCount int64
// LoadSupersededCount is the cumulative number of successful actual loader
// invocations whose result was discarded because a newer cache mutation,
// such as Set, GetOrSet and GetOrSetEntry insertions, Delete, or
// Clear, won before publication.
//
// Superseded loads are still included in LoadFoundCount or LoadNotFoundCount,
// according to the loader result, and are not included in LoadErrorCount.
LoadSupersededCount int64
// LoadDuration is the cumulative duration of actual loader invocations,
// including found, not-found, and failed loads.
LoadDuration time.Duration
// singleflight result shared with at least one other caller.
SharedCount int64
// DeletedEntryCount is the cumulative number of resident entries removed by
// key-scoped deletion operations such as Delete and GetAndDelete.
// Missing keys and duplicate keys that were already removed do not increase
// the count.
DeletedEntryCount int64
// ClearedEntryCount is the cumulative number of resident entries removed by
// Clear.
//
// This may include physically resident entries whose TTL had already expired
// but which had not yet been removed.
ClearedEntryCount int64
// CleanupCount is the cumulative number of completed explicit
// DeleteExpired calls.
CleanupCount int64
// CleanupWorkerRunCount is the cumulative number of cleanup quanta
// completed by the background cleanup worker.
CleanupWorkerRunCount int64
// CleanupWorkerPendingCount is the cumulative number of background cleanup
// quanta that completed with more expired entries still pending.
CleanupWorkerPendingCount int64
// CleanupWorkerDuration is the cumulative time spent executing background
// cleanup quanta. Waiting between cleanup runs is not included.
CleanupWorkerDuration time.Duration
// EvictionCount is the cumulative number of entries evicted because a
// storage segment reached capacity.
EvictionCount int64
// ExpirationCount is the cumulative number of entries removed because their
// TTL expired, either lazily during lookup or by manual or background cleanup.
ExpirationCount int64
}
Stats is a detached snapshot of cache statistics.
The snapshot is assembled from independent cache segments. Concurrent cache activity may continue while Stats is being collected, so individual fields are not guaranteed to represent one globally atomic instant.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
extra
|
|
|
paceotel
module
|
|
|
internal
|
|
|
singleflight
Package singleflight provides a duplicate function call suppression mechanism.
|
Package singleflight provides a duplicate function call suppression mechanism. |


