From b9a231ef36a1e96bc15af6f7dcfa23f5dbbcd75c Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 8 Mar 2026 12:26:09 +0000 Subject: [PATCH] Dedup relay copies per observer and increase DB connection limit Drop duplicate MQTT packets when the same packet hash arrives at the same observer with the same hop count within 120s. Increase TimescaleDB max_connections to 300 to prevent connection exhaustion on startup. Co-Authored-By: Claude Opus 4.6 --- backend/src/mqtt/client.ts | 31 +++++++++++++++++++++++++++++++ docker-compose.yml | 1 + 2 files changed, 32 insertions(+) diff --git a/backend/src/mqtt/client.ts b/backend/src/mqtt/client.ts index fe406ce..8d4178d 100644 --- a/backend/src/mqtt/client.ts +++ b/backend/src/mqtt/client.ts @@ -101,6 +101,33 @@ function isEmptyPacketEnvelope(json: Record, rawHex: string, pa && (payloadLen ?? 0) <= 0; } +/** + * Per-observer packet dedup — prevents relay copies of the same packet from being + * ingested multiple times when they arrive at the same observer with the same hop count. + * Keyed by "packetHash:observerKey:hopCount". Entries expire after 120 seconds. + */ +const seenPackets = new Map(); +const SEEN_PACKETS_MAX = 50_000; +const SEEN_PACKETS_TTL_MS = 120_000; + +function isDuplicatePacket(packetHash: string, observerKey: string, hopCount: number | undefined): boolean { + const now = Date.now(); + // Periodic cleanup + if (seenPackets.size > SEEN_PACKETS_MAX / 2) { + for (const [k, ts] of seenPackets) { + if (now - ts > SEEN_PACKETS_TTL_MS) seenPackets.delete(k); + } + } + const key = `${packetHash}:${observerKey}:${hopCount ?? '?'}`; + if (seenPackets.has(key)) return true; + if (seenPackets.size >= SEEN_PACKETS_MAX) { + const oldest = seenPackets.keys().next().value; + if (oldest !== undefined) seenPackets.delete(oldest); + } + seenPackets.set(key, now); + return false; +} + /** * Dedup map for advert counts — prevents relay copies of the same advert packet * from incrementing the count multiple times. Keyed by decoded message hash. @@ -424,6 +451,10 @@ async function handleMessage(topic: string, rawPayload: Buffer): Promise { const finalHash = decodedHash ?? (json['hash'] as string | undefined) ?? crypto.randomUUID(); + if (isDuplicatePacket(finalHash, observerKey, decodedHops)) { + return; + } + void upsertNode(observerKey, { iata, network }); emitNode(observerKey, { network, observerId: observerKey }); diff --git a/docker-compose.yml b/docker-compose.yml index f6d5db4..d26c8b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,7 @@ services: timescaledb: image: timescale/timescaledb:latest-pg16 restart: unless-stopped + command: ["postgres", "-c", "max_connections=300"] environment: POSTGRES_DB: ${POSTGRES_DB:-meshcore} POSTGRES_USER: ${POSTGRES_USER:-meshcore}