From d207f2d85fe48a398bb088ebdbe733182cb70982 Mon Sep 17 00:00:00 2001 From: Elia <83713217+eliahilse@users.noreply.github.com> Date: Thu, 20 Aug 2026 18:52:37 +0200 Subject: [PATCH] feat(web): three Hyperfly rows that each add one thing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relabels the benchmark rows so the progression is legible rather than implied: Hyperfly is the schema-compiled layout alone, Hyperfly + Brotli puts generic compression on top of it, and Hyperfly Profiled adds a dictionary trained on the route's own traffic. The middle row is new — the site previously jumped from bare layout to the full stack, which hid which of the two later stages was doing the work. It turns out to be worth showing. On events Brotli takes 55 bytes off the columnar output and the profile takes 1 231, which is the whole argument for training: a compressor sees one response and has to rediscover the recurring user agents every time. On orders Brotli costs two bytes rather than saving any, and on candles the last two rows are identical to the byte because that route has no string column for a dictionary to key on. All three are left visible. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_hf1 --- apps/bench/src/profiles.ts | 13 +++++++++---- apps/web/app/benchmark.tsx | 40 +++++++++++++++++++------------------- apps/web/app/page.tsx | 15 +++++++------- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/apps/bench/src/profiles.ts b/apps/bench/src/profiles.ts index 6c5dac2..89131f2 100644 --- a/apps/bench/src/profiles.ts +++ b/apps/bench/src/profiles.ts @@ -25,6 +25,7 @@ export interface CorpusResult { brotli: number; protobuf: number; columnar: number; + columnarBrotli: number; profiled: number; full: number; dictEntries: number; @@ -60,6 +61,7 @@ export function measureCorpus(suite: CorpusSuite): CorpusResult { let br = 0; let proto = 0; let col = 0; + let colBr = 0; let prof = 0; let full = 0; @@ -70,7 +72,9 @@ export function measureCorpus(suite: CorpusSuite): CorpusResult { br += brotli(j, constants.BROTLI_MODE_TEXT); proto += suite.proto.encode(message).length; - col += columnar.encode(message as never).length; + const c = columnar.encode(message as never); + col += c.length; + colBr += brotli(c, constants.BROTLI_MODE_GENERIC); const p = profiled.encode(message as never); prof += p.length; full += brotli(p, constants.BROTLI_MODE_GENERIC); @@ -98,6 +102,7 @@ export function measureCorpus(suite: CorpusSuite): CorpusResult { brotli: mean(br), protobuf: mean(proto), columnar: mean(col), + columnarBrotli: mean(colBr), profiled: mean(prof), full: mean(full), dictEntries: columns.reduce((sum, c) => sum + c.dict.length, 0), @@ -120,12 +125,12 @@ export function runProfileSuite(suites: CorpusSuite[] = defaultSuites()): Corpus const results = suites.map(measureCorpus); console.log("\ncorpora — per-message averages, profile trained on the route's own traffic"); - console.log(" route msgs json gzip br4 proto col prof full dictionary"); + console.log(" route msgs json br4 proto col col+br prof full dictionary"); for (const r of results) { const cell = (v: number) => String(v).padStart(6); console.log( - ` ${r.route.padEnd(17)} ${String(r.messages).padStart(4)} ${cell(r.json)} ${cell(r.gzip)} ` + - `${cell(r.brotli)} ${cell(r.protobuf)} ${cell(r.columnar)} ${cell(r.profiled)} ${cell(r.full)} ` + + ` ${r.route.padEnd(17)} ${String(r.messages).padStart(4)} ${cell(r.json)} ` + + `${cell(r.brotli)} ${cell(r.protobuf)} ${cell(r.columnar)} ${cell(r.columnarBrotli)} ${cell(r.profiled)} ${cell(r.full)} ` + `${r.dictEntries} entries / ${r.dictBytes}B` + (r.breakEven ? ` (pays for itself after ${r.breakEven} requests)` : ""), ); diff --git a/apps/web/app/benchmark.tsx b/apps/web/app/benchmark.tsx index cb3bda9..5b6baea 100644 --- a/apps/web/app/benchmark.tsx +++ b/apps/web/app/benchmark.tsx @@ -25,11 +25,11 @@ const PAYLOADS: Payload[] = [ { label: "JSON + gzip", bytes: 2503, kind: "generic" }, { label: "JSON + Brotli — edge q4", bytes: 2512, kind: "generic" }, { label: "Protobuf", bytes: 7190, kind: "binary" }, - { label: "Hyperfly · columnar", bytes: 2109, kind: "hyperfly" }, - { label: "Hyperfly · profiled", bytes: 896, kind: "profile" }, - { label: "Hyperfly · full", bytes: 823, kind: "full" }, + { label: "Hyperfly", bytes: 2109, kind: "hyperfly" }, + { label: "Hyperfly + Brotli", bytes: 2054, kind: "profile" }, + { label: "Hyperfly Profiled", bytes: 823, kind: "full" }, ], - note: "An audit log repeats itself across requests, not within one: the same user agents, the same actor emails, the same resource ids, request after request. A compressor only ever sees one response and has to rediscover them every time. The profile learned 692 values once, and pays for itself after ten requests.", + note: "An audit log repeats itself across requests, not within one: the same user agents, the same actor emails, the same resource ids, request after request. A compressor only ever sees one response and has to rediscover them every time — which is why Brotli takes 55 bytes off and the profile takes 1 231. It learned 692 values once, and pays for itself after ten requests.", }, { route: "GET /v1/devices", @@ -39,11 +39,11 @@ const PAYLOADS: Payload[] = [ { label: "JSON + gzip", bytes: 1473, kind: "generic" }, { label: "JSON + Brotli — edge q4", bytes: 1422, kind: "generic" }, { label: "Protobuf", bytes: 2007, kind: "binary" }, - { label: "Hyperfly · columnar", bytes: 896, kind: "hyperfly" }, - { label: "Hyperfly · profiled", bytes: 705, kind: "profile" }, - { label: "Hyperfly · full", bytes: 638, kind: "full" }, + { label: "Hyperfly", bytes: 896, kind: "hyperfly" }, + { label: "Hyperfly + Brotli", bytes: 818, kind: "profile" }, + { label: "Hyperfly Profiled", bytes: 638, kind: "full" }, ], - note: "An enum with six members is an index, not a string. Bounded integers ship as offsets from their declared minimum and booleans pack into bitmaps — that is the columnar row. The profile then learns the fleet: the device ids that recur on every page.", + note: "An enum with six members is an index, not a string. Bounded integers ship as offsets from their declared minimum and booleans pack into bitmaps — that is the first row, before anything has been compressed or learned. The profile then learns the fleet: the device ids that recur on every page.", }, { route: "GET /v1/orders/:id", @@ -53,11 +53,11 @@ const PAYLOADS: Payload[] = [ { label: "JSON + gzip", bytes: 423, kind: "generic" }, { label: "JSON + Brotli — edge q4", bytes: 408, kind: "generic" }, { label: "Protobuf", bytes: 388, kind: "binary" }, - { label: "Hyperfly · columnar", bytes: 271, kind: "hyperfly" }, - { label: "Hyperfly · profiled", bytes: 184, kind: "profile" }, - { label: "Hyperfly · full", bytes: 188, kind: "full" }, + { label: "Hyperfly", bytes: 271, kind: "hyperfly" }, + { label: "Hyperfly + Brotli", bytes: 273, kind: "profile" }, + { label: "Hyperfly Profiled", bytes: 188, kind: "full" }, ], - note: "The single-entity response, and the case a general compressor handles worst: under a kilobyte there is nothing yet to build a window from. Note that full is larger than profiled here — at 184 bytes, Brotli's framing costs more than it saves, so the right configuration for this route is to skip it.", + note: "The single-entity response, and the case a general compressor handles worst: under a kilobyte there is nothing yet to build a window from. Brotli actually costs two bytes here rather than saving any — at this size its framing outweighs what it finds. What does work is knowing the catalogue in advance.", }, { route: "GET /v1/feed", @@ -67,11 +67,11 @@ const PAYLOADS: Payload[] = [ { label: "JSON + gzip", bytes: 2307, kind: "generic" }, { label: "JSON + Brotli — edge q4", bytes: 2294, kind: "generic" }, { label: "Protobuf", bytes: 4396, kind: "binary" }, - { label: "Hyperfly · columnar", bytes: 1908, kind: "hyperfly" }, - { label: "Hyperfly · profiled", bytes: 1536, kind: "profile" }, - { label: "Hyperfly · full", bytes: 1535, kind: "full" }, + { label: "Hyperfly", bytes: 1908, kind: "hyperfly" }, + { label: "Hyperfly + Brotli", bytes: 1902, kind: "profile" }, + { label: "Hyperfly Profiled", bytes: 1535, kind: "full" }, ], - note: "Prose is the hard case: the bodies are genuinely new every time and nothing can invent redundancy that is not there. What does recur are the authors, so that is what the profile takes. Full and profiled land within a byte of each other because the dictionary already removed what Brotli was living on.", + note: "Prose is the hard case: the bodies are genuinely new every time and nothing can invent redundancy that is not there. What does recur are the authors, so that is what the profile takes. This is the narrowest margin on the page, and it is the honest one to look at first.", }, { route: "GET /v1/candles", @@ -81,11 +81,11 @@ const PAYLOADS: Payload[] = [ { label: "JSON + gzip", bytes: 928, kind: "generic" }, { label: "JSON + Brotli — edge q4", bytes: 842, kind: "generic" }, { label: "Protobuf", bytes: 2034, kind: "binary" }, - { label: "Hyperfly · columnar", bytes: 496, kind: "hyperfly" }, - { label: "Hyperfly · profiled", bytes: 496, kind: "profile" }, - { label: "Hyperfly · full", bytes: 372, kind: "full" }, + { label: "Hyperfly", bytes: 496, kind: "hyperfly" }, + { label: "Hyperfly + Brotli", bytes: 372, kind: "profile" }, + { label: "Hyperfly Profiled", bytes: 372, kind: "full" }, ], - note: "Timestamps become deltas and exact-decimal prices travel as integer mantissas rather than eight raw bytes. The profile changes nothing at all here, and the row is left in to show it: this route's only string sits outside the array, so there is no column for a dictionary to key on.", + note: "Timestamps become deltas and exact-decimal prices travel as integer mantissas rather than eight raw bytes. The last two rows are identical to the byte, and the row is left in to show it: this route's only string sits outside the array, so there is no column for a dictionary to key on and training buys nothing at all.", }, ]; diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index d4b9a2a..36156f1 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -164,13 +164,14 @@ export default function Home() {

Bytes per message, averaged over 500 independent responses per route, measured - with the TypeScript reference implementation — reproduce with `bun run bench`. The - profile is trained on the route's own traffic, which is what a deployment does; - it is an out-of-band artifact, and the repo reports its size and how many requests - it takes to pay for itself (ten for events, thirty-five for orders). The Brotli row - is q4, the level edges actually run on dynamic responses. Protobuf gets proper - enums and int64. Corpora are synthetic but shaped like real routes: a fixed device - fleet, a fixed product catalogue, a recurring cast of authors. + with the TypeScript reference implementation — reproduce with `bun run bench`. Each + Hyperfly row adds exactly one thing: schema-compiled layout, then generic + compression on top of it, then a dictionary trained on the route's own traffic. + That dictionary is an out-of-band artifact; the repo reports its size and how many + requests it takes to pay for itself — ten for events, thirty-five for orders. The + Brotli rows are q4, the level edges actually run on dynamic responses. Protobuf + gets proper enums and int64. Corpora are synthetic but shaped like real routes: a + fixed device fleet, a fixed product catalogue, a recurring cast of authors.