Public: anyone can read this; the crew can change it.
A stranger's way to a forge answers under budget: /pricing, /forges/new and /claim join the page-load budget #11414
Check · Review · Land · Live done — Landed and live in production.
torque/forge:torque/11410-a-stranger-s-way-to-a-forge-answers-unde → main 1 commit fork
2 files
changed +29
−8
src/perf/page_budget.ts +7
| @@ -20,6 +20,8 @@ export interface PageBudget { | ||
| 20 | 20 | readonly medianMs: number; |
| 21 | 21 | /** One warm request runs at most this many SQL statements. */ |
| 22 | 22 | readonly statements: number; |
| 23 | /** Walked with no cookie: a stranger's page, not a signed-in person's. */ | |
| 24 | readonly signedOut?: true; | |
| 23 | 25 | } |
| 24 | 26 | |
| 25 | 27 | /** Statement limits are today's count, signed in, plus a little: lower them as pages get cheaper, raise one only on purpose. */ |
| @@ -29,6 +31,11 @@ export const PAGE_BUDGETS: readonly PageBudget[] = [ | ||
| 29 | 31 | { page: "map", pattern: "/map", medianMs: 50, statements: 21 }, |
| 30 | 32 | { page: "an app page", pattern: "/:owner/:name", medianMs: 50, statements: 49 }, |
| 31 | 33 | { page: "changelog", pattern: "/changelog", medianMs: 50, statements: 20 }, |
| 34 | // A stranger's way to a forge (#11410): walked signed out. `/claim` is | |
| 35 | // measured on a spawned forge with a live link, as the child's owner sees it. | |
| 36 | { page: "pricing", pattern: "/pricing", medianMs: 50, statements: 10, signedOut: true }, | |
| 37 | { page: "new forge", pattern: "/forges/new", medianMs: 50, statements: 14, signedOut: true }, | |
| 38 | { page: "claim", pattern: "/claim", medianMs: 50, statements: 14, signedOut: true }, | |
| 32 | 39 | ]; |
| 33 | 40 | |
| 34 | 41 | /** The path a budget's pattern names for one repository: `:owner` and `:name` filled, everything else as written. */ |
test/page-budget.test.ts +22 −8
| @@ -15,6 +15,8 @@ import { tmpdir } from "node:os"; | ||
| 15 | 15 | import { join } from "node:path"; |
| 16 | 16 | import { commitToBranch, createRepo } from "../src/actions/repos.ts"; |
| 17 | 17 | import { createApp, type App } from "../src/app.ts"; |
| 18 | import { createOrg } from "../src/core/orgs.ts"; | |
| 19 | import { mintClaim } from "../src/spawn/claim.ts"; | |
| 18 | 20 | import { createIssue } from "../src/core/issues.ts"; |
| 19 | 21 | import { getRepo } from "../src/core/repos.ts"; |
| 20 | 22 | import { createUser, type User } from "../src/core/users.ts"; |
| @@ -73,6 +75,11 @@ beforeAll(async () => { | ||
| 73 | 75 | const made = createIssue(app.db, { repoId: notes.id, authorId: bob.id, title: `Issue ${i}`, body: "body" }); |
| 74 | 76 | if (!made.ok) throw new Error(made.error); |
| 75 | 77 | } |
| 78 | // A spawned forge with a live claim link, so `/claim` shows its form. | |
| 79 | const kin = createOrg(app.db, "kin", alice.id); | |
| 80 | if (!kin.ok) throw new Error(kin.error); | |
| 81 | await Bun.write(join(root, "parent.json"), JSON.stringify({ org: "kin" })); | |
| 82 | mintClaim(app.db, kin.value.id); | |
| 76 | 83 | servers = await serve(app); |
| 77 | 84 | const signin = await fetch(`${servers.url}/signin`, { |
| 78 | 85 | method: "POST", |
| @@ -90,8 +97,8 @@ afterAll(() => { | ||
| 90 | 97 | rmSync(root, { recursive: true, force: true }); |
| 91 | 98 | }); |
| 92 | 99 | |
| 93 | /** Every page is walked as alice, signed in: the map asks for a viewer. */ | |
| 94 | const INIT = (): RequestInit => ({ headers: { cookie }, redirect: "manual" }); | |
| 100 | /** Pages are walked as alice, signed in (the map asks for a viewer); a stranger's pages (`signedOut`) with no cookie. */ | |
| 101 | const init = (b: PageBudget): RequestInit => (b.signedOut ? { redirect: "manual" } : { headers: { cookie }, redirect: "manual" }); | |
| 95 | 102 | |
| 96 | 103 | function url(b: PageBudget): string { |
| 97 | 104 | return `${servers.url}${pagePath(b, "alice", "notes")}`; |
| @@ -99,14 +106,14 @@ function url(b: PageBudget): string { | ||
| 99 | 106 | |
| 100 | 107 | /** One request warms; then the fewest statements of `COUNTED` warm requests is the count, so a background tick's queries do not land on a page. */ |
| 101 | 108 | async function count(b: PageBudget): Promise<{ status: number; statements: number; phases: PageMeasure["phases"] }> { |
| 102 | const first = await fetch(url(b), INIT()); | |
| 109 | const first = await fetch(url(b), init(b)); | |
| 103 | 110 | await first.arrayBuffer(); |
| 104 | 111 | let status = first.status; |
| 105 | 112 | let header = ""; |
| 106 | 113 | let fewest = Number.POSITIVE_INFINITY; |
| 107 | 114 | for (let i = 0; i < COUNTED; i++) { |
| 108 | 115 | const n = await statements(app.db, async () => { |
| 109 | const res = await fetch(url(b), INIT()); | |
| 116 | const res = await fetch(url(b), init(b)); | |
| 110 | 117 | await res.arrayBuffer(); |
| 111 | 118 | status = res.status; |
| 112 | 119 | header = res.headers.get("server-timing") ?? ""; |
| @@ -119,12 +126,12 @@ async function count(b: PageBudget): Promise<{ status: number; statements: numbe | ||
| 119 | 126 | |
| 120 | 127 | /** One warm-up, then `TIMED` timed requests; the phases are the last request's. */ |
| 121 | 128 | async function time(b: PageBudget): Promise<{ samples: number[]; phases: PageMeasure["phases"] }> { |
| 122 | await (await fetch(url(b), INIT())).arrayBuffer(); | |
| 129 | await (await fetch(url(b), init(b))).arrayBuffer(); | |
| 123 | 130 | const samples: number[] = []; |
| 124 | 131 | let header = ""; |
| 125 | 132 | for (let i = 0; i < TIMED; i++) { |
| 126 | 133 | const t = performance.now(); |
| 127 | const res = await fetch(url(b), INIT()); | |
| 134 | const res = await fetch(url(b), init(b)); | |
| 128 | 135 | await res.arrayBuffer(); |
| 129 | 136 | samples.push(performance.now() - t); |
| 130 | 137 | header = res.headers.get("server-timing") ?? ""; |
| @@ -132,14 +139,21 @@ async function time(b: PageBudget): Promise<{ samples: number[]; phases: PageMea | ||
| 132 | 139 | return { samples, phases: parsePhases(header) }; |
| 133 | 140 | } |
| 134 | 141 | |
| 135 | test("the budget table names five pages, each once, with a median and a statement limit", () => { | |
| 136 | expect(PAGE_BUDGETS.map((b) => b.pattern)).toEqual(["/", "/explore", "/map", "/:owner/:name", "/changelog"]); | |
| 142 | test("the budget table names eight pages, each once, with a median and a statement limit", () => { | |
| 143 | expect(PAGE_BUDGETS.map((b) => b.pattern)).toEqual([ | |
| 144 | "/", "/explore", "/map", "/:owner/:name", "/changelog", "/pricing", "/forges/new", "/claim", | |
| 145 | ]); | |
| 137 | 146 | for (const b of PAGE_BUDGETS) { |
| 138 | 147 | expect(b.medianMs).toBeLessThanOrEqual(50); |
| 139 | 148 | expect(Number.isInteger(b.statements) && b.statements > 0).toBe(true); |
| 140 | 149 | } |
| 141 | 150 | }); |
| 142 | 151 | |
| 152 | test("a stranger's way to a forge is on the list, walked signed out", () => { | |
| 153 | const strangers = PAGE_BUDGETS.filter((b) => b.signedOut).map((b) => b.pattern); | |
| 154 | expect(strangers).toEqual(["/pricing", "/forges/new", "/claim"]); | |
| 155 | }); | |
| 156 | ||
| 143 | 157 | test("each first page runs no more SQL statements warm than its budget", async () => { |
| 144 | 158 | const findings: string[] = []; |
| 145 | 159 | const table: string[] = []; |
new above
offline