From 91e5189ee87d4985d95983475660049fed69e310 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Mon, 20 Jul 2026 14:47:49 +0200 Subject: [PATCH] Phase 3.5: kill remaining SRH callers (/api/series, search-featured, rpc-hub-stats) via loadSnapshotFromBlob adapter --- src/app/api/series/[slug]/route.ts | 6 ++++- src/lib/bench-blob.ts | 35 ++++++++++++++++++++++++++++++ src/lib/rpc-hub-stats.ts | 17 +++++++++++---- src/lib/search-featured.ts | 6 ++++- 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/app/api/series/[slug]/route.ts b/src/app/api/series/[slug]/route.ts index 47c52114..23428968 100644 --- a/src/app/api/series/[slug]/route.ts +++ b/src/app/api/series/[slug]/route.ts @@ -3,6 +3,7 @@ import { unstable_cache } from "next/cache"; import { getBenchmark } from "@/data/benchmarks"; import { filterSig, loadSpecsUncached, specToBenchmark } from "@/lib/materialize/load"; import { readMaterialized } from "@/lib/materialize/store"; +import { loadSnapshotFromBlob } from "@/lib/bench-blob"; import { buildProviderColors } from "@/lib/series-colors"; import { logoPath } from "@/lib/logo-manifest"; import { clientKey, rateLimit, tooManyRequests } from "@/lib/rate-limit"; @@ -32,7 +33,10 @@ const getSeriesMapCached = unstable_cache( venue: string | undefined, ): Promise | null> => { const sig = filterSig({ chain, region, kind, venue }); - const stored = await readMaterialized(slug, sig); + // Try CDN blob first (Phase 3), fall back to Redis via SRH. + const stored = + (await loadSnapshotFromBlob(slug, sig)) ?? + (await readMaterialized(slug, sig)); if (stored) { const fromBlob = range === "7d" diff --git a/src/lib/bench-blob.ts b/src/lib/bench-blob.ts index 9a10deb2..07f96cde 100644 --- a/src/lib/bench-blob.ts +++ b/src/lib/bench-blob.ts @@ -17,6 +17,10 @@ */ import type { Benchmark } from "@/types/benchmark"; +import { + MAT_SCHEMA_VERSION, + type MaterializedSnapshot, +} from "@/lib/materialize/schema"; const DEFAULT_BASE_URL = "https://kv.openchainbench.com/aggregate"; const FETCH_TIMEOUT_MS = 5_000; @@ -90,3 +94,34 @@ export async function loadVariantFromBlob( if (env.sig !== sig) return null; return env.bench; } + +/** + * Snapshot-shaped adapter for callers that expect the exact + * `readMaterialized` return value. Lets `readMaterialized(slug, sig)` + * call sites become `loadSnapshotFromBlob(slug, sig) ?? readMaterialized(slug, sig)` + * with zero downstream code change. `state` is stubbed empty because + * the blob broadcast doesn't include ring-buffer state (worker-internal, + * never read by the site — only the aggregation loop uses it). + */ +export async function loadSnapshotFromBlob( + slug: string, + sig: string, +): Promise { + const url = sig + ? `${baseUrl()}/variants/${encodeURIComponent(slug)}/${encodeURIComponent(sig)}.json` + : `${baseUrl()}/benches/${encodeURIComponent(slug)}.json`; + const raw = await fetchJson(url); + if (!isBenchEnvelope(raw) || raw.v !== 1 || raw.slug !== slug) return null; + if (sig) { + const env = raw as VariantEnvelope; + if (env.sig !== sig) return null; + } + return { + v: MAT_SCHEMA_VERSION, + slug, + sig, + builtAt: raw.builtAt, + bench: raw.bench, + state: { providers: {}, rings: {} }, + }; +} diff --git a/src/lib/rpc-hub-stats.ts b/src/lib/rpc-hub-stats.ts index 8575e3ce..426cd5c1 100644 --- a/src/lib/rpc-hub-stats.ts +++ b/src/lib/rpc-hub-stats.ts @@ -30,6 +30,7 @@ import { } from "@/lib/materialize/load"; import { REMOVED_BENCH_SLUGS } from "@/lib/removed-benches"; import { readMaterialized, storeConfigured } from "@/lib/materialize/store"; +import { loadSnapshotFromBlob } from "@/lib/bench-blob"; import { chainLabelForSlug } from "@/lib/chains"; import { matchesChainSlug } from "@/lib/chain-aliases"; import type { Benchmark, ProviderResult } from "@/types/benchmark"; @@ -173,7 +174,10 @@ function liveRows(bench: Benchmark): ProviderResult[] { } async function buildChain(spec: Spec): Promise { - const snap = await readMaterialized(spec.slug, ""); + // Try CDN blob first (Phase 3), fall back to Redis via SRH. + const snap = + (await loadSnapshotFromBlob(spec.slug, "")) ?? + (await readMaterialized(spec.slug, "")); if (!snap) return null; const bench = snap.bench; const rows = liveRows(bench); @@ -193,9 +197,14 @@ async function buildChain(spec: Spec): Promise { } } const variantSnaps = await Promise.all( - RPC_REGION_KEYS.map((region) => - readMaterialized(spec.slug, filterSig({ region })).catch(() => null), - ), + RPC_REGION_KEYS.map(async (region) => { + const sig = filterSig({ region }); + // Try CDN blob first, fall back to Redis via SRH. + return ( + (await loadSnapshotFromBlob(spec.slug, sig).catch(() => null)) ?? + (await readMaterialized(spec.slug, sig).catch(() => null)) + ); + }), ); for (let i = 0; i < RPC_REGION_KEYS.length; i++) { const region = RPC_REGION_KEYS[i]; diff --git a/src/lib/search-featured.ts b/src/lib/search-featured.ts index 8345d860..483cc0ba 100644 --- a/src/lib/search-featured.ts +++ b/src/lib/search-featured.ts @@ -15,6 +15,7 @@ import { getBenchmarks } from "@/data/benchmarks"; import { readMaterialized } from "@/lib/materialize/store"; +import { loadSnapshotFromBlob } from "@/lib/bench-blob"; import { leader, fieldValue } from "@/lib/citation"; const FEATURED_BENCH_SLUGS = [ @@ -104,7 +105,10 @@ export async function buildFeaturedLeadersFromStore(): Promise { try { - const snap = await readMaterialized(slug, ""); + // Try CDN blob first (Phase 3), fall back to Redis via SRH. + const snap = + (await loadSnapshotFromBlob(slug, "")) ?? + (await readMaterialized(slug, "")); return snap ? snap.bench : null; } catch { return null;