Repository navigation
perf(neighbors): rebuild graphs outside request and pointer locks - #541
Conversation
|
Coordinator note for the review: the first CI run failed in Playwright shard 1 at This PR moves neighbour-graph rebuilds into the background, so please check whether those rebuilds hold a read transaction on the DB long enough to block an external writer. That would happen if the fixture DB is not in WAL mode, or if a rebuild keeps a statement or transaction open across the whole build. Also check whether the test is simply flaky on master. I have started a rerun of the failed jobs. |
Review — CS-Macmini PR#541 — head 5196586Dom: APPROVE med nits Independent, read-only review. The change does what it says: the expensive store scan moves off the request goroutine and out of the reader's mutex, the swap is a genuine atomic publish of a finished graph, and initial readers coalesce. The perf claim holds and is in fact understated — see the request-latency measurement below. The nits are two confirmed test gaps, one widened config knob, and a red CI run the body does not disclose. Findings
F1 detail. Before:
The default (300 s) equals the old TTL, so default behaviour is unchanged, and the new comment does say "higher = an older graph between ticks". That is why this is a nit and not a blocker. Suggestion: clamp the graph rebuild to at most once per F2 detail. Mutant M2 — swap the two statements so the response is built before the rebuild: resp := s.buildDefaultNeighborGraphResponse()
s.rebuildNeighborGraph()
F3 detail. Mutant M3 — publish without the pointer mutex in g := s.buildNeighborGraph()
s.neighborGraph = g
F5 detail. Failing jobs: An Answers to the review points#495 / #412 lock rule. No half-built graph. Verified [K]: No stampede. Perf claim — reproduced and extended.
On master exactly one request absorbs the entire build. On head the worst request-side wait drops by roughly three orders of magnitude while the tick cost is unchanged (the ~50 ms delta is contention from my own 26 M-iteration sampling loop). This is stronger evidence than the getter microbenchmark, and it is the number the PR should quote.
Overlap with #540 (bridge-TX filter), head
Acceptance criteria / red before green. There is no issue, so I used the four behaviours the body claims. With the new test file kept and only the three production files reverted to master, 5 of 6 new tests fail [T] — No behaviour change beyond the purpose. The persisted Tests and mutantsAll on the merged tree ( Legend: [T] measured by me, [A] author's claim, [K] code reading.
E2E against a local Go server on port 13802 with
Mutants (all mine, 4 run):
Policy checks [T]: commit author and committer are Not verified
|
neighbors: follow-ups from #541 (graph-age clamp, cache/publish tests, atomic.Pointer)
Summary
Keep expensive neighbor graph construction out of requests once a graph exists.
Server.neighborGraph, even past the old TTL, while the background recomputer builds a replacement.The distinct persisted
PacketStore.graph/ 60-second recomputer is untouched. Base:8798f74eeec124f21d0e82a8d64b93b31d7facc0. No schema, API, dependency or workflow change. Only comments for the existing recompute-interval setting are updated; no new knob or default is introduced.Deterministic verification / independent review
-race -count=3passes; parent independently ran the six tests twice under-race(1.531 s), with non-empty execution and no races.Measured request-side cost
The reproducible benchmark deliberately presents a stale graph with 30,000 packets. Baseline getter median (three rounds of three iterations): 16.832 ms, approximately 13.07 MB and 280,032 allocations per operation. The new getter is constant-time with 0 B / 0 allocations; a longer 100,000-iteration control measured 7.493–12.11 ns/op (median 8.789).
This measures the snapshot getter, not endpoint latency, production throughput or eliminated work. The graph scan, CPU and allocations are moved to the background and still paid on each tick. Lower configured intervals cost more CPU; higher intervals allow older results. No live-instance performance claim is made.
Limitations
Serialization is not perfect startup coalescing: if an HTTP request builds first and the initial recompute follows, two builds can occur sequentially. They cannot overlap, and existing readers do not wait for the second build. No new panic-recovery policy or destructive historical rebuild is added.
Local validation is macOS arm64 / Go 1.26.3. Linux PR CI is separate. Draft only, auto-merge off; no ready, merge, publish, deployment or staging/production contact.
Post-push Chromium confirmation on the published commit: Nodes list/detail and real neighbor/Paths API contracts pass on a disposable local fixture. Initial PR CI run:
40067027978; no rerun was requested.Source credit / discovery
Adapted from the original contribution by
efiten: