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
51 changes: 51 additions & 0 deletions src/app/api/aggregate/route.ts
Original file line number Diff line number Diff line change
@@ -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",
},
});
}
14 changes: 12 additions & 2 deletions src/lib/aggregate-blob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@ 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";
const FETCH_TIMEOUT_MS = 8_000;
// 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;

type AggregateEnvelope = {
Expand Down Expand Up @@ -56,6 +63,9 @@ async function fetchAndProject(): Promise<Benchmark[] | null> {
// 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}`);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/sitemap-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ async function buildFullSitemap(): Promise<MetadataRoute.Sitemap> {
export async function buildSitemap(): Promise<MetadataRoute.Sitemap> {
try {
const timeout = new Promise<never>((_, 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) {
Expand Down
Loading