Store a new row's earliest timestamp as first_seen (closes #112)
check / check (push) Canceled after 0s

Each handler took the latest time per key in a batch and inserted it as
a new row's first_seen, so a key updated twice in one batch, seconds
apart, was stored first seen at the later time, and the IP lookup could
answer with a prefix first_seen after the route's last_seen.

The prefix handler now keeps the earliest time of all of the batch's
updates for each prefix. The AS, peer and peering handlers keep the
earliest and the latest time per key in database.SeenTimes, and
GetOrCreateASNBatch, UpdatePeerBatch and RecordPeering store a new row
first seen at the earliest and last seen at the latest. A stored row
still has only its last_seen set, to the latest.

Model: opus-5-5
This commit was merged in pull request #137.
This commit is contained in:
2026-10-20 02:59:17 +02:00
parent c4dadd1477
commit 444457bcf8
15 changed files with 384 additions and 103 deletions
+11 -9
View File
@@ -347,8 +347,19 @@ func (h *PrefixHandler) flushBatch() {
lastUpdates := make(map[prefixAndPeer]prefixUpdate)
// Each prefix the batch names, announced or withdrawn, with the earliest
// timestamp among all of its updates, not only each peer's last one: the
// prefix tables store only first_seen, written when the prefix is new and
// never changed after.
prefixesToUpdate := make(map[string]time.Time)
for _, update := range batch {
lastUpdates[prefixAndPeer{update.prefix, update.peer}] = update
seen, exists := prefixesToUpdate[update.prefix]
if !exists || update.timestamp.Before(seen) {
prefixesToUpdate[update.prefix] = update.timestamp
}
}
// Collect routes to upsert and delete
@@ -357,16 +368,7 @@ func (h *PrefixHandler) flushBatch() {
routesToDelete []database.LiveRouteDeletion
)
// Collect unique prefixes to update
prefixesToUpdate := make(map[string]time.Time)
for _, update := range lastUpdates {
// Track prefix for both announcements and withdrawals
seen, exists := prefixesToUpdate[update.prefix]
if !exists || update.timestamp.After(seen) {
prefixesToUpdate[update.prefix] = update.timestamp
}
// An announcement is stored when its path gives an origin, or when it
// ends in an AS set of several members: that route is stored with
// origin 0 (none). An announcement with an empty path is not stored.