Observability
Two behaviours for knowing what your cache is doing: eviction callbacks and statistics.
onEviction
Register a callback that fires when entries are automatically evicted by other behaviours
(TTL expiry, LRU eviction, memory pressure). Does NOT fire on explicit remove() calls.
val cache = fastCache<String, DatabaseConnection> {
expireAfterAccess(5.minutes)
onEviction { key, value ->
value.close() // clean up the connection
logger.info("Evicted: $key")
}
} Use cases:
- Resource cleanup — close connections, release file handles, cancel timers
- Logging — track what the cache is dropping and when
- Metrics — feed eviction events into Prometheus, Micrometer, or custom dashboards
What counts as eviction?
| Operation | Fires onEviction? |
|---|---|
TTL expires (expireAfterAccess / expireAfterWrite) | Yes |
LRU eviction (maxEntries / maxMemoryUsage) | Yes |
Hard TTL eviction (refreshAfterWrite with hardTtl) | Yes |
Explicit cache.remove(key) | No |
cache.clear() | No |
statistics
Track hits, misses, puts, and evictions. The statistics() builder method returns a
StatisticsBehaviour handle — hold onto it to call snapshot() later:
import io.peekandpoke.ultra.cache.FastCache
import io.peekandpoke.ultra.cache.fastCache
lateinit var stats: FastCache.StatisticsBehaviour<String, String>
val cache = fastCache<String, String> {
expireAfterAccess(10.minutes)
maxEntries(1000)
stats = statistics()
}
// Use the cache...
cache.put("a", "1")
cache.getOrPut("b") { "2" }
cache.get("a") // hit
cache.get("missing") // miss
// Take a snapshot
val snapshot = stats.snapshot()
println(snapshot.hitCount) // 1
println(snapshot.missCount) // 1
println(snapshot.putCount) // 2
println(snapshot.evictionCount) // 0
println(snapshot.requestCount) // 2 (hits + misses)
println(snapshot.hitRate) // 0.5 CacheStats data class
The snapshot() method returns an immutable CacheStats instance. Old snapshots
are not affected by subsequent cache operations.
| Property | Type | Description |
|---|---|---|
hitCount | Long | Successful get() / has() lookups |
missCount | Long | Failed get() / has() lookups |
putCount | Long | Total put() calls (including getOrPut misses) |
evictionCount | Long | Entries removed by behaviours (not explicit remove()) |
requestCount | Long | hitCount + missCount |
hitRate | Double | hitCount / requestCount (NaN if no requests) |
How statistics counts
Statistics processes every action in every batch, not just the last-per-key. If a key is read
5 times in one batch, that's 5 hits. Evictions are counted via the eviction listener — the same
mechanism used by onEviction.
Combining both
lateinit var stats: FastCache.StatisticsBehaviour<String, String>
val cache = fastCache<String, String> {
expireAfterWrite(10.minutes)
maxEntries(5000)
onEviction { key, _ ->
logger.debug("Evicted: $key")
}
stats = statistics()
}
// Periodic monitoring
fun reportCacheHealth() {
val s = stats.snapshot()
metrics.gauge("cache.hit_rate", s.hitRate)
metrics.counter("cache.evictions", s.evictionCount)
}