Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/app/api/aggregate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@
// of kv.openchainbench.com to get near-zero latency from any Vercel
// function in the same region.
export const runtime = "nodejs";
// ISR: generate once, serve from Vercel Data Cache for 60 s, regenerate
// in background. Keeps s-maxage in the response (Next.js strips it when
// the route uses cache:"no-store" fetches internally, which makes the
// route dynamic and prevents CDN caching — removing that flag fixes it).
export const revalidate = 60;

const UPSTREAM = "https://kv.openchainbench.com/aggregate/latest.json";
const UPSTREAM_TIMEOUT_MS = 25_000;
// Paris VPS → IAD1 with 7.3 MB payload: measured 37-49 s uncompressed.
// With Accept-Encoding:gzip Caddy compresses to ~2.3 MB in ~18 s. Set
// the timeout to 70 s so cold-cache first requests always complete.
const UPSTREAM_TIMEOUT_MS = 70_000;

export async function GET() {
// Retry once: some Vercel function instances can't reach the Paris VPS
Expand All @@ -28,7 +36,10 @@ export async function GET() {
upstream = await fetch(UPSTREAM, {
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS),
headers: { "Accept-Encoding": "gzip, br" },
cache: "no-store",
// No cache:"no-store" here: that flag opts the route into
// dynamic mode and causes Next.js to strip s-maxage from our
// response headers, breaking Vercel CDN caching. The ISR
// revalidate=60 above handles freshness instead.
});
} catch (err) {
lastErr = String(err);
Expand Down
6 changes: 5 additions & 1 deletion src/lib/aggregate-blob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ const DEFAULT_URL =
process.env.VERCEL_ENV
? "https://openchainbench.com/api/aggregate"
: "https://kv.openchainbench.com/aggregate/latest.json";
const FETCH_TIMEOUT_MS = 20_000;
// Needs to be > the Paris VPS response time (measured 18 s compressed,
// 37-49 s uncompressed). Set to 65 s so cold-cache ISR misses on
// /api/aggregate have enough headroom to complete before we give up and
// fall through to the Redis fan-out path.
const FETCH_TIMEOUT_MS = 65_000;
const MIN_BENCHES = 40;

type AggregateEnvelope = {
Expand Down
11 changes: 7 additions & 4 deletions src/lib/sitemap-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,11 +577,14 @@ async function buildFullSitemap(): Promise<MetadataRoute.Sitemap> {

export async function buildSitemap(): Promise<MetadataRoute.Sitemap> {
try {
// 20 s: tight enough to reply well within the smoke-test's 90 s window
// even if the static fallback itself takes a few seconds. The Vercel
// maxDuration on the route is 60 s; this leaves 40 s of headroom.
// 55 s: the Vercel maxDuration on the route is 60 s. With /api/aggregate
// ISR-cached (revalidate=60 on that route) warm requests return in <1 ms
// and the full sitemap completes in ~10 s. Cold-start requests (first hit
// after deployment) may take 18-49 s for the aggregate; 55 s gives enough
// headroom while leaving 5 s for buildStaticFallback() before Vercel kills
// the function.
const timeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error("sitemap build timeout")), 20_000),
setTimeout(() => reject(new Error("sitemap build timeout")), 55_000),
);
// Attach a no-op .catch() to prevent unhandled-rejection crashes if
// buildFullSitemap() rejects AFTER the race has already settled (via
Expand Down
Loading