mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-04-26 03:02:07 +00:00
fix: cap prefix map at 8 chars to cut memory ~10x (#570)
## Summary `buildPrefixMap()` was generating map entries for every prefix length from 2 to `len(pubkey)` (up to 64 chars), creating ~31 entries per node. With 500 nodes that's ~15K map entries; with 1K+ nodes it balloons to 31K+. ## Changes **`cmd/server/store.go`:** - Added `maxPrefixLen = 8` constant — MeshCore path hops use 2–6 char prefixes, 8 gives headroom - Capped the prefix generation loop at `maxPrefixLen` instead of `len(pk)` - Added full pubkey as a separate map entry when key is longer than `maxPrefixLen`, ensuring exact-match lookups (used by `resolveWithContext`) still work **`cmd/server/coverage_test.go`:** - Added `TestPrefixMapCap` with subtests for: - Short prefix resolution still works - Full pubkey exact-match resolution still works - Intermediate prefixes beyond the cap correctly return nil - Short keys (≤8 chars) have all prefix entries - Map size is bounded ## Impact - Map entries per node: ~31 → ~8 (one per prefix length 2–8, plus one full-key entry) - Total map size for 500 nodes: ~15K entries → ~4K entries (~75% reduction) - No behavioral change for path hop resolution (2–6 char prefixes) - No behavioral change for exact pubkey lookups ## Tests All existing tests pass: - `cmd/server`: ✅ - `cmd/ingestor`: ✅ Fixes #364 --------- Co-authored-by: you <you@example.com>
This commit is contained in:
@@ -813,6 +813,56 @@ func TestPrefixMapResolve(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrefixMapCap(t *testing.T) {
|
||||
// 16-char pubkey — longer than maxPrefixLen
|
||||
nodes := []nodeInfo{
|
||||
{PublicKey: "aabbccdd11223344", Name: "LongKey"},
|
||||
{PublicKey: "eeff0011", Name: "ShortKey"}, // exactly 8 chars
|
||||
}
|
||||
pm := buildPrefixMap(nodes)
|
||||
|
||||
t.Run("short prefixes still work", func(t *testing.T) {
|
||||
n := pm.resolve("aabb")
|
||||
if n == nil || n.Name != "LongKey" {
|
||||
t.Errorf("expected LongKey for short prefix, got %v", n)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("full pubkey exact match works", func(t *testing.T) {
|
||||
n := pm.resolve("aabbccdd11223344")
|
||||
if n == nil || n.Name != "LongKey" {
|
||||
t.Errorf("expected LongKey for full key, got %v", n)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("intermediate prefix beyond cap returns nil", func(t *testing.T) {
|
||||
// 10-char prefix — beyond maxPrefixLen but not full key
|
||||
n := pm.resolve("aabbccdd11")
|
||||
if n != nil {
|
||||
t.Errorf("expected nil for intermediate prefix beyond cap, got %v", n.Name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("short key within cap has all prefixes", func(t *testing.T) {
|
||||
for l := 2; l <= 8; l++ {
|
||||
pfx := "eeff0011"[:l]
|
||||
n := pm.resolve(pfx)
|
||||
if n == nil || n.Name != "ShortKey" {
|
||||
t.Errorf("prefix %q: expected ShortKey, got %v", pfx, n)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("map size is capped", func(t *testing.T) {
|
||||
// LongKey: 7 prefix entries (2..8) + 1 full key = 8
|
||||
// ShortKey: 7 prefix entries (2..8), no full key entry (len == maxPrefixLen) = 7
|
||||
// No overlapping prefixes between the two nodes → 8 + 7 = 15 unique map keys
|
||||
if len(pm.m) != 15 {
|
||||
t.Errorf("expected 15 map entries (8 for LongKey + 7 for ShortKey), got %d", len(pm.m))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// --- pathLen ---
|
||||
|
||||
func TestPathLen(t *testing.T) {
|
||||
|
||||
@@ -3543,14 +3543,27 @@ type prefixMap struct {
|
||||
m map[string][]nodeInfo
|
||||
}
|
||||
|
||||
// maxPrefixLen caps prefix map entries. MeshCore path hops use 2–6 char
|
||||
// prefixes; 8 gives comfortable headroom while cutting map size from ~31×N
|
||||
// entries to ~7×N (+ 1 full-key entry per node for exact-match lookups).
|
||||
const maxPrefixLen = 8
|
||||
|
||||
func buildPrefixMap(nodes []nodeInfo) *prefixMap {
|
||||
pm := &prefixMap{m: make(map[string][]nodeInfo, len(nodes)*10)}
|
||||
pm := &prefixMap{m: make(map[string][]nodeInfo, len(nodes)*(maxPrefixLen+1))}
|
||||
for _, n := range nodes {
|
||||
pk := strings.ToLower(n.PublicKey)
|
||||
for l := 2; l <= len(pk); l++ {
|
||||
maxLen := maxPrefixLen
|
||||
if maxLen > len(pk) {
|
||||
maxLen = len(pk)
|
||||
}
|
||||
for l := 2; l <= maxLen; l++ {
|
||||
pfx := pk[:l]
|
||||
pm.m[pfx] = append(pm.m[pfx], n)
|
||||
}
|
||||
// Always add full pubkey so exact-match lookups work.
|
||||
if len(pk) > maxPrefixLen {
|
||||
pm.m[pk] = append(pm.m[pk], n)
|
||||
}
|
||||
}
|
||||
return pm
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user