Files
meshcore-analyzer/cmd/server/repeater_enrich_bulk_test.go
T
efiten 981664528e perf(server): serve stale repeater enrich cache instead of inline rebuild (#1272) (#1436)
## Summary

- Removes the TTL-based inline rebuild from `GetRepeaterRelayInfoMap`
and `GetRepeaterUsefulnessScoreMap`
- When the cache is non-nil it is returned immediately, regardless of
age — no more 700ms on-request recompute
- Inline compute is retained only as a nil-cache guard (edge case: tests
without a running recomputer)
- Fixes the stale `// 15s-TTL gate` comment in
`recomputeRepeaterEnrichmentSafe`

**Root cause:** `computeRepeaterRelayInfoMap` runs inline when the TTL
expires, taking ~700ms on a busy instance.
`StartRepeaterEnrichmentRecomputer` (introduced in #1262) already keeps
the cache warm via synchronous prewarm at startup + 5-min ticks, making
the inline path dead code that fires only when the TTL is shorter than
the recomputer interval (e.g. custom `analytics.defaultIntervalSeconds >
600`).

## Test plan

- [ ] `TestGetRepeaterRelayInfoMap_ServesStaleOnTTLExpiry` — regression
guard: stale sentinel is returned without recompute
- [ ] `TestGetRepeaterUsefulnessScoreMap_ServesStaleOnTTLExpiry` — same
for usefulness score map
- [ ] `TestGetRepeaterRelayInfoMap_BuildsWhenNil` — nil-cache fallback
still works
- [ ] Full `-short` suite passes (`go test -short ./...`)

Closes #1272

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 15:01:58 -07:00

90 lines
3.0 KiB
Go

package main
import (
"sync"
"testing"
"time"
)
// TestGetRepeaterRelayInfoMap_ServesStaleOnTTLExpiry is a regression guard
// for issue #1272.
//
// Background: GetRepeaterRelayInfoMap used to rebuild the cache inline
// whenever the TTL expired, causing ~700ms latency spikes on /api/nodes.
// The recomputer (StartRepeaterEnrichmentRecomputer) runs every 5 min and
// already keeps the cache warm; there is no reason to rebuild on-request.
//
// This test verifies that a populated cache is ALWAYS returned as-is,
// even when its timestamp is ancient (simulating TTL expiry under the old
// code). The stale sentinel value proves no inline recompute occurred.
func TestGetRepeaterRelayInfoMap_ServesStaleOnTTLExpiry(t *testing.T) {
store := &PacketStore{
byPathHop: make(map[string][]*StoreTx),
}
// Pre-populate the cache with a sentinel entry that would NOT be
// produced by computeRepeaterRelayInfoMap on the empty byPathHop.
stale := map[string]RepeaterRelayInfo{
"sentinel": {RelayCount24h: 9999},
}
store.repeaterRelayAt = time.Now().Add(-24 * time.Hour) // well past any TTL
store.repeaterRelayCache = stale
store.repeaterRelayCacheWin = 24
got := store.GetRepeaterRelayInfoMap(24)
if got["sentinel"].RelayCount24h != 9999 {
t.Fatalf("stale cache not served: sentinel missing or overwritten (RelayCount24h=%d)", got["sentinel"].RelayCount24h)
}
}
// TestGetRepeaterUsefulnessScoreMap_ServesStaleOnTTLExpiry mirrors
// TestGetRepeaterRelayInfoMap_ServesStaleOnTTLExpiry for the usefulness
// score map (same fix, same root cause).
func TestGetRepeaterUsefulnessScoreMap_ServesStaleOnTTLExpiry(t *testing.T) {
store := &PacketStore{
byPathHop: make(map[string][]*StoreTx),
byPayloadType: make(map[int][]*StoreTx),
}
stale := map[string]float64{"sentinel": 0.42}
store.repeaterUsefulAt = time.Now().Add(-24 * time.Hour)
store.repeaterUsefulCache = stale
got := store.GetRepeaterUsefulnessScoreMap()
if got["sentinel"] != 0.42 {
t.Fatalf("stale cache not served: sentinel missing or overwritten (score=%v)", got["sentinel"])
}
}
// TestGetRepeaterRelayInfoMap_BuildsWhenNil verifies that when the cache
// is nil (before the recomputer's first prewarm), GetRepeaterRelayInfoMap
// computes inline and caches the result for subsequent callers.
func TestGetRepeaterRelayInfoMap_BuildsWhenNil(t *testing.T) {
pt2 := 2
now := time.Now().UTC()
tx := &StoreTx{
ID: 1,
Hash: "abc",
FirstSeen: now.Add(-10 * time.Minute).Format(time.RFC3339Nano),
PayloadType: &pt2,
}
store := &PacketStore{
byPathHop: map[string][]*StoreTx{"aabbcc": {tx}},
byPayloadType: map[int][]*StoreTx{pt2: {tx}},
mu: sync.RWMutex{},
}
got := store.GetRepeaterRelayInfoMap(24)
if _, ok := got["aabbcc"]; !ok {
t.Fatal("inline compute did not produce entry for seeded hop key")
}
// Second call must return the cached result, not a fresh recompute.
got2 := store.GetRepeaterRelayInfoMap(24)
if got2["aabbcc"].RelayCount24h != got["aabbcc"].RelayCount24h {
t.Fatal("second call returned different map — cache not installed")
}
}