From ebb912548dfa12e31763ee2fb7513be6b141ee5a Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:51:14 +0200 Subject: [PATCH 1/2] fix: increase aggregate-blob timeout 8s->20s, add gzip header, sitemap 25s->45s --- src/lib/aggregate-blob.ts | 7 ++++++- src/lib/sitemap-builder.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/aggregate-blob.ts b/src/lib/aggregate-blob.ts index ace33ba6..83daac05 100644 --- a/src/lib/aggregate-blob.ts +++ b/src/lib/aggregate-blob.ts @@ -25,7 +25,9 @@ import { loadSpecsUncached } from "@/lib/materialize/load"; import { overlayEditorial, slimBenchmarkForCache } from "@/lib/spec"; const DEFAULT_URL = "https://kv.openchainbench.com/aggregate/latest.json"; -const FETCH_TIMEOUT_MS = 8_000; +// 20s: blob is 2MB gzipped, served from Paris VPS. Vercel US East adds +// ~100-200ms latency. 8s was too tight without a CDN in front. +const FETCH_TIMEOUT_MS = 20_000; const MIN_BENCHES = 40; type AggregateEnvelope = { @@ -56,6 +58,9 @@ async function fetchAndProject(): Promise { // Bypass Next's fetch memoization; the outer unstable_cache handles // cross-request caching, we don't want double-layered TTLs. cache: "no-store", + // Explicitly request compression so Vercel's Node.js fetch gets the + // 2MB gzip payload instead of the 7.5MB raw JSON. + headers: { "Accept-Encoding": "gzip, br" }, }); if (!res.ok) { console.warn(`[aggregate-blob] fetch ${url} → ${res.status}`); diff --git a/src/lib/sitemap-builder.ts b/src/lib/sitemap-builder.ts index ff9a3b28..fcef82ea 100644 --- a/src/lib/sitemap-builder.ts +++ b/src/lib/sitemap-builder.ts @@ -565,7 +565,7 @@ async function buildFullSitemap(): Promise { export async function buildSitemap(): Promise { try { const timeout = new Promise((_, reject) => - setTimeout(() => reject(new Error("sitemap build timeout")), 25_000), + setTimeout(() => reject(new Error("sitemap build timeout")), 45_000), ); return await Promise.race([buildFullSitemap(), timeout]); } catch (err) { From 88d94dc75f4b8928ad6345235f9fdad71ccb3d89 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Tue, 25 Aug 2026 02:09:46 +0200 Subject: [PATCH 2/2] fix: sitemap blob via Vercel CDN proxy, timeout 8s->20s, sitemap 25s->45s --- src/app/api/aggregate/route.ts | 51 ++++++++++++++++++++++++++++++++++ src/lib/aggregate-blob.ts | 11 ++++++-- 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/app/api/aggregate/route.ts diff --git a/src/app/api/aggregate/route.ts b/src/app/api/aggregate/route.ts new file mode 100644 index 00000000..c18aa18c --- /dev/null +++ b/src/app/api/aggregate/route.ts @@ -0,0 +1,51 @@ +// Vercel-edge CDN proxy for the aggregate blob. +// +// kv.openchainbench.com/aggregate/latest.json lives on the Paris VPS +// (no CDN). Vercel functions run in IAD1 (US East) and were timing out +// on the 7.5 MB payload at the old 8 s limit. This route re-exposes the +// blob through Vercel's own CDN: the first request fetches from VPS +// (slow), Vercel caches the response at IAD1's edge for 60 s, all +// subsequent calls within that window cost ~1 ms instead of ~4 s. +// +// aggregate-blob.ts can point AGGREGATE_BLOB_URL at this route instead +// of kv.openchainbench.com to get near-zero latency from any Vercel +// function in the same region. +export const runtime = "nodejs"; + +const UPSTREAM = "https://kv.openchainbench.com/aggregate/latest.json"; +const UPSTREAM_TIMEOUT_MS = 25_000; + +export async function GET() { + let upstream: Response; + try { + upstream = await fetch(UPSTREAM, { + signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS), + headers: { "Accept-Encoding": "gzip, br" }, + cache: "no-store", + }); + } catch (err) { + return new Response( + JSON.stringify({ error: "upstream fetch failed", detail: String(err) }), + { status: 502, headers: { "Content-Type": "application/json" } }, + ); + } + + if (!upstream.ok) { + return new Response( + JSON.stringify({ error: "upstream error", status: upstream.status }), + { status: 502, headers: { "Content-Type": "application/json" } }, + ); + } + + const body = await upstream.arrayBuffer(); + return new Response(body, { + status: 200, + headers: { + "Content-Type": "application/json", + // Vercel CDN caches 60 s + 5 min SWR — aligns with the materialize + // worker's publish cadence (~60 s). The sitemap and homepage get a + // warm edge hit after the first request. + "Cache-Control": "public, s-maxage=60, stale-while-revalidate=300", + }, + }); +} diff --git a/src/lib/aggregate-blob.ts b/src/lib/aggregate-blob.ts index 83daac05..57ea5efd 100644 --- a/src/lib/aggregate-blob.ts +++ b/src/lib/aggregate-blob.ts @@ -24,9 +24,14 @@ import type { Benchmark } from "@/types/benchmark"; import { loadSpecsUncached } from "@/lib/materialize/load"; import { overlayEditorial, slimBenchmarkForCache } from "@/lib/spec"; -const DEFAULT_URL = "https://kv.openchainbench.com/aggregate/latest.json"; -// 20s: blob is 2MB gzipped, served from Paris VPS. Vercel US East adds -// ~100-200ms latency. 8s was too tight without a CDN in front. +// In production, use the self-hosted Vercel CDN proxy (/api/aggregate) +// so IAD1 functions pay ~1 ms (edge cache hit) instead of ~4 s crossing +// the Atlantic to kv.openchainbench.com (Paris VPS, no CDN). +// In dev/preview, hit the VPS directly — CDN caching doesn't apply. +const DEFAULT_URL = + process.env.VERCEL_ENV === "production" + ? "https://openchainbench.com/api/aggregate" + : "https://kv.openchainbench.com/aggregate/latest.json"; const FETCH_TIMEOUT_MS = 20_000; const MIN_BENCHES = 40;