A new prefix's first_seen is the latest timestamp among the batch's updates for it, not the earliest #112

Closed
opened 2026-10-19 22:35:20 +02:00 by clawbot · 1 comment
Collaborator

Where: PrefixHandler.flushBatch in internal/routewatch/prefixhandler.go (the prefixesToUpdate map); insertPrefixesInTable in internal/database/database.go.

flushBatch collects one timestamp per prefix for the prefixes tables and keeps the latest: if !exists || update.timestamp.After(seen) { prefixesToUpdate[update.prefix] = update.timestamp }. Since PR 98 the prefix tables hold only first_seen, inserted with ON CONFLICT (prefix) DO NOTHING, so for a prefix new to the table the value written is the latest time among the batch's updates for it, not the earliest. The After rule was right when the map also fed last_seen; for an insert-only first_seen it picks the wrong end.

Scenario: prefix P, not yet stored, is announced by peer A at feed time T and by peer B at T plus N seconds, both in one batch (N is 1 to 2 s normally; tens of seconds while a long write or a peer-down delete holds the flush goroutine and the batch grows toward 250,000 updates). prefixes_v4.first_seen becomes T plus N. The IP lookup answer reports that as first_seen, and since it may return A's route with last_seen T, the same answer can show a first_seen later than its last_seen.

The same happens in the other three tables: ASHandler.flushBatch and PeerHandler.flushBatch keep the newest timestamp per AS and per peer and upsertASN and UpdatePeerBatch insert it as first_seen; PeeringHandler.processPeerings does the same for a new peering.

Severity: low.

Definition of done

  • Each handler keeps the earliest timestamp per key for the row's first_seen (and the latest for last_seen where there is one), with a comment saying why.
  • Test, failing before the fix: two announcements of a never-seen prefix from two peers with timestamps T and T plus 5 s handled into one batch, then flushBatch; prefixes_v4.first_seen is T (today T plus 5 s).
  • make check passes.

Model: fable-5-1

Where: `PrefixHandler.flushBatch` in `internal/routewatch/prefixhandler.go` (the `prefixesToUpdate` map); `insertPrefixesInTable` in `internal/database/database.go`. `flushBatch` collects one timestamp per prefix for the prefixes tables and keeps the latest: `if !exists || update.timestamp.After(seen) { prefixesToUpdate[update.prefix] = update.timestamp }`. Since PR 98 the prefix tables hold only `first_seen`, inserted with `ON CONFLICT (prefix) DO NOTHING`, so for a prefix new to the table the value written is the latest time among the batch's updates for it, not the earliest. The `After` rule was right when the map also fed `last_seen`; for an insert-only `first_seen` it picks the wrong end. Scenario: prefix P, not yet stored, is announced by peer A at feed time T and by peer B at T plus N seconds, both in one batch (N is 1 to 2 s normally; tens of seconds while a long write or a peer-down delete holds the flush goroutine and the batch grows toward 250,000 updates). `prefixes_v4.first_seen` becomes T plus N. The IP lookup answer reports that as `first_seen`, and since it may return A's route with `last_seen` T, the same answer can show a `first_seen` later than its `last_seen`. The same happens in the other three tables: `ASHandler.flushBatch` and `PeerHandler.flushBatch` keep the newest timestamp per AS and per peer and `upsertASN` and `UpdatePeerBatch` insert it as `first_seen`; `PeeringHandler.processPeerings` does the same for a new peering. Severity: low. ## Definition of done - Each handler keeps the earliest timestamp per key for the row's `first_seen` (and the latest for `last_seen` where there is one), with a comment saying why. - Test, failing before the fix: two announcements of a never-seen prefix from two peers with timestamps T and T plus 5 s handled into one batch, then `flushBatch`; `prefixes_v4.first_seen` is T (today T plus 5 s). - `make check` passes. Model: fable-5-1
clawbot self-assigned this 2026-10-19 22:35:20 +02:00
Author
Collaborator

#137 stores a new prefix first seen at the earliest time among all of the batch's updates for it.

It does not change the AS, peer and peering handlers, which this issue's first done item also names: the PR was scoped to the prefix handler. Their first_seen is still the latest time in the batch, and the PR's commit closes this issue when it lands.

Model: opus-5-5

https://git.eeqj.de/sneak/routewatch/pulls/137 stores a new prefix first seen at the earliest time among all of the batch's updates for it. It does not change the AS, peer and peering handlers, which this issue's first done item also names: the PR was scoped to the prefix handler. Their `first_seen` is still the latest time in the batch, and the PR's commit closes this issue when it lands. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/routewatch#112