| 195 | 195 | | | A branch's log, the branch list, a file by branch and path | repos' data-centre cache | until the repository's refs change (`refs_version`), 5 min at most | only while no handed-out push credential is live; by commit hash for good (docs/ARTIFACTS.md R9) | |
| 196 | 196 | | | A target branch's history, for mergeability | the repos isolate | 60 s, per target head | 100 pull requests checked after a push walk it once (R10) | |
| 197 | 197 | | | Git store credentials | repos isolate and KV | reused 50 min (1 h tokens); 3 min for ones handed out | (R3) | |
| 198 | + | | A file's highlighted lines (blob, blame) | the site's isolate (about 8 MB), then the data centre's cache (`content.g1t.internal/highlight-lines/`) | for good (30 days in the data centre) | by SHA-256 of the language and the text, nothing else: the same text on any branch, commit or page is one entry. `HIGHLIGHT_VERSION` in `lib/highlight.server.ts` is in the key; bump it when the theme, grammars, Shiki or `linesToHtml` change. Nothing kept for a file without a language or over 200,000 characters | |
| 199 | + | | A pull request's first highlighted files | as above (`highlight-diff/`, about 4 MB per isolate) | for good | by the language and each line's side and text (`diffContent` in `lib/diff.ts`); line numbers and the path are not in it | |
| 200 | + | | Parsed markdown | the isolate, or the browser tab (400,000 characters of source, about 8 MB) | until pushed out | by the text and the repository its references point into (`components/markdown.tsx`); the tree is rendered with the page's components each time | |
| 201 | + | | React Router's route tables | the isolate | its life | the build is loaded once and the handler made once (`workers/app.ts`); development still reloads it per request | |
| 198 | 202 | | |
| 199 | 203 | | ## Server-Timing |
| 200 | 204 | | |
| ⋯ |
| 358 | 362 | | | Crawler, nothing moved | 460 to 570 ms | not yet measured on production | |
| 359 | 363 | | | Browser, first byte | 115 to 160 ms (`total`), 200 to 245 ms measured from Colorado | unchanged: the page does not wait for any of this | |
| 360 | 364 | | |
| 365 | + | ## CPU per page (2026-10-09) |
| 366 | + | |
| 367 | + | Workers bill CPU time past 30 million ms a cycle. From 1 to 9 October the |
| 368 | + | site (`g1t`) used 83.3 million CPU-ms over 1.93 million requests, about 43 |
| 369 | + | ms a request and seven tenths of all g1t's Workers CPU; `g1t-repos` used |
| 370 | + | 28.3 million over 6.58 million (about 4 ms). Signed-out pages are kept |
| 371 | + | for 30 s (above), but a crawler reads each file once, so most of its |
| 372 | + | requests render. |
| 373 | + | |
| 374 | + | ### Measuring it |
| 375 | + | |
| 376 | + | `Server-Timing` cannot show CPU: a Worker's clock does not move while it |
| 377 | + | computes, so `total;dur=0` on a page that rendered for 20 ms is normal. |
| 378 | + | Measure locally instead, with the built site and fake services: |
| 379 | + | |
| 380 | + | 1. `npm run build -w apps/web`. |
| 381 | + | 2. Load `build/server/index.js` in Node with `cloudflare:workers` pointed |
| 382 | + | at a stub whose `env` has a service binding per service, answering |
| 383 | + | `POST /rpc/<method>` from fixtures. A page's `.data` (fetched signed |
| 384 | + | out from production) decoded with React Router's turbo-stream decoder |
| 385 | + | gives realistic answers; files and READMEs can come from the working |
| 386 | + | tree. |
| 387 | + | 3. Call the worker's `fetch` with a crawler's user agent (the whole page |
| 388 | + | renders before the answer) and read `process.cpuUsage()` over 100 |
| 389 | + | requests after a few to warm up. Run Node with `--single-threaded` so |
| 390 | + | garbage collection and compilation count on the one thread, as in a |
| 391 | + | Worker; on Windows `cpuUsage` moves in 15.6 ms steps, so divide a long |
| 392 | + | run, never time one request. |
| 393 | + | 4. `node --cpu-prof` on the same loop says where it goes. |
| 394 | + | |
| 395 | + | ### Where it went |
| 396 | + | |
| 397 | + | Profiled with fixtures from flagon-io/g1t: |
| 398 | + | |
| 399 | + | | Page | CPU a request | Where | |
| 400 | + | | --- | --- | --- | |
| 401 | + | | A 130-line TypeScript file | 30 ms | 63% highlighting (Shiki's tokenizer), 17% rendering | |
| 402 | + | | A 1,800-line TSX file (2.4 MB page) | 250 ms | 85% highlighting; the rest rendering and encoding the page | |
| 403 | + | | The Files page with a README | 32 ms | 46% parsing the README (remark, rehype-raw, sanitize, the plugins) | |
| 404 | + | | Any page | 1 to 1.5 ms more | React Router rebuilt its route tables for every request: given the build as a function, it wraps every route for the timings and flattens and ranks the route table again each time | |
| 405 | + | | `package-lock.json` (3.8 MB page, too large to highlight) | 170 ms | rendering one row per line and the file again in the page's data | |
| 406 | + | |
| 407 | + | The CSP nonce, `isbot`, Server-Timing bookkeeping and the signed-out |
| 408 | + | cache's own work were each under 1% of a page's CPU in the profiles. |
| 409 | + | |
| 410 | + | ### What changed |
| 411 | + | |
| 412 | + | - **Highlighting is kept by content** (`lib/highlight.server.ts`, |
| 413 | + | `lib/content-cache.ts`): the isolate first, then the data centre's |
| 414 | + | cache, then Shiki. The key is a SHA-256 of the language and the text, |
| 415 | + | with a version, so a file that is the same on another branch or commit, |
| 416 | + | in blame, or for the next crawler is highlighted once per data centre. |
| 417 | + | A pull request's first files are kept the same way. |
| 418 | + | - **Markdown is parsed once per text** (`lib/markdown-tree.ts`, |
| 419 | + | `components/markdown.tsx`): the steps `react-markdown` runs on every |
| 420 | + | render are split, and the parsed tree is kept per isolate (and per |
| 421 | + | browser tab). `lib/markdown-tree.test.ts` renders README.md, this file |
| 422 | + | and a set of edge cases (raw HTML, scripts, `javascript:` links, alerts, |
| 423 | + | references) both ways and checks the HTML is identical. |
| 424 | + | - **The request handler is made once per isolate** (`workers/app.ts`). |
| 425 | + | - Shiki's module is asked for once per isolate, not on every highlight. |
| 426 | + | |
| 427 | + | ### Measured |
| 428 | + | |
| 429 | + | Locally, CPU a request, median of three runs of 100 requests, signed out |
| 430 | + | as a crawler. "Seen" is a file or README this isolate (or data centre) |
| 431 | + | has highlighted or parsed before; "new" is one it has not. |
| 432 | + | |
| 433 | + | | Page | Before | After, seen | After, new | |
| 434 | + | | --- | --- | --- | --- | |
| 435 | + | | `/` (landing) | 12.7 ms | 10.6 ms | 10.6 ms | |
| 436 | + | | `/pricing` | 4.8 ms | 3.3 ms | 3.3 ms | |
| 437 | + | | Files, root with README.md | 30.8 ms | 9.5 ms | 23.7 ms | |
| 438 | + | | Files, `docs/` (a 30,000-character README) | 44.7 ms | 8.9 ms | 33.1 ms | |
| 439 | + | | Files, no README | 9.2 ms | 7.2 ms | 6.9 ms | |
| 440 | + | | A 130-line TypeScript file | 27.3 ms | 7.8 ms | 24.5 ms | |
| 441 | + | | A 1,800-line TSX file | 255.6 ms | 41.3 ms | 246.7 ms | |
| 442 | + | | A 560-line Rust file | 34.7 ms | 14.8 ms | 30.0 ms | |
| 443 | + | | A markdown file's source | 18.4 ms | 11.6 ms | 18.8 ms | |
| 444 | + | | `package-lock.json` | 200.2 ms | 134.1 ms | 127.8 ms | |
| 445 | + | |
| 446 | + | A pull request's first screens: a 288-line diff took 18.8 ms to |
| 447 | + | highlight and 0.15 ms to read back from the data centre's cache. Hashing |
| 448 | + | the text for the key is part of every "new" figure above. Small |
| 449 | + | differences (a few ms) are within the noise of these runs; the large |
| 450 | + | saving on `package-lock.json`, which nothing here caches, is partly that |
| 451 | + | noise and partly less garbage per request. |
| 452 | + | |
| 453 | + | What is left on large files is rendering: a row per line, and the same |
| 454 | + | lines again in the page's data for hydration. Files over 200,000 |
| 455 | + | characters (lock files) are not highlighted and still cost about 130 ms |
| 456 | + | for a crawler. |
| 457 | + | |
| 361 | 458 | | ## Client navigation |
| 362 | 459 | | |
| 363 | 460 | | - `<Link prefetch="intent">` on the sidebar, project tabs, breadcrumbs, |