From 9da294968ce0776730bdbf6cf7640a31fac53c1f Mon Sep 17 00:00:00 2001 From: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> Date: Wed, 8 Jul 2026 10:23:00 -0400 Subject: [PATCH] perf(desktop): cap aux backfill request concurrency Finding L3-3: fetchChunkedAuxEvents awaited each auxiliary-event chunk sequentially, delaying visible reaction/edit/deletion backfills on large history windows. Run aux chunks through a small shared concurrency helper so relay history requests overlap without unbounded fanout. The helper preserves chunk result order and the existing fail-fast semantics. Co-authored-by: Tyler Longwell Signed-off-by: Tyler Longwell --- desktop/src/shared/api/concurrency.ts | 20 ++++++++++++++++++++ desktop/src/shared/api/relayClientSession.ts | 13 ++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 desktop/src/shared/api/concurrency.ts diff --git a/desktop/src/shared/api/concurrency.ts b/desktop/src/shared/api/concurrency.ts new file mode 100644 index 000000000..925af8921 --- /dev/null +++ b/desktop/src/shared/api/concurrency.ts @@ -0,0 +1,20 @@ +export async function collectWithConcurrency( + items: T[], + concurrency: number, + worker: (item: T) => Promise, +): Promise { + const workerCount = Math.min(Math.max(1, concurrency), items.length); + const results = new Array(items.length); + let nextIndex = 0; + + await Promise.all( + Array.from({ length: workerCount }, async () => { + while (nextIndex < items.length) { + const currentIndex = nextIndex++; + results[currentIndex] = await worker(items[currentIndex]); + } + }), + ); + + return results; +} diff --git a/desktop/src/shared/api/relayClientSession.ts b/desktop/src/shared/api/relayClientSession.ts index c2462d08c..63b71497d 100644 --- a/desktop/src/shared/api/relayClientSession.ts +++ b/desktop/src/shared/api/relayClientSession.ts @@ -29,6 +29,7 @@ import { buildChannelMentionFilter, buildGlobalStreamFilter, } from "@/shared/api/relayChannelFilters"; +import { collectWithConcurrency } from "@/shared/api/concurrency"; import { replayLiveSubscriptions } from "@/shared/api/relayReconnectReplay"; import { RelayConnectionStateEmitter } from "@/shared/api/relayConnectionStateEmitter"; import { @@ -41,7 +42,8 @@ import { buildThreadReferenceTags } from "@/features/messages/lib/threading"; const RECONNECT_BASE_DELAY_MS = 1_000, RECONNECT_MAX_DELAY_MS = 30_000, - EVENT_BATCH_MS = 16; + EVENT_BATCH_MS = 16, + AUX_BACKFILL_CONCURRENCY = 4; /** * Passive liveness check. The relay sends heartbeat pings every 30s; if no @@ -217,10 +219,11 @@ export class RelayClient { chunks.push(eventIds.slice(i, i + AUX_BACKFILL_CHUNK_SIZE)); } - const batches: RelayEvent[][] = []; - for (const ids of chunks) { - batches.push(await this.requestHistory(buildFilter(channelId, ids))); - } + const batches = await collectWithConcurrency( + chunks, + AUX_BACKFILL_CONCURRENCY, + (ids) => this.requestHistory(buildFilter(channelId, ids)), + ); return batches.flat(); }