adaptive
API
adaptive
packageAPI reference for the adaptive
package.
Imports
(7)
S
struct
SlidingWindow
SlidingWindow is a TTLStrategy that detects hot keys using a
sliding time window and adjusts TTL accordingly.
v1/cache/adaptive/sliding_window.go:14-27
type SlidingWindow struct
Methods
Record
Method
Record implements cache.TTLStrategy.Record.
Parameters
key
string
func (*SlidingWindow) Record(key string)
{
now := time.Now()
s.mu.Lock()
defer s.mu.Unlock()
timestamps := append(s.hits[key], now)
cutoff := now.Add(-s.Window)
i := 0
for _, t := range timestamps {
if t.After(cutoff) {
timestamps[i] = t
i++
}
}
s.hits[key] = timestamps[:i]
}
TTL
Method
TTL implements cache.TTLStrategy.TTL.
Parameters
key
string
Returns
func (*SlidingWindow) TTL(key string) time.Duration
{
now := time.Now()
s.mu.Lock()
defer s.mu.Unlock()
timestamps := s.hits[key]
cutoff := now.Add(-s.Window)
i := 0
for _, t := range timestamps {
if t.After(cutoff) {
timestamps[i] = t
i++
}
}
timestamps = timestamps[:i]
s.hits[key] = timestamps
ttl := s.ColdTTL
hot := false
if len(timestamps) >= s.Threshold {
ttl = s.HotTTL
hot = true
}
if last, ok := s.lastTTL[key]; !ok || last != ttl {
slog.Info("adaptive ttl adjusted", "key", key, "ttl", ttl)
if s.adjustCounter != nil {
s.adjustCounter.Inc()
}
s.lastTTL[key] = ttl
}
if hot && !s.hotKeys[key] {
if s.hotGauge != nil {
s.hotGauge.Inc()
}
s.hotKeys[key] = true
} else if !hot && s.hotKeys[key] {
if s.hotGauge != nil {
s.hotGauge.Dec()
}
delete(s.hotKeys, key)
}
return ttl
}
Fields
| Name | Type | Description |
|---|---|---|
| Window | time.Duration | |
| Threshold | int | |
| ColdTTL | time.Duration | |
| HotTTL | time.Duration | |
| mu | sync.Mutex | |
| hits | map[string][]time.Time | |
| lastTTL | map[string]time.Duration | |
| hotKeys | map[string]bool | |
| adjustCounter | prometheus.Counter | |
| hotGauge | prometheus.Gauge |
F
function
NewSlidingWindow
NewSlidingWindow creates a new SlidingWindow strategy.
reg may be nil to disable metrics registration.
Parameters
window
threshold
int
coldTTL
hotTTL
Returns
v1/cache/adaptive/sliding_window.go:31-53
func NewSlidingWindow(window time.Duration, threshold int, coldTTL, hotTTL time.Duration, reg prometheus.Registerer) *SlidingWindow
{
sw := &SlidingWindow{
Window: window,
Threshold: threshold,
ColdTTL: coldTTL,
HotTTL: hotTTL,
hits: make(map[string][]time.Time),
lastTTL: make(map[string]time.Duration),
hotKeys: make(map[string]bool),
}
if reg != nil {
sw.adjustCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "warp_adaptive_ttl_adjustments_total",
Help: "Total number of TTL adjustments by the adaptive strategy",
})
sw.hotGauge = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "warp_adaptive_hot_keys",
Help: "Number of keys currently considered hot",
})
reg.MustRegister(sw.adjustCounter, sw.hotGauge)
}
return sw
}
F
function
TestSlidingWindowAdjustsTTL
Parameters
t
v1/cache/adaptive/sliding_window_test.go:11-50
func TestSlidingWindowAdjustsTTL(t *testing.T)
{
reg := prometheus.NewRegistry()
sw := NewSlidingWindow(50*time.Millisecond, 2, time.Second, 5*time.Second, reg)
key := "k"
sw.Record(key)
if ttl := sw.TTL(key); ttl != time.Second {
t.Fatalf("expected cold ttl, got %v", ttl)
}
if c := testutil.ToFloat64(sw.adjustCounter); c != 1 {
t.Fatalf("expected 1 adjustment, got %v", c)
}
if g := testutil.ToFloat64(sw.hotGauge); g != 0 {
t.Fatalf("expected 0 hot keys, got %v", g)
}
sw.Record(key)
sw.Record(key)
if ttl := sw.TTL(key); ttl != 5*time.Second {
t.Fatalf("expected hot ttl, got %v", ttl)
}
if c := testutil.ToFloat64(sw.adjustCounter); c != 2 {
t.Fatalf("expected 2 adjustments, got %v", c)
}
if g := testutil.ToFloat64(sw.hotGauge); g != 1 {
t.Fatalf("expected 1 hot key, got %v", g)
}
time.Sleep(80 * time.Millisecond)
sw.Record(key)
if ttl := sw.TTL(key); ttl != time.Second {
t.Fatalf("expected cold ttl after window, got %v", ttl)
}
if c := testutil.ToFloat64(sw.adjustCounter); c != 3 {
t.Fatalf("expected 3 adjustments, got %v", c)
}
if g := testutil.ToFloat64(sw.hotGauge); g != 0 {
t.Fatalf("expected 0 hot keys after cooling, got %v", g)
}
}