Compare commits

...

1 Commits

Author SHA1 Message Date
you
7ec2b77ccd fix(#791): update memory-accounting to reflect eliminated spTxIndex
Remove perSubpathEntryBytes constant and all O(path²) subpath cost
calculations from estimateStoreTxBytes and estimateStoreTxBytesTypical.
Update comments to note that spTxIndex was eliminated in #791 and
singleton subpath entries are dropped from spIndex.
2026-04-19 00:04:21 +00:00
2 changed files with 7 additions and 28 deletions

View File

@@ -2322,24 +2322,12 @@ func TestSubpathTxIndexPopulated(t *testing.T) {
store := NewPacketStore(db, nil)
store.Load()
// spTxIndex must be populated alongside spIndex
if len(store.spTxIndex) == 0 {
t.Fatal("expected spTxIndex to be populated after Load()")
// spIndex must be populated after Load()
if len(store.spIndex) == 0 {
t.Fatal("expected spIndex to be populated after Load()")
}
// Every key in spIndex must also exist in spTxIndex with matching count
for key, count := range store.spIndex {
txs, ok := store.spTxIndex[key]
if !ok {
t.Errorf("spTxIndex missing key %q that exists in spIndex", key)
continue
}
if len(txs) != count {
t.Errorf("spTxIndex[%q] has %d txs, spIndex count is %d", key, len(txs), count)
}
}
// GetSubpathDetail should return correct match count via indexed lookup
// GetSubpathDetail should return correct match count via scan fallback
detail := store.GetSubpathDetail([]string{"eeff", "0011"})
if detail == nil {
t.Fatal("expected non-nil detail for existing subpath")

View File

@@ -2640,10 +2640,11 @@ func (s *PacketStore) buildDistanceIndex() {
// usage and independent of GC state.
//
// Issue #743: Previous estimates missed major per-packet allocations:
// - spTxIndex: O(path²) entries per tx (50-150MB at scale)
// - ResolvedPath on observations (~25MB at scale)
// - Per-tx maps: obsKeys, observerSet (~11MB at scale)
// - byPathHop index entries (20-40MB at scale)
// Note: spTxIndex was eliminated in #791 (saved ~280MB at 3.4M subpaths).
// Singleton subpath entries are also dropped from spIndex (#791, saves ~150MB).
const (
storeTxBaseBytes = 384 // StoreTx struct fields + map headers + sync.Once + string headers
storeObsBaseBytes = 192 // StoreObs struct fields + string headers
@@ -2657,15 +2658,12 @@ const (
// Per path hop: byPathHop index entry (pointer + map bucket)
perPathHopBytes = 50
// Per subpath entry in spTxIndex: string key + slice append + pointer
perSubpathEntryBytes = 40
// Per resolved path element on an observation
perResolvedPathElemBytes = 24 // *string pointer + string header + avg pubkey length
)
// estimateStoreTxBytes returns the estimated memory cost of a StoreTx (excluding observations).
// Includes per-tx maps (obsKeys, observerSet), byPathHop entries, and spTxIndex subpath entries.
// Includes per-tx maps (obsKeys, observerSet) and byPathHop entries.
func estimateStoreTxBytes(tx *StoreTx) int64 {
base := int64(storeTxBaseBytes)
base += int64(len(tx.RawHex) + len(tx.Hash) + len(tx.DecodedJSON) + len(tx.PathJSON))
@@ -2678,12 +2676,6 @@ func estimateStoreTxBytes(tx *StoreTx) int64 {
hops := int64(len(txGetParsedPath(tx)))
base += hops * perPathHopBytes
// spTxIndex: O(path²) subpath combinations
if hops > 1 {
subpaths := hops * (hops - 1) / 2
base += subpaths * perSubpathEntryBytes
}
return base
}
@@ -2697,7 +2689,6 @@ func estimateStoreTxBytesTypical(numObs int) int64 {
base += perTxMapsBytes
hops := int64(3)
base += hops * perPathHopBytes
base += (hops * (hops - 1) / 2) * perSubpathEntryBytes
// Add observation costs
obsBase := int64(storeObsBaseBytes) + 30 + 30 + 60 // observer ID + name + path
obsBase += int64(numIndexesPerObs * indexEntryBytes)