mirror of
https://github.com/gadgethd/ukmesh.git
synced 2026-10-23 06:12:27 +00:00
fix(paths): split multibyte-paths into two simple queries joined in JS (timeout → 0.3s)
This commit is contained in:
1 parent
991b99fef7
commit
8306cd770c
2 files changed
+68
-47
No files matched your search
@@ -353,3 +353,10 @@ ROOT CAUSE (3 layers, all fixed):
|
||||
RESULTS: heardBy 80.6s→87ms, advert 1.8s→6ms, status 2.7s→44ms, recentPackets 11s→222ms, links 42.5s→40ms. Full owner/live battery <500ms. Zero owner/live timeouts since deploy. Health healthy, HopReach 200.
|
||||
Deploy notes: docker build used a STALE cache once (image lacked the patch) — verify image content (grep dist) before deploy; compose recreate uses the .env pin at recreate time — update pin BEFORE recreate or pass BACKEND_IMAGE=<digest> explicitly.
|
||||
FOLLOW-UP (pre-existing, not part of this fix): stats-page queries (multibyte/buckets in statsRepository.ts) still use per-row canonical joins + run on the analytics pool (hardcoded max 2) → 1-2 min under load, can transiently starve health synthetic checks. Same function→join fix applies when next touched.
|
||||
|
||||
## PATHS OVERLAY + GNOME OFFLINE INVESTIGATION (2026-08-06 ~22:00-23:30Z, Ben-reported)
|
||||
1. PATHS TOGGLE EMPTY — ROOT CAUSE: GET /path-beta/multibyte-paths timed out (25s+). The single JOIN blob made the planner estimate rows=1 on the scoped-node CTE (node_identity_sightings EXISTS is not estimable) → nested loops re-materialising the 13k-row identity view per row → 17.4M-row scans, 100-155s. ANALYZE + MATERIALIZED + UNION variants all still mis-estimated. FIX (commit 6e5d4c5): split into two simple statements (scoped nodes ~325ms + multibyte links ~92ms) joined + geometry-filtered in JS. Live: ukmesh 0.30s / 4,202 segments / maxCount 698,565; noparams 0.22s. Backend 273/273.
|
||||
2. SITE-WIDE STALENESS — NONE. Ingest healthy (7.5k+5.3k packets/2h; top senders fresh; Sh@DoW nodes last seen 1-5 min). Multibyte evidence flowing (10,176 today). Other observers' traffic normal (NULL-src = encrypted, expected).
|
||||
3. GNOME-STKTN-RPT-V4 OFFLINE — REAL, NOT A WEBSITE BUG. Steady ~13 payload-mentions/hour for days → abrupt stop 21:32:00.315Z. Zero evidence in any column since (src/rx/topic/payload/status). The site is correctly reporting the device went silent. GNOME-MSG-RPT online (22:13Z, 2,746 adverts). Likely device/power/RF at the STKTN site — physical check needed.
|
||||
4. SIGHTINGS MV: node_identity_sightings (22.5k rows, zero indexes, per-query canonical grouping) → indexed MATERIALIZED VIEW (unique (node_id,network) + (network,node_id)), refreshed every 5 min with the links MV. All buildNodeScopeClause consumers (topology, viable links, paths, network filters) benefit.
|
||||
5. recordMultibyteEvidence deadlock errors: 4 in 6h pre-fix, 0 in the 30 min after the MV swap. Watch.
|
||||
+61
-47
@@ -1237,53 +1237,67 @@ export async function getMultibytePathSegments(network?: string, observer?: stri
|
||||
const scope = buildScopePlaceholders(1, network, observer);
|
||||
const params: unknown[] = [...scope.params];
|
||||
|
||||
const res = await pool.query<{
|
||||
a_lat: number;
|
||||
a_lon: number;
|
||||
b_lat: number;
|
||||
b_lon: number;
|
||||
count: number;
|
||||
}>(
|
||||
`SELECT
|
||||
a.lat AS a_lat,
|
||||
a.lon AS a_lon,
|
||||
b.lat AS b_lat,
|
||||
b.lon AS b_lon,
|
||||
nl.multibyte_observed_count AS count
|
||||
FROM node_identity_links nl
|
||||
JOIN node_identity_nodes a ON a.node_id = nl.node_a_id
|
||||
JOIN node_identity_nodes b ON b.node_id = nl.node_b_id
|
||||
WHERE nl.multibyte_observed_count > 0
|
||||
AND nl.itm_viable = true
|
||||
AND a.lat IS NOT NULL
|
||||
AND a.lon IS NOT NULL
|
||||
AND b.lat IS NOT NULL
|
||||
AND b.lon IS NOT NULL
|
||||
AND a.lat BETWEEN -90 AND 90
|
||||
AND b.lat BETWEEN -90 AND 90
|
||||
AND a.lon BETWEEN -180 AND 180
|
||||
AND b.lon BETWEEN -180 AND 180
|
||||
AND NOT (ABS(a.lat) < 5 AND ABS(a.lon) < 5)
|
||||
AND NOT (ABS(b.lat) < 5 AND ABS(b.lon) < 5)
|
||||
AND (a.name IS NULL OR a.name NOT LIKE '%🚫%')
|
||||
AND (b.name IS NULL OR b.name NOT LIKE '%🚫%')
|
||||
AND SQRT(
|
||||
POWER((a.lat - b.lat) * 111, 2)
|
||||
+ POWER((a.lon - b.lon) * 111 * COS(RADIANS((a.lat + b.lat) / 2)), 2)
|
||||
) <= ${MAX_MULTIBYTE_PATH_SEGMENT_KM}
|
||||
${buildNodeScopeClause(scope, 'a')}
|
||||
${buildNodeScopeClause(scope, 'b')}
|
||||
ORDER BY nl.multibyte_observed_count DESC`,
|
||||
params,
|
||||
);
|
||||
// Two simple statements joined in JS. A single JOIN blob makes the planner
|
||||
// guess rows=1 on the scoped-node CTE (the sightings EXISTS is not
|
||||
// estimable) and pick nested loops that re-materialise the identity view
|
||||
// per row: 100s+. Splitting keeps both statements simple enough that the
|
||||
// planner cannot mis-estimate them (measured: ~325ms + ~92ms).
|
||||
const [nodesRes, linksRes] = await Promise.all([
|
||||
pool.query<{
|
||||
node_id: string;
|
||||
lat: number | null;
|
||||
lon: number | null;
|
||||
name: string | null;
|
||||
}>(
|
||||
`SELECT node_id, lat, lon, name
|
||||
FROM node_identity_nodes n
|
||||
WHERE 1=1${buildNodeScopeClause(scope, 'n')}`,
|
||||
params,
|
||||
),
|
||||
pool.query<{
|
||||
node_a_id: string;
|
||||
node_b_id: string;
|
||||
multibyte_observed_count: number;
|
||||
}>(
|
||||
`SELECT node_a_id, node_b_id, multibyte_observed_count
|
||||
FROM node_identity_links
|
||||
WHERE multibyte_observed_count > 0
|
||||
AND itm_viable = true`,
|
||||
),
|
||||
]);
|
||||
|
||||
const segments = res.rows.map((row) => ({
|
||||
positions: [
|
||||
[Number(row.a_lat), Number(row.a_lon)],
|
||||
[Number(row.b_lat), Number(row.b_lon)],
|
||||
] as [[number, number], [number, number]],
|
||||
count: Number(row.count ?? 0),
|
||||
}));
|
||||
const nodesById = new Map(nodesRes.rows.map((row) => [row.node_id, row]));
|
||||
const segments: Array<{
|
||||
positions: [[number, number], [number, number]];
|
||||
count: number;
|
||||
}> = [];
|
||||
for (const link of linksRes.rows) {
|
||||
const a = nodesById.get(link.node_a_id);
|
||||
const b = nodesById.get(link.node_b_id);
|
||||
if (!a || !b) continue;
|
||||
if (a.lat == null || a.lon == null || b.lat == null || b.lon == null) continue;
|
||||
if (a.lat < -90 || a.lat > 90 || b.lat < -90 || b.lat > 90) continue;
|
||||
if (a.lon < -180 || a.lon > 180 || b.lon < -180 || b.lon > 180) continue;
|
||||
if (Math.abs(a.lat) < 5 && Math.abs(a.lon) < 5) continue;
|
||||
if (Math.abs(b.lat) < 5 && Math.abs(b.lon) < 5) continue;
|
||||
if ((a.name ?? '').includes('🚫') || (b.name ?? '').includes('🚫')) continue;
|
||||
const distKm = Math.sqrt(
|
||||
Math.pow((a.lat - b.lat) * 111, 2)
|
||||
+ Math.pow(
|
||||
(a.lon - b.lon) * 111 * Math.cos(((a.lat + b.lat) / 2) * (Math.PI / 180)),
|
||||
2,
|
||||
),
|
||||
);
|
||||
if (distKm > MAX_MULTIBYTE_PATH_SEGMENT_KM) continue;
|
||||
segments.push({
|
||||
positions: [
|
||||
[a.lat, a.lon],
|
||||
[b.lat, b.lon],
|
||||
],
|
||||
count: link.multibyte_observed_count,
|
||||
});
|
||||
}
|
||||
segments.sort((x, y) => y.count - x.count);
|
||||
|
||||
const maxCount = segments.reduce((max, segment) => Math.max(max, segment.count), 0);
|
||||
return { maxCount, segments };
|
||||
@@ -1311,7 +1325,7 @@ export async function getViableLinks(network?: string, observer?: string): Promi
|
||||
if (network && !observer) {
|
||||
const scopedNetworks = network === 'ukmesh' ? UKMESH_NETWORKS : [network];
|
||||
const res = await pool.query<ViableLinkRow>(
|
||||
`WITH net_nodes AS (
|
||||
`WITH net_nodes AS MATERIALIZED (
|
||||
SELECT DISTINCT node_id FROM node_identity_nodes
|
||||
WHERE network = ANY($1::text[])
|
||||
AND (name IS NULL OR name NOT LIKE '%🚫%')
|
||||
|
||||
Reference in new issue
Block a user