From f0d2f2d3cd7154fc4aea71448d58f4c41ff99d38 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Tue, 26 May 2026 21:30:48 +0200 Subject: [PATCH] =?UTF-8?q?fix(perf):=20bump=20Prom=20timeout=204s?= =?UTF-8?q?=E2=86=9210s=20+=20cron=20pre-warms=20bench=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two root causes pinned via [DRAFT-TRACE] logs on staging: 1. Prom queries quantile_over_time(0.99, …[24h]) consistently take 4-8s under load. The 4s client timeout aborted, collapsing benches to draft when the per-bench KV snapshot wasn't available as fallback. Bump to 10s — fits the observed worst case with headroom. 2. The /api/cron/health-check route runs every 5 min but only computed Slack transitions. Add a loadAllBenchmarks() call so the per-bench unstable_cache stays warm and writeSnapshot() refreshes the KV snapshot layer. Eliminates the cold-start window where a fresh Vercel function instance has no in-memory cache AND the snapshot is stale or missing — exactly the pattern that produces the visible 'bench in draft' symptom users observed. --- src/app/api/cron/health-check/route.ts | 18 +++++++++++++++++- src/lib/prometheus.ts | 9 +++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/app/api/cron/health-check/route.ts b/src/app/api/cron/health-check/route.ts index 0c5d2c48..7892bede 100644 --- a/src/app/api/cron/health-check/route.ts +++ b/src/app/api/cron/health-check/route.ts @@ -1,7 +1,7 @@ import { timingSafeEqual } from "node:crypto"; import { NextResponse, type NextRequest } from "next/server"; import { getBenchmarkSlugs } from "@/data/benchmarks"; -import { getSpecs } from "@/lib/spec"; +import { getSpecs, loadAllBenchmarks } from "@/lib/spec"; import { extractMetricName, Prometheus } from "@/lib/prometheus"; export const runtime = "nodejs"; @@ -214,12 +214,28 @@ export async function GET(req: NextRequest) { } } + // Pre-warm the per-bench unstable_cache + KV snapshot layer. This + // single call is what spares an unlucky cold-start user a draft render + // when Prom is slow: it keeps each bench's runtime-cache entry fresh + // and triggers a writeSnapshot to KV so the snapshot fallback always + // has a recent good value to surface. Failures are caught so a Prom + // hiccup here doesn't 500 the cron and lose the Slack alert. + let prewarmCount = 0; + let prewarmErr: string | undefined; + try { + const benches = await loadAllBenchmarks(); + prewarmCount = benches.length; + } catch (err) { + prewarmErr = err instanceof Error ? err.message : String(err); + } + return NextResponse.json({ checked: liveSpecs.length, transitions: transitions.length, sent: sent.length, dryRun: !webhook, transitionsList: transitions, + prewarm: { count: prewarmCount, err: prewarmErr }, }); } diff --git a/src/lib/prometheus.ts b/src/lib/prometheus.ts index f9186f70..92afbd02 100644 --- a/src/lib/prometheus.ts +++ b/src/lib/prometheus.ts @@ -17,8 +17,13 @@ type PromEnvelope = | { status: "success"; data: T; warnings?: string[] } | { status: "error"; errorType: string; error: string }; -/** Default timeout for Prometheus queries. keep short, build time is finite. */ -const DEFAULT_TIMEOUT_MS = 4_000; +/** Default timeout for Prometheus queries. Bumped 4s → 10s after staging + * logs showed `quantile_over_time(0.99, …[24h])` consistently taking + * 4-8s under load (observed 2026-05-26), which would otherwise abort + * the query and collapse the bench to a draft render. 10s is the safe + * upper bound for our heaviest percentile-over-window queries while + * still bounding the SSR render at a tolerable ceiling. */ +const DEFAULT_TIMEOUT_MS = 10_000; export class Prometheus { constructor(public readonly baseUrl: string) {