| 1 | #!/usr/bin/env node |
| 2 | // Where should g1t's Workers run? A throwaway experiment that answers it |
| 3 | // with numbers, without touching any production Worker. |
| 4 | // |
| 5 | // It deploys a handful of tiny Workers named g1t-probe-*, one per |
| 6 | // placement setting, each bound read-only in practice (it runs only |
| 7 | // `SELECT 1`) to the g1t-repos D1 database, plus g1t-probe-edge, which runs |
| 8 | // near the caller and calls each of the others through a service binding, |
| 9 | // the way the site calls the services. Then it measures them from this |
| 10 | // machine, prints a table, and deletes them. |
| 11 | // |
| 12 | // node scripts/perf/placement-probe.mjs deploy # make the probes (needs `wrangler login`) |
| 13 | // node scripts/perf/placement-probe.mjs measure # 1 warm-up + 7 requests each, a table |
| 14 | // node scripts/perf/placement-probe.mjs delete # remove them all |
| 15 | // node scripts/perf/placement-probe.mjs apply '{"region":"aws:us-west-1"}' |
| 16 | // # write a placement into every config that should have it |
| 17 | // node scripts/perf/placement-probe.mjs apply '{"mode":"smart"}' # back to Smart Placement |
| 18 | // |
| 19 | // docs/PERFORMANCE.md explains what to look for and which Workers `apply` |
| 20 | // changes. Nothing here runs in CI. |
| 21 | |
| 22 | import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; |
| 23 | import { tmpdir } from "node:os"; |
| 24 | import { join } from "node:path"; |
| 25 | |
| 26 | import { exec, wranglerEnv } from "../deploy/cloudflare.mjs"; |
| 27 | import { ROOT } from "../deploy/stack.mjs"; |
| 28 | |
| 29 | const WRANGLER = join(ROOT, "node_modules/wrangler/bin/wrangler.js"); |
| 30 | const DATABASE = { name: "g1t-repos", id: "f9544c51-c3bf-4621-a96f-8a6d5cf24a97" }; |
| 31 | |
| 32 | /** Each placement tried. Regions near D1's WNAM, one far one as a control. */ |
| 33 | export const VARIANTS = [ |
| 34 | { name: "off", placement: { mode: "off" } }, |
| 35 | { name: "smart", placement: { mode: "smart" } }, |
| 36 | { name: "aws-us-west-1", placement: { region: "aws:us-west-1" } }, |
| 37 | { name: "aws-us-west-2", placement: { region: "aws:us-west-2" } }, |
| 38 | { name: "gcp-us-west1", placement: { region: "gcp:us-west1" } }, |
| 39 | { name: "gcp-us-west2", placement: { region: "gcp:us-west2" } }, |
| 40 | { name: "aws-us-east-1", placement: { region: "aws:us-east-1" } }, |
| 41 | ]; |
| 42 | |
| 43 | /** |
| 44 | * The Workers that read D1 or call services that do, by config: what |
| 45 | * `apply` sets the placement of. The rest (pages, models, og, runner, |
| 46 | * status, docs) serve from the edge or have no data of their own. |
| 47 | */ |
| 48 | export const PLACED = [ |
| 49 | "apps/web/wrangler.jsonc", |
| 50 | "apps/api/wrangler.jsonc", |
| 51 | "apps/sudo/wrangler.jsonc", |
| 52 | "services/actions/wrangler.jsonc", |
| 53 | "services/billing/wrangler.jsonc", |
| 54 | "services/context/wrangler.jsonc", |
| 55 | "services/deployments/wrangler.jsonc", |
| 56 | "services/events/wrangler.jsonc", |
| 57 | "services/identity/wrangler.jsonc", |
| 58 | "services/integrations/wrangler.jsonc", |
| 59 | "services/projects/wrangler.jsonc", |
| 60 | "services/repos/wrangler.jsonc", |
| 61 | "services/search/wrangler.jsonc", |
| 62 | "services/security/wrangler.jsonc", |
| 63 | "services/webhooks/wrangler.jsonc", |
| 64 | "services/work/wrangler.jsonc", |
| 65 | ]; |
| 66 | |
| 67 | const PROBE = ` |
| 68 | const QUERIES = 5; |
| 69 | async function ranIn() { |
| 70 | const trace = await (await fetch("https://cloudflare.com/cdn-cgi/trace")).text(); |
| 71 | return /colo=(\\w+)/.exec(trace)?.[1] ?? null; |
| 72 | } |
| 73 | async function timedQueries(db) { |
| 74 | const ms = []; |
| 75 | let meta = null; |
| 76 | for (let i = 0; i < QUERIES; i++) { |
| 77 | const started = Date.now(); |
| 78 | const result = await db.prepare("SELECT 1 AS one").run(); |
| 79 | ms.push(Date.now() - started); |
| 80 | meta = result.meta; |
| 81 | } |
| 82 | return { ms, region: meta?.served_by_region ?? null, primary: meta?.served_by_primary ?? null, sql_ms: meta?.timings?.sql_duration_ms ?? null }; |
| 83 | } |
| 84 | export default { |
| 85 | async fetch(request, env) { |
| 86 | const { pathname } = new URL(request.url); |
| 87 | if (pathname === "/chain") { |
| 88 | const names = Object.keys(env).filter((key) => key.startsWith("TO_")); |
| 89 | const out = {}; |
| 90 | await Promise.all(names.map(async (name) => { |
| 91 | const started = Date.now(); |
| 92 | try { |
| 93 | const answer = await (await env[name].fetch("https://probe/")).json(); |
| 94 | out[name.slice(3).toLowerCase().replaceAll("_", "-")] = { rpc_ms: Date.now() - started, ran_in: answer.ran_in, primary_ms: answer.primary.ms }; |
| 95 | } catch (error) { |
| 96 | out[name] = { error: String(error) }; |
| 97 | } |
| 98 | })); |
| 99 | return Response.json({ worker: env.NAME, ingress: request.cf?.colo ?? null, ran_in: await ranIn(), chain: out }); |
| 100 | } |
| 101 | const where = await ranIn(); |
| 102 | const primary = await timedQueries(env.DB); |
| 103 | const session = await timedQueries(env.DB.withSession("first-unconstrained")); |
| 104 | return Response.json({ worker: env.NAME, ingress: request.cf?.colo ?? null, ran_in: where, primary, session }); |
| 105 | }, |
| 106 | }; |
| 107 | `; |
| 108 | |
| 109 | function configFor(variant, bindings = []) { |
| 110 | return { |
| 111 | name: `g1t-probe-${variant.name}`, |
| 112 | main: "worker.js", |
| 113 | account_id: "1e6f2cffa3f445920836e8ebe446bb58", |
| 114 | compatibility_date: "2026-09-26", |
| 115 | workers_dev: true, |
| 116 | preview_urls: false, |
| 117 | placement: variant.placement, |
| 118 | vars: { NAME: variant.name }, |
| 119 | d1_databases: [{ binding: "DB", database_name: DATABASE.name, database_id: DATABASE.id }], |
| 120 | services: bindings, |
| 121 | }; |
| 122 | } |
| 123 | |
| 124 | async function wrangler(args, cwd) { |
| 125 | const { code, out } = await exec(process.execPath, [WRANGLER, ...args], { cwd, env: wranglerEnv() }); |
| 126 | return { code, out }; |
| 127 | } |
| 128 | |
| 129 | const URLS = join(tmpdir(), "g1t-placement-probe", "urls.json"); |
| 130 | |
| 131 | async function deploy() { |
| 132 | const urls = {}; |
| 133 | const edge = { name: "edge", placement: { mode: "off" } }; |
| 134 | const bindings = VARIANTS.map((v) => ({ binding: `TO_${v.name.toUpperCase().replaceAll("-", "_")}`, service: `g1t-probe-${v.name}` })); |
| 135 | for (const [variant, extra] of [...VARIANTS.map((v) => [v, []]), [edge, bindings]]) { |
| 136 | const dir = join(tmpdir(), "g1t-placement-probe", variant.name); |
| 137 | rmSync(dir, { recursive: true, force: true }); |
| 138 | mkdirSync(dir, { recursive: true }); |
| 139 | writeFileSync(join(dir, "worker.js"), PROBE); |
| 140 | writeFileSync(join(dir, "wrangler.json"), JSON.stringify(configFor(variant, extra), null, 2)); |
| 141 | process.stdout.write(`deploying g1t-probe-${variant.name} ${JSON.stringify(variant.placement)} … `); |
| 142 | const { code, out } = await wrangler(["deploy", "--config", "wrangler.json"], dir); |
| 143 | const url = /https:\/\/g1t-probe-[\w-]+\.[\w-]+\.workers\.dev/.exec(out)?.[0]; |
| 144 | console.log(code === 0 ? url ?? "deployed (no URL printed)" : "FAILED"); |
| 145 | if (code !== 0) console.log(out.split("\n").slice(-15).join("\n")); |
| 146 | if (url) urls[variant.name] = url; |
| 147 | } |
| 148 | writeFileSync(URLS, JSON.stringify(urls, null, 2)); |
| 149 | console.log("\nSmart Placement needs traffic from several places and up to 15 minutes before it moves anything;"); |
| 150 | console.log("the region variants are placed at once. Wait a minute, then: node scripts/perf/placement-probe.mjs measure"); |
| 151 | } |
| 152 | |
| 153 | const median = (values) => { |
| 154 | const sorted = [...values].filter((v) => typeof v === "number").sort((a, b) => a - b); |
| 155 | return sorted.length ? sorted[Math.floor(sorted.length / 2)] : null; |
| 156 | }; |
| 157 | |
| 158 | async function timedFetch(url) { |
| 159 | const started = performance.now(); |
| 160 | const response = await fetch(url, { headers: { "cache-control": "no-cache" } }); |
| 161 | const ttfb = performance.now() - started; |
| 162 | const body = await response.json(); |
| 163 | return { ttfb: Math.round(ttfb), placement: response.headers.get("cf-placement"), body }; |
| 164 | } |
| 165 | |
| 166 | async function measure() { |
| 167 | const urls = JSON.parse(readFileSync(URLS, "utf8")); |
| 168 | const rows = []; |
| 169 | for (const variant of VARIANTS) { |
| 170 | const url = urls[variant.name]; |
| 171 | if (!url) continue; |
| 172 | await timedFetch(url).catch(() => null); |
| 173 | const runs = []; |
| 174 | for (let i = 0; i < 7; i++) runs.push(await timedFetch(url)); |
| 175 | const last = runs.at(-1); |
| 176 | rows.push({ |
| 177 | variant: variant.name, |
| 178 | "cf-placement": last.placement ?? "", |
| 179 | "ran in": last.body.ran_in, |
| 180 | "ttfb ms (p50)": median(runs.map((r) => r.ttfb)), |
| 181 | "D1 primary ms/query (p50)": median(runs.flatMap((r) => r.body.primary.ms)), |
| 182 | "D1 session ms/query (p50)": median(runs.flatMap((r) => r.body.session.ms)), |
| 183 | "session served by": `${last.body.session.region ?? "?"}${last.body.session.primary ? " (primary)" : ""}`, |
| 184 | }); |
| 185 | } |
| 186 | console.table(rows); |
| 187 | if (urls.edge) { |
| 188 | await timedFetch(`${urls.edge}/chain`).catch(() => null); |
| 189 | const chains = []; |
| 190 | for (let i = 0; i < 5; i++) chains.push(await timedFetch(`${urls.edge}/chain`)); |
| 191 | const names = Object.keys(chains[0].body.chain); |
| 192 | console.log(`\nFrom g1t-probe-edge (runs in ${chains[0].body.ran_in}) through a service binding, as the site calls a service:`); |
| 193 | console.table( |
| 194 | names.map((name) => ({ |
| 195 | target: name, |
| 196 | "target ran in": chains.at(-1).body.chain[name].ran_in, |
| 197 | "rpc ms (p50)": median(chains.map((c) => c.body.chain[name].rpc_ms)), |
| 198 | "its D1 ms/query (p50)": median(chains.flatMap((c) => c.body.chain[name].primary_ms ?? [])), |
| 199 | })), |
| 200 | ); |
| 201 | } |
| 202 | console.log("\nPick the region with the lowest D1 primary ms/query (the database's neighbour), then:"); |
| 203 | console.log(` node scripts/perf/placement-probe.mjs apply '{"region":"<it>"}'`); |
| 204 | } |
| 205 | |
| 206 | async function remove() { |
| 207 | for (const name of [...VARIANTS.map((v) => v.name), "edge"]) { |
| 208 | const { code } = await wrangler(["delete", "--name", `g1t-probe-${name}`, "--force"], ROOT); |
| 209 | console.log(`g1t-probe-${name}: ${code === 0 ? "deleted" : "not deleted (already gone?)"}`); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | /** Sets `"placement"` in every config in PLACED, keeping the line's place. */ |
| 214 | export function withPlacement(text, placement) { |
| 215 | const fields = Object.entries(placement).map(([key, value]) => `"${key}": ${JSON.stringify(value)}`); |
| 216 | const line = `"placement": { ${fields.join(", ")} },`; |
| 217 | if (/"placement":\s*\{[^}]*\},?/.test(text)) return text.replace(/"placement":\s*\{[^}]*\},?/, line); |
| 218 | return text.replace(/(\r?\n)([ \t]*)("compatibility_date":[^\r\n]*)/, (_, nl, indent, date) => `${nl}${indent}${date}${nl}${indent}${line}`); |
| 219 | } |
| 220 | |
| 221 | function apply(json) { |
| 222 | const placement = JSON.parse(json); |
| 223 | for (const file of PLACED) { |
| 224 | const path = join(ROOT, file); |
| 225 | const before = readFileSync(path, "utf8"); |
| 226 | const after = withPlacement(before, placement); |
| 227 | if (after !== before) writeFileSync(path, after); |
| 228 | console.log(`${file}: ${after === before ? "unchanged" : JSON.stringify(placement)}`); |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | const [command, argument] = process.argv.slice(2); |
| 233 | if (process.argv[1]?.replaceAll("\\", "/").endsWith("scripts/perf/placement-probe.mjs")) { |
| 234 | if (command === "deploy") await deploy(); |
| 235 | else if (command === "measure") await measure(); |
| 236 | else if (command === "delete") await remove(); |
| 237 | else if (command === "apply" && argument) apply(argument); |
| 238 | else { |
| 239 | console.log("usage: node scripts/perf/placement-probe.mjs deploy | measure | delete | apply '<placement json>'"); |
| 240 | process.exitCode = 1; |
| 241 | } |
| 242 | } |