From 190af3ce86351f68948279578a6533454dfe776e Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Sat, 18 Jul 2026 18:28:24 +0200 Subject: [PATCH] spec: throttle aggregate Redis fan-out to 12 concurrent (was 68, causing alphabetically-later specs to timeout as draft) --- src/lib/spec.ts | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/lib/spec.ts b/src/lib/spec.ts index 231dd22b..c528b49d 100644 --- a/src/lib/spec.ts +++ b/src/lib/spec.ts @@ -334,11 +334,56 @@ export class AllBenchmarksDraftError extends Error { * suite can exercise the error path without standing up Prometheus or * the Next cache backend. */ +/** Concurrency cap for the parallel per-bench Redis fan-out below. + * + * The naive `Promise.allSettled(specs.map(loadOne))` fires ~68 read + * chains at once (each does GET pointer → GET blob → optional GET lkg = + * ~150-200 Redis ops), which reliably saturated SRH's request pool and + * starved the alphabetically-later specs (`perp-open-interest` → `zksync-rpc`) + * into 8s timeouts — they then rendered as draft placeholders on the + * homepage even though their blobs were sitting in Redis the whole time. + * + * Cap at 12 in-flight chains so the total concurrent Redis ops stay + * well under the pool ceiling. The aggregate is behind unstable_cache + * (revalidate 300 s) so the slightly-longer worst-case wall time on + * cache miss is invisible to end users. */ +const AGGREGATE_LOAD_CONCURRENCY = 12; + +async function limitedAllSettled( + items: T[], + concurrency: number, + fn: (item: T) => Promise, +): Promise[]> { + const results: PromiseSettledResult[] = new Array(items.length); + let cursor = 0; + const worker = async () => { + while (true) { + const i = cursor++; + if (i >= items.length) return; + try { + results[i] = { status: "fulfilled", value: await fn(items[i]) }; + } catch (reason) { + results[i] = { status: "rejected", reason }; + } + } + }; + const workers = Array.from( + { length: Math.min(concurrency, items.length) }, + () => worker(), + ); + await Promise.all(workers); + return results; +} + export async function aggregateBenchmarks( specs: Spec[], loadOne: (slug: string) => Promise, ): Promise { - const settled = await Promise.allSettled(specs.map((s) => loadOne(s.slug))); + const settled = await limitedAllSettled( + specs, + AGGREGATE_LOAD_CONCURRENCY, + (s) => loadOne(s.slug), + ); const benchmarks: Benchmark[] = []; for (let i = 0; i < specs.length; i++) { const spec = specs[i];