diff --git a/cache.go b/cache.go index 9aeb815..e388be8 100644 --- a/cache.go +++ b/cache.go @@ -127,15 +127,15 @@ func (c *Client[T]) getShard(key string) *shard[T] { func (c *Client[T]) get(key string) (value T, exists, ignore, refresh bool) { shard := c.getShard(key) val, exists, ignore, refresh := shard.get(key) - c.reportCacheHits(exists) + c.reportCacheHits(exists, ignore, refresh) return val, exists, ignore, refresh } // Get retrieves a single value from the cache. func (c *Client[T]) Get(key string) (T, bool) { shard := c.getShard(key) - val, ok, ignore, _ := shard.get(key) - c.reportCacheHits(ok) + val, ok, ignore, refresh := shard.get(key) + c.reportCacheHits(ok, ignore, refresh) return val, ok && !ignore } diff --git a/distribution.go b/distribution.go index d45d815..6463d24 100644 --- a/distribution.go +++ b/distribution.go @@ -93,10 +93,12 @@ func distributedFetch[V, T any](c *Client[T], key string, fetchFn FetchFn[V]) Fe // Check if the record is fresh enough to not need a refresh. if !c.distributedEarlyRefreshes || c.clock.Since(record.CreatedAt) < c.distributedRefreshAfterDuration { if record.IsMissingRecord { + c.reportDistributedMissingRecord() return record.Value, ErrNotFound } return record.Value, nil } + c.reportDistributedRefresh() stale, hasStale = record.Value, true } @@ -179,15 +181,20 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet if !c.distributedEarlyRefreshes || c.clock.Since(record.CreatedAt) < c.distributedRefreshAfterDuration { // We never wan't to return missing records. if !record.IsMissingRecord { + c.reportDistributedMissingRecord() fresh[id] = record.Value } continue } idsToRefresh = append(idsToRefresh, id) + c.reportDistributedRefresh() + // We never wan't to return missing records. if !record.IsMissingRecord { stale[id] = record.Value + } else { + c.reportDistributedMissingRecord() } } diff --git a/metrics.go b/metrics.go index 7f85b72..ba9f11f 100644 --- a/metrics.go +++ b/metrics.go @@ -5,6 +5,11 @@ type MetricsRecorder interface { CacheHit() // CacheMiss is called for every key that results in a cache miss. CacheMiss() + // Refresh is called when a get operation results in a refresh. + Refresh() + // MissingRecord is called every time the cache is asked to + // lookup a key which has been marked as missing. + MissingRecord() // ForcedEviction is called when the cache reaches its capacity, and has to // evict keys in order to write a new one. ForcedEviction() @@ -24,6 +29,12 @@ type DistributedMetricsRecorder interface { DistributedCacheHit() // DistributedCacheHit is called for every key that results in a cache miss. DistributedCacheMiss() + // DistributedRefresh is called when we retrieve a record from + // the distributed storage that should be refreshed. + DistributedRefresh() + // DistributetedMissingRecord is called when we retrieve a record from the + // distributed storage that has been marked as a missing record. + DistributedMissingRecord() // DistributedFallback is called when you are using a distributed storage // with early refreshes, and the call for a value was supposed to refresh it, // but the call failed. When that happens, the cache fallbacks to the latest @@ -39,6 +50,10 @@ func (d *distributedMetricsRecorder) DistributedCacheHit() {} func (d *distributedMetricsRecorder) DistributedCacheMiss() {} +func (d *distributedMetricsRecorder) DistributedRefresh() {} + +func (d *distributedMetricsRecorder) DistributedMissingRecord() {} + func (d *distributedMetricsRecorder) DistributedFallback() {} func (s *shard[T]) reportForcedEviction() { @@ -56,10 +71,19 @@ func (s *shard[T]) reportEntriesEvicted(n int) { } // reportCacheHits is used to report cache hits and misses to the metrics recorder. -func (c *Client[T]) reportCacheHits(cacheHit bool) { +func (c *Client[T]) reportCacheHits(cacheHit, missingRecord, refresh bool) { if c.metricsRecorder == nil { return } + + if missingRecord { + c.metricsRecorder.MissingRecord() + } + + if refresh { + c.metricsRecorder.Refresh() + } + if !cacheHit { c.metricsRecorder.CacheMiss() return @@ -92,6 +116,20 @@ func (c *Client[T]) reportDistributedCacheHit(cacheHit bool) { c.metricsRecorder.DistributedCacheHit() } +func (c *Client[T]) reportDistributedRefresh() { + if c.metricsRecorder == nil { + return + } + c.metricsRecorder.DistributedRefresh() +} + +func (c *Client[T]) reportDistributedMissingRecord() { + if c.metricsRecorder == nil { + return + } + c.metricsRecorder.DistributedMissingRecord() +} + func (c *Client[T]) reportDistributedStaleFallback() { if c.metricsRecorder == nil { return diff --git a/sturdyc_test.go b/sturdyc_test.go index 409b5e9..781f323 100644 --- a/sturdyc_test.go +++ b/sturdyc_test.go @@ -26,6 +26,8 @@ type TestMetricsRecorder struct { sync.Mutex cacheHits int cacheMisses int + refreshes int + missingRecords int evictions int forcedEvictions int evictedEntries int @@ -52,6 +54,18 @@ func (r *TestMetricsRecorder) CacheMiss() { r.cacheMisses++ } +func (r *TestMetricsRecorder) Refresh() { + r.Lock() + defer r.Unlock() + r.refreshes++ +} + +func (r *TestMetricsRecorder) MissingRecord() { + r.Lock() + defer r.Unlock() + r.missingRecords++ +} + func (r *TestMetricsRecorder) ObserveCacheSize(_ func() int) {} func (r *TestMetricsRecorder) CacheBatchRefreshSize(n int) {