fix: keep IATA packet paging alive past short pages

A packet repeats once per observer in the site scan, so grouping can
collapse a scan_depth window to fewer packets than the page asked for.
That short page set hasMore=false and stranded all older history: YOW
dead-ended after 45 packets, ~6 minutes back.

Report scan saturation and the floor the scans covered, and treat a
saturated short page as more data. Clamp the cursor to the floor so
paging on cannot skip the band the scan never read.
This commit is contained in:
MrAlders0n authored and Ded committed 2026-08-19 16:21:26 -07:00
1 parent 7d759d0a0f
commit ddc6dc1ca9
5 files changed
+227 -58

No files matched your search

+53 -26
View File
@@ -2894,25 +2894,8 @@ func (q *Queries) ListPacketsAfterID(ctx context.Context, arg ListPacketsAfterID
}
const listPacketsByIATAs = `-- name: ListPacketsByIATAs :many
SELECT
p.packet_hash,
p.payload_type,
p.route_type,
p.first_heard_at,
p.last_heard_at,
p.scope_id,
ts.name AS scope_name,
sh.site_heard_at,
(SELECT COUNT(*) FROM packet_observations po2 WHERE po2.packet_hash = p.packet_hash) AS observation_count,
po.observer_id AS latest_observer_id,
o.display_name AS latest_observer_name,
po.iata AS latest_observer_iata,
po.path_length_byte AS latest_observer_path_length_byte,
po.hash_size AS latest_observer_hash_size,
po.hop_count AS latest_observer_hop_count,
po.path_bytes AS latest_observer_path_bytes
FROM (
SELECT hits.packet_hash, MAX(hits.heard_at)::timestamptz AS site_heard_at
WITH scanned AS (
SELECT req.iata AS req_iata, hits.packet_hash, hits.heard_at
FROM unnest($1::bpchar[]) AS req(iata)
CROSS JOIN LATERAL (
SELECT po3.packet_hash, po3.heard_at
@@ -2930,15 +2913,51 @@ FROM (
ORDER BY po3.heard_at DESC
LIMIT $8
) hits
GROUP BY hits.packet_hash
),
saturation AS (
SELECT
COUNT(*) > 0 AS scan_saturated,
MAX(floor_ts)::timestamptz AS scan_floor
FROM (
SELECT MIN(heard_at) AS floor_ts
FROM scanned
GROUP BY req_iata
HAVING COUNT(*) >= $8
) filled
),
page AS (
SELECT scanned.packet_hash, MAX(scanned.heard_at)::timestamptz AS site_heard_at
FROM scanned
GROUP BY scanned.packet_hash
HAVING ($2::timestamptz IS NULL OR NOT EXISTS (
SELECT 1 FROM packet_observations px
WHERE px.packet_hash = hits.packet_hash
WHERE px.packet_hash = scanned.packet_hash
AND px.iata = ANY($1::bpchar[])
AND px.heard_at >= $2))
ORDER BY site_heard_at DESC
LIMIT $9
) sh
)
SELECT
p.packet_hash,
p.payload_type,
p.route_type,
p.first_heard_at,
p.last_heard_at,
p.scope_id,
ts.name AS scope_name,
sh.site_heard_at,
sat.scan_saturated,
sat.scan_floor,
(SELECT COUNT(*) FROM packet_observations po2 WHERE po2.packet_hash = p.packet_hash) AS observation_count,
po.observer_id AS latest_observer_id,
o.display_name AS latest_observer_name,
po.iata AS latest_observer_iata,
po.path_length_byte AS latest_observer_path_length_byte,
po.hash_size AS latest_observer_hash_size,
po.hop_count AS latest_observer_hop_count,
po.path_bytes AS latest_observer_path_bytes
FROM page sh
CROSS JOIN saturation sat
JOIN packets p ON p.packet_hash = sh.packet_hash
LEFT JOIN LATERAL (
SELECT observer_id, iata, path_length_byte, hash_size, hop_count, path_bytes
@@ -2973,6 +2992,8 @@ type ListPacketsByIATAsRow struct {
ScopeID *int32 `json:"scope_id"`
ScopeName *string `json:"scope_name"`
SiteHeardAt pgtype.Timestamptz `json:"site_heard_at"`
ScanSaturated bool `json:"scan_saturated"`
ScanFloor pgtype.Timestamptz `json:"scan_floor"`
ObservationCount int64 `json:"observation_count"`
LatestObserverID uuid.UUID `json:"latest_observer_id"`
LatestObserverName *string `json:"latest_observer_name"`
@@ -2988,10 +3009,14 @@ type ListPacketsByIATAsRow struct {
// to fill a page for a quiet site; walking the site's own observation log is
// proportional to the page size instead. Results are ordered by when the
// requested sites heard the packet (site-local recency) and the cursor
// follows that ordering. scan_depth is a multiple of the page size to
// absorb per-observer duplicates; if duplication exceeds it across a
// page, pagination ends early (hasMore=false) rather than returning a
// short page, even though deeper matches exist.
// follows that ordering. scan_depth caps how deep each site's observation
// log is walked. A packet repeats once per observer that heard it, so a
// page can collapse to fewer distinct packets than were asked for without
// the site being exhausted. scan_saturated reports whether any site hit
// that cap and scan_floor the oldest heard_at they all cover, so a short
// page can keep paging instead of reading as the end of the data.
// A site that filled scan_depth still has unread history below its floor.
// The newest such floor is the point above which every site is covered.
func (q *Queries) ListPacketsByIATAs(ctx context.Context, arg ListPacketsByIATAsParams) ([]ListPacketsByIATAsRow, error) {
rows, err := q.db.Query(ctx, listPacketsByIATAs,
arg.Iatas,
@@ -3020,6 +3045,8 @@ func (q *Queries) ListPacketsByIATAs(ctx context.Context, arg ListPacketsByIATAs
&i.ScopeID,
&i.ScopeName,
&i.SiteHeardAt,
&i.ScanSaturated,
&i.ScanFloor,
&i.ObservationCount,
&i.LatestObserverID,
&i.LatestObserverName,