One call, five shapes
"How often, how recently, first time this window?" shows up under a lot of names. Two of them are fully built packages on this site; the rest are the same three lines of code, aimed at a different key.
| Problem | Key | "Frequent lately?" drives | Status |
|---|---|---|---|
| Cache admission | object key | admit vs. reject | Built — admission |
| Rate limiting | client / tenant | throttle level | Built — SketchProxy |
| Hot-key detection | row / IP / URL | alarm / mitigation | Pattern below |
| Fraud velocity | card / device | risk score input | Pattern below |
| Telemetry sampling | log template | suppression probability | Studied — fleet-sampling |
A database, CDN, or packet filter needs to spot keys that suddenly go hot — trending content, an abusive IP, a celebrity row — on the hottest path in the system. No package ships this; it's the same Observe call SketchProxy makes, aimed at a different key.
trend := epochsketch.New(epochsketch.Config{ NumBuckets: 1 << 18, TickDuration: 10 * time.Second, }) func isTrending(key string) bool { estimate, first := trend.Observe(key) return !first && estimate >= 500 // climbing fast, not a one-off }
"Has this card or device been attempting things unusually often in the last few minutes?" Millions of entities, most seen once and never again — the churn case the fixed-memory guarantee exists for.
velocity := epochsketch.New(epochsketch.Config{ NumBuckets: 1 << 20, TickDuration: time.Minute, }) func isSuspicious(cardFingerprint string) bool { estimate, _ := velocity.Observe(cardFingerprint) return estimate > 20 // attempts this minute }
Note the different TickDuration in each example above — the decay window is the actual design decision here, not the threshold. A minute-long tick forgets a burst that ended five minutes ago; a second-long one wouldn't.
Every Sketch exposes Stats() — self-metrics for whatever's scraping your process, no separate instrumentation.
s := sk.Stats() fmt.Printf("evictions=%d chips=%d scrubbed=%d\n", s.Evictions, s.Chips, s.ScrubbedZombies)
| Field | What it counts |
|---|---|
| CASRetriesExhausted | Writes dropped after 3 lost CAS races — fail-open, not corrupted |
| Evictions | Challengers that took over a fully-chipped slot |
| Chips | Challengers that weakened a victim by one tally, didn't take the slot |
| BucketOverflowPressure | Eviction attempts a challenger lost |
| ScrubbedZombies | Slots opportunistically cleared past the evaporation horizon |
For the mechanism behind all of this — the word layout, the eviction contest, why decay needs no background process — see the design page. For the normative spec, see the docs.