perf: use cached ParsedDecoded() instead of repeated json.Unmarshal (#1871)

## Problem

`StoreTx.ParsedDecoded()` already caches the result of `json.Unmarshal`
on first call via `sync.Once`. However, 13 call sites in `store.go` and
1 in `routes.go` were independently unmarshaling `DecodedJSON` into
local `map[string]interface{}` variables on every access, completely
ignoring the cache.

## Impact

For a store with 50k+ transmissions, each analytics endpoint that
iterates all packets re-parses 50k JSON strings per request. With
multiple endpoints, this means hundreds of thousands of redundant
`json.Unmarshal` calls per page load, each allocating new maps and
slices.

## Fix

Replace each `var d map[string]interface{};
json.Unmarshal([]byte(tx.DecodedJSON), &d)` with `d :=
tx.ParsedDecoded()`, which returns the cached parse result (parsed once,
reused forever).

### Call sites changed (14 total):
- `untrackAdvertPubkey` — advert PK extraction during eviction
- Ingestion path — payload field for API responses
- `evictStaleInternal` — node cleanup during eviction
- `GetAnalyticsTopology` — node PK extraction
- `GetAnalyticsHashCollisions` — advert PK extraction
- `GetAnalyticsDistance` — region node PK building
- `resolveAreaNodes` — node PK extraction
- `GetRecentPackets` — payload field for API response
- `routes.go` byType grouping — type field extraction

### Left unchanged (3 sites):
Three call sites that unmarshal into typed structs (`grpDec`,
`decodedMsg`, `decodedGrp`) cannot use `ParsedDecoded()` since they need
specific struct types.

## Testing
- `go build` passes
- No behavior change — same data, same logic, just avoids redundant
parses

---------

Co-authored-by: Joel Claw <358739783+Joel-Claw@users.noreply.github.com>
This commit is contained in:
Joel ClawandJoel Claw authored and GitHub committed 2026-09-02 09:10:56 +02:00
1 parent b38536911a
commit f49e3fcc26
2 files changed
+29 -24

No files matched your search

+1 -2
View File
@@ -2954,8 +2954,7 @@ func (s *Server) handleAudioLabBuckets(w http.ResponseWriter, r *http.Request) {
}
typeName := "UNKNOWN"
if tx.DecodedJSON != "" {
var d map[string]interface{}
if err := json.Unmarshal([]byte(tx.DecodedJSON), &d); err == nil {
if d := tx.ParsedDecoded(); d != nil {
if t, ok := d["type"].(string); ok && t != "" {
typeName = t
}
+28 -22
View File
@@ -1798,8 +1798,8 @@ func (s *PacketStore) untrackAdvertPubkey(tx *StoreTx) {
if tx.PayloadType == nil || *tx.PayloadType != PayloadADVERT || tx.DecodedJSON == "" {
return
}
var d map[string]interface{}
if json.Unmarshal([]byte(tx.DecodedJSON), &d) != nil {
d := tx.ParsedDecoded()
if d == nil {
return
}
pk := ""
@@ -2916,9 +2916,14 @@ func (s *PacketStore) IngestNewFromDB(sinceID, limit int) ([]map[string]interfac
},
}
if tx.DecodedJSON != "" {
var payload map[string]interface{}
if json.Unmarshal([]byte(tx.DecodedJSON), &payload) == nil {
decoded["payload"] = payload
if payload := tx.ParsedDecoded(); payload != nil {
// Copy the cached map so broadcast consumers cannot mutate
// the shared cache or cause a data race (#1871 review).
cp := make(map[string]interface{}, len(payload))
for k, v := range payload {
cp[k] = v
}
decoded["payload"] = cp
}
}
// For TRACE packets, decode the full packet to include path.hopsCompleted
@@ -3189,9 +3194,14 @@ func (s *PacketStore) IngestNewObservations(sinceObsID, limit int) []map[string]
},
}
if tx.DecodedJSON != "" {
var payload map[string]interface{}
if json.Unmarshal([]byte(tx.DecodedJSON), &payload) == nil {
decoded["payload"] = payload
if payload := tx.ParsedDecoded(); payload != nil {
// Copy the cached map so broadcast consumers cannot mutate
// the shared cache or cause a data race (#1871 review).
cp := make(map[string]interface{}, len(payload))
for k, v := range payload {
cp[k] = v
}
decoded["payload"] = cp
}
}
// For TRACE packets, decode the full packet to include path.hopsCompleted
@@ -3737,8 +3747,8 @@ func (s *PacketStore) computeNodeHomeRegions() map[string]string {
continue
}
var d map[string]interface{}
if json.Unmarshal([]byte(tx.DecodedJSON), &d) != nil {
d := tx.ParsedDecoded()
if d == nil {
continue
}
pk, _ := d["pubKey"].(string)
@@ -4651,8 +4661,7 @@ func (s *PacketStore) evictStaleInternal(rpBatch map[int][]string) int {
// Must mirror indexByNode: process decoded JSON fields AND resolved_path pubkeys.
evictedFromNode := make(map[string]bool)
if tx.DecodedJSON != "" {
var decoded map[string]interface{}
if json.Unmarshal([]byte(tx.DecodedJSON), &decoded) == nil {
if decoded := tx.ParsedDecoded(); decoded != nil {
for _, field := range []string{"pubKey", "destPubKey", "srcPubKey"} {
if v, ok := decoded[field].(string); ok && v != "" {
if hashes, ok := s.nodeHashes[v]; ok {
@@ -5009,8 +5018,7 @@ func computeDistancesForTx(tx *StoreTx, nodeByPk map[string]*nodeInfo, repeaterS
var senderNode *nodeInfo
if tx.DecodedJSON != "" {
var dec map[string]interface{}
if json.Unmarshal([]byte(tx.DecodedJSON), &dec) == nil {
if dec := tx.ParsedDecoded(); dec != nil {
if pk, ok := dec["pubKey"].(string); ok && pk != "" {
senderNode = nodeByPk[pk]
}
@@ -8030,8 +8038,7 @@ func (s *PacketStore) computeAnalyticsHashSizes(region, area string) map[string]
var advertPK, advertName string
var advertParsed bool
if tx.PayloadType != nil && *tx.PayloadType == PayloadADVERT && tx.DecodedJSON != "" {
var d map[string]interface{}
if json.Unmarshal([]byte(tx.DecodedJSON), &d) == nil {
if d := tx.ParsedDecoded(); d != nil {
if v, ok := d["pubKey"].(string); ok {
advertPK = v
} else if v, ok := d["public_key"].(string); ok {
@@ -8342,8 +8349,7 @@ func (s *PacketStore) computeHashCollisions(region, area string) map[string]inte
}
// Collect node public keys from advert packets
if tx.DecodedJSON != "" {
var d map[string]interface{}
if json.Unmarshal([]byte(tx.DecodedJSON), &d) == nil {
if d := tx.ParsedDecoded(); d != nil {
if pk, ok := d["pubKey"].(string); ok && pk != "" {
regionNodePKs[pk] = true
}
@@ -8706,8 +8712,8 @@ func (s *PacketStore) computeNodeHashSizeInfo() map[string]*hashSizeNodeInfo {
}
hs := int((pathByte>>6)&0x3) + 1
var d map[string]interface{}
if json.Unmarshal([]byte(tx.DecodedJSON), &d) != nil {
d := tx.ParsedDecoded()
if d == nil {
continue
}
pk := ""
@@ -9645,8 +9651,8 @@ func (s *PacketStore) GetNodeAnalytics(pubkey string, days int) (*NodeAnalyticsR
if p.DecodedJSON == "" {
continue
}
var decoded map[string]interface{}
if json.Unmarshal([]byte(p.DecodedJSON), &decoded) != nil {
decoded := p.ParsedDecoded()
if decoded == nil {
continue
}
type candidate struct{ key, name string }