adaptive API

adaptive

package

API reference for the adaptive package.

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

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)
	}
}