mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-22 15:23:30 +00:00
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:
1 parent
7d759d0a0f
commit
ddc6dc1ca9
5 files changed
+227
-58
No files matched your search
+53
-26
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user