The peering handler kept every distinct AS path of the last 30 minutes in
memory (measured over a million entries) and every 30 seconds copied the
whole map and re-recorded every peering in the window, driving a large heap
sawtooth and near-continuous database writes.
Changes, all in internal/routewatch/peeringhandler.go:
processPeerings swaps the path map for a fresh empty one under the lock
instead of copying it, so each path is processed exactly once and the
memory is released every run. Stored results are unchanged because RecordPeering upserts last_seen.
A new distinct path is dropped once the map holds maxTrackedPaths
(500000) entries; the running drop count is logged with each run. There
is no per-handler metrics sink, so the run log is where it surfaces.
The 30-minute time prune and its ticker are removed; the swap makes them
dead code. The map mutex drops from RWMutex to Mutex since the read
path is gone.
Tests (internal/routewatch/peeringhandler_test.go): a run empties the
map; the same path seen before and after a run is recorded in both runs;
the cap drops and counts new paths while refreshing an already-tracked
path.
Disclosure: host make check cannot complete here because the host golangci-lint is built with Go 1.25 and the host Go toolchain is 1.26,
which makes it panic on any package in this module. The gate was run as the
repo's authoritative Docker build (script/cibuild), whose pinned lint
stage matches the toolchain; it is green.
Model: opus-4-8
Part of the memory work in https://git.eeqj.de/sneak/routewatch/issues/3;
implements https://git.eeqj.de/sneak/routewatch/issues/10.
The peering handler kept every distinct AS path of the last 30 minutes in
memory (measured over a million entries) and every 30 seconds copied the
whole map and re-recorded every peering in the window, driving a large heap
sawtooth and near-continuous database writes.
Changes, all in `internal/routewatch/peeringhandler.go`:
- `processPeerings` swaps the path map for a fresh empty one under the lock
instead of copying it, so each path is processed exactly once and the
memory is released every run. Stored results are unchanged because
`RecordPeering` upserts `last_seen`.
- A new distinct path is dropped once the map holds `maxTrackedPaths`
(500000) entries; the running drop count is logged with each run. There
is no per-handler metrics sink, so the run log is where it surfaces.
- The 30-minute time prune and its ticker are removed; the swap makes them
dead code. The map mutex drops from `RWMutex` to `Mutex` since the read
path is gone.
Tests (`internal/routewatch/peeringhandler_test.go`): a run empties the
map; the same path seen before and after a run is recorded in both runs;
the cap drops and counts new paths while refreshing an already-tracked
path.
Disclosure: host `make check` cannot complete here because the host
`golangci-lint` is built with Go 1.25 and the host Go toolchain is 1.26,
which makes it panic on any package in this module. The gate was run as the
repo's authoritative Docker build (`script/cibuild`), whose pinned lint
stage matches the toolchain; it is green.
Model: opus-4-8
processPeerings now takes the accumulated AS-path map under the lock and
replaces it with a fresh empty one, so each path is processed once and the
map never grows past a single 30-second interval's traffic. HandleMessage
stops adding new paths once maxTrackedPaths (500000) is reached and counts
the drops, which are logged with each run. The 30-minute prune and its
ticker are removed as dead code under the swap, and the map mutex drops
from RWMutex to Mutex since the read path is gone. RecordPeering already
upserts last_seen, so stored peerings are unchanged.
Model: opus-4-8
PASS — the map swap (no copy) under the lock, the maxTrackedPaths (500000) cap that drops and counts new paths, and removal of the 30-minute prune and its ticker all satisfy #10 and its definition of done; the three tests genuinely fail when the swap or the cap is broken, and the branch rebased onto the current next head is green.
Disclosure: host make check cannot run here (host golangci-lint panics on the Go 1.26 toolchain), so gating used the repo's authoritative Docker build with the lint and test stages forced to re-execute.
Model: opus-4-8
PASS — the map swap (no copy) under the lock, the `maxTrackedPaths` (500000) cap that drops and counts new paths, and removal of the 30-minute prune and its ticker all satisfy https://git.eeqj.de/sneak/routewatch/issues/10 and its definition of done; the three tests genuinely fail when the swap or the cap is broken, and the branch rebased onto the current `next` head is green.
Disclosure: host `make check` cannot run here (host `golangci-lint` panics on the Go 1.26 toolchain), so gating used the repo's authoritative Docker build with the lint and test stages forced to re-execute.
Model: opus-4-8
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of the memory work in #3;
implements #10.
The peering handler kept every distinct AS path of the last 30 minutes in
memory (measured over a million entries) and every 30 seconds copied the
whole map and re-recorded every peering in the window, driving a large heap
sawtooth and near-continuous database writes.
Changes, all in
internal/routewatch/peeringhandler.go:processPeeringsswaps the path map for a fresh empty one under the lockinstead of copying it, so each path is processed exactly once and the
memory is released every run. Stored results are unchanged because
RecordPeeringupsertslast_seen.maxTrackedPaths(500000) entries; the running drop count is logged with each run. There
is no per-handler metrics sink, so the run log is where it surfaces.
dead code. The map mutex drops from
RWMutextoMutexsince the readpath is gone.
Tests (
internal/routewatch/peeringhandler_test.go): a run empties themap; the same path seen before and after a run is recorded in both runs;
the cap drops and counts new paths while refreshing an already-tracked
path.
Disclosure: host
make checkcannot complete here because the hostgolangci-lintis built with Go 1.25 and the host Go toolchain is 1.26,which makes it panic on any package in this module. The gate was run as the
repo's authoritative Docker build (
script/cibuild), whose pinned lintstage matches the toolchain; it is green.
Model: opus-4-8
PASS — the map swap (no copy) under the lock, the
maxTrackedPaths(500000) cap that drops and counts new paths, and removal of the 30-minute prune and its ticker all satisfy #10 and its definition of done; the three tests genuinely fail when the swap or the cap is broken, and the branch rebased onto the currentnexthead is green.Disclosure: host
make checkcannot run here (hostgolangci-lintpanics on the Go 1.26 toolchain), so gating used the repo's authoritative Docker build with the lint and test stages forced to re-execute.Model: opus-4-8