diff --git a/apps/bench/src/corpora/events.ts b/apps/bench/src/corpora/events.ts new file mode 100644 index 0000000..d312121 --- /dev/null +++ b/apps/bench/src/corpora/events.ts @@ -0,0 +1,194 @@ +import { z } from "zod"; +import { intIn, mulberry32, pick } from "../prng.js"; + +export const EventResponse = z.object({ + route: z.literal("events"), + cursor: z.string().nullable(), + events: z.array( + z.object({ + id: z.string(), + type: z.enum([ + "user.login", + "user.logout", + "file.upload", + "file.delete", + "billing.charge", + "billing.refund", + "project.create", + "project.archive", + "member.invite", + "member.remove", + ]), + actorId: z.string(), + actorEmail: z.string(), + resourceId: z.string(), + resourceType: z.enum(["user", "file", "project", "invoice", "member", "apikey"]), + ip: z.string(), + userAgent: z.string(), + region: z.enum(["us-east", "us-west", "eu-central", "eu-west", "ap-south", "ap-northeast", "sa-east", "af-south"]), + durationMs: z.number().int().min(0).max(60000), + ok: z.boolean(), + at: z.number().int().min(0), + }), + ), +}); + +type ResourceType = z.output["events"][number]["resourceType"]; + +const EVENT_TYPES = [ + "user.login", + "user.logout", + "file.upload", + "file.delete", + "billing.charge", + "billing.refund", + "project.create", + "project.archive", + "member.invite", + "member.remove", +] as const; + +const RESOURCE_TYPES = ["user", "file", "project", "invoice", "member", "apikey"] as const; + +const RESOURCE_PREFIX: Record = { + user: "usr", + file: "file", + project: "proj", + invoice: "inv", + member: "mem", + apikey: "key", +}; + +const REGIONS = ["us-east", "us-west", "eu-central", "eu-west", "ap-south", "ap-northeast", "sa-east", "af-south"] as const; + +const FIRST_NAMES = [ + "Ada", + "Grace", + "Alan", + "Edsger", + "Barbara", + "Donald", + "Radia", + "Ken", + "Margaret", + "Linus", + "Katherine", + "Vint", + "Tim", + "Claude", + "Hedy", + "Marvin", + "John", + "Frances", + "Dennis", + "Brian", +]; + +const LAST_NAMES = [ + "Lovelace", + "Hopper", + "Turing", + "Dijkstra", + "Liskov", + "Knuth", + "Perlman", + "Thompson", + "Hamilton", + "Torvalds", + "Johnson", + "Cerf", + "Berners-Lee", + "Shannon", + "Lamarr", + "Minsky", + "Backus", + "Allen", + "Ritchie", + "Kernighan", +]; + +const DOMAINS = ["acme.io", "globex.com", "initech.dev", "umbrella.co", "hooli.com", "starkindustries.com", "wayneenterprises.com", "piedpiper.io"]; + +const USER_AGENTS = [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0", + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", + "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/124.0.0.0 Safari/537.36", + "curl/8.6.0", + "PostmanRuntime/7.36.3", + "okhttp/4.12.0", + "python-requests/2.31.0", +]; + +function hexId(rng: () => number, len: number): string { + let out = ""; + for (let i = 0; i < len; i++) out += Math.floor(rng() * 16).toString(16); + return out; +} + +function skewedPick(rng: () => number, items: readonly T[]): T { + const r = rng(); + return items[Math.floor(r * r * items.length)]!; +} + +const universeRng = mulberry32(482910); + +const ACTORS = Array.from({ length: 150 }, (_, i) => { + const first = pick(universeRng, FIRST_NAMES); + const last = pick(universeRng, LAST_NAMES); + const domain = pick(universeRng, DOMAINS); + return { + id: `usr_${i.toString(36).padStart(4, "0")}`, + email: `${first.toLowerCase()}.${last.toLowerCase()}${i}@${domain}`, + }; +}); + +const RESOURCES = RESOURCE_TYPES.reduce( + (acc, type) => { + acc[type] = Array.from({ length: 50 }, () => `${RESOURCE_PREFIX[type]}_${hexId(universeRng, 8)}`); + return acc; + }, + {} as Record, +); + +const IPS = Array.from({ length: 80 }, () => `${intIn(universeRng, 1, 223)}.${intIn(universeRng, 0, 255)}.${intIn(universeRng, 0, 255)}.${intIn(universeRng, 1, 254)}`); + +export function eventsCorpus(count: number, seed: number): z.output[] { + const rng = mulberry32(seed); + const messages: z.output[] = []; + const base = 1755000000000; + let eventSeq = 0; + for (let m = 0; m < count; m++) { + const n = intIn(rng, 20, 50); + const events = []; + for (let i = 0; i < n; i++) { + const resourceType = pick(rng, RESOURCE_TYPES); + const actor = skewedPick(rng, ACTORS); + events.push({ + id: `evt_${eventSeq.toString(36).padStart(6, "0")}_${hexId(rng, 8)}`, + type: pick(rng, EVENT_TYPES), + actorId: actor.id, + actorEmail: actor.email, + resourceId: skewedPick(rng, RESOURCES[resourceType]), + resourceType, + ip: skewedPick(rng, IPS), + userAgent: skewedPick(rng, USER_AGENTS), + region: pick(rng, REGIONS), + durationMs: rng() < 0.9 ? intIn(rng, 5, 1200) : intIn(rng, 1200, 60000), + ok: rng() < 0.92, + at: base - intIn(rng, 0, 2592000000), + }); + eventSeq++; + } + messages.push({ + route: "events", + cursor: m < count - 1 ? hexId(rng, 16) : null, + events, + }); + } + return messages; +} diff --git a/apps/bench/src/corpora/orders.ts b/apps/bench/src/corpora/orders.ts new file mode 100644 index 0000000..7292a44 --- /dev/null +++ b/apps/bench/src/corpora/orders.ts @@ -0,0 +1,234 @@ +import { z } from "zod"; +import { intIn, mulberry32, pick } from "../prng.js"; + +export const OrderResponse = z.object({ + route: z.literal("order"), + id: z.string(), + status: z.enum(["pending", "paid", "packed", "shipped", "delivered", "refunded"]), + currency: z.enum(["USD", "EUR", "GBP", "JPY", "CHF"]), + customer: z.object({ + id: z.string(), + name: z.string(), + email: z.string(), + tier: z.enum(["free", "standard", "plus", "enterprise"]), + }), + shipping: z.object({ + country: z.enum(["US", "GB", "DE", "FR", "JP", "CA", "AU", "NL"]), + city: z.string(), + postcode: z.string(), + }), + items: z.array( + z.object({ + sku: z.string(), + title: z.string(), + qty: z.number().int().min(1).max(20), + unitPrice: z.number(), + taxRate: z.number(), + }), + ), + subtotal: z.number(), + tax: z.number(), + total: z.number(), + placedAt: z.number().int().min(0), + note: z.string().nullable(), +}); + +const PRODUCT_COUNT = 120; +const CUSTOMER_COUNT = 200; + +const ADJECTIVES = [ + "Wireless", "Steel", "Organic", "Compact", "Premium", "Vintage", "Portable", "Ceramic", + "Leather", "Digital", "Rustic", "Modern", "Ultra", "Classic", "Eco", +] as const; + +const NOUNS = [ + "Mouse", "Backpack", "Kettle", "Speaker", "Notebook", "Charger", "Sneakers", "Lamp", + "Blanket", "Headphones", "Wallet", "Bottle", "Camera", "Chair", "Desk", "Mug", + "Jacket", "Watch", "Keyboard", "Tent", +] as const; + +const TAX_RATES = [0, 0.05, 0.07, 0.08, 0.1, 0.15, 0.19, 0.2, 0.21] as const; + +const FIRST_NAMES = [ + "Olivia", "Liam", "Emma", "Noah", "Ava", "Ethan", "Sophia", "Mason", + "Isabella", "Lucas", "Mia", "Elijah", "Amelia", "James", "Harper", "Benjamin", +] as const; + +const LAST_NAMES = [ + "Garcia", "Muller", "Nguyen", "Smith", "Rossi", "Dubois", "Kowalski", "Tanaka", + "Silva", "Andersson", "Kim", "Novak", "Haddad", "Fischer", "Costa", "Ivanov", +] as const; + +const EMAIL_DOMAINS = ["gmail.com", "outlook.com", "yahoo.com", "icloud.com", "protonmail.com", "corp-mail.com"] as const; + +const TIERS_WEIGHTED = ["free", "free", "free", "standard", "standard", "standard", "plus", "plus", "enterprise"] as const; + +const STATUSES_WEIGHTED = [ + "pending", "paid", "paid", "paid", "packed", "packed", "shipped", "shipped", "shipped", + "delivered", "delivered", "delivered", "delivered", "refunded", +] as const; + +const CURRENCIES_WEIGHTED = ["USD", "USD", "USD", "EUR", "EUR", "GBP", "JPY", "CHF"] as const; + +const CITIES = [ + { city: "New York", postcode: "10001", country: "US" }, + { city: "Los Angeles", postcode: "90001", country: "US" }, + { city: "Chicago", postcode: "60601", country: "US" }, + { city: "Austin", postcode: "73301", country: "US" }, + { city: "Seattle", postcode: "98101", country: "US" }, + { city: "London", postcode: "EC1A 1BB", country: "GB" }, + { city: "Manchester", postcode: "M1 1AE", country: "GB" }, + { city: "Birmingham", postcode: "B1 1AA", country: "GB" }, + { city: "Leeds", postcode: "LS1 1AA", country: "GB" }, + { city: "Bristol", postcode: "BS1 1AA", country: "GB" }, + { city: "Berlin", postcode: "10115", country: "DE" }, + { city: "Munich", postcode: "80331", country: "DE" }, + { city: "Hamburg", postcode: "20095", country: "DE" }, + { city: "Cologne", postcode: "50667", country: "DE" }, + { city: "Frankfurt", postcode: "60306", country: "DE" }, + { city: "Paris", postcode: "75001", country: "FR" }, + { city: "Lyon", postcode: "69001", country: "FR" }, + { city: "Marseille", postcode: "13001", country: "FR" }, + { city: "Toulouse", postcode: "31000", country: "FR" }, + { city: "Nice", postcode: "06000", country: "FR" }, + { city: "Tokyo", postcode: "100-0001", country: "JP" }, + { city: "Osaka", postcode: "530-0001", country: "JP" }, + { city: "Kyoto", postcode: "600-8216", country: "JP" }, + { city: "Yokohama", postcode: "220-0011", country: "JP" }, + { city: "Nagoya", postcode: "460-0008", country: "JP" }, + { city: "Toronto", postcode: "M5H 2N2", country: "CA" }, + { city: "Vancouver", postcode: "V6B 1A1", country: "CA" }, + { city: "Montreal", postcode: "H2Y 1C6", country: "CA" }, + { city: "Calgary", postcode: "T2P 1J9", country: "CA" }, + { city: "Ottawa", postcode: "K1P 1J1", country: "CA" }, + { city: "Sydney", postcode: "2000", country: "AU" }, + { city: "Melbourne", postcode: "3000", country: "AU" }, + { city: "Brisbane", postcode: "4000", country: "AU" }, + { city: "Perth", postcode: "6000", country: "AU" }, + { city: "Adelaide", postcode: "5000", country: "AU" }, + { city: "Amsterdam", postcode: "1012 AB", country: "NL" }, + { city: "Rotterdam", postcode: "3011 AA", country: "NL" }, + { city: "The Hague", postcode: "2511 CV", country: "NL" }, + { city: "Utrecht", postcode: "3511 LN", country: "NL" }, + { city: "Eindhoven", postcode: "5611 AZ", country: "NL" }, +] as const; + +const NOTES = [ + "Please deliver after 6pm.", + "Leave with concierge.", + "Gift wrap requested.", + "Fragile - handle with care.", + "Customer requested expedited processing.", + "Address verified by support.", + null, null, null, null, null, null, null, null, null, null, null, null, null, null, +] as const; + +type Product = { sku: string; title: string; unitPrice: number; taxRate: number }; +type Customer = { id: string; name: string; email: string; tier: (typeof TIERS_WEIGHTED)[number] }; + +function round2(n: number): number { + return Math.round(n * 100) / 100; +} + +function skewedIndex(rng: () => number, length: number): number { + return Math.min(length - 1, Math.floor(rng() ** 2 * length)); +} + +function skewedPick(rng: () => number, items: readonly T[]): T { + return items[skewedIndex(rng, items.length)]!; +} + +function buildProducts(rng: () => number): Product[] { + const products: Product[] = []; + const used = new Set(); + while (products.length < PRODUCT_COUNT) { + const title = `${pick(rng, ADJECTIVES)} ${pick(rng, NOUNS)}`; + if (used.has(title)) continue; + used.add(title); + products.push({ + sku: `SKU-${String(products.length).padStart(5, "0")}`, + title, + unitPrice: round2(3 + rng() * 297), + taxRate: pick(rng, TAX_RATES), + }); + } + return products; +} + +function buildCustomers(rng: () => number): Customer[] { + const customers: Customer[] = []; + for (let i = 0; i < CUSTOMER_COUNT; i++) { + const first = pick(rng, FIRST_NAMES); + const last = pick(rng, LAST_NAMES); + const domain = pick(rng, EMAIL_DOMAINS); + customers.push({ + id: `cus-${String(i).padStart(5, "0")}`, + name: `${first} ${last}`, + email: `${first.toLowerCase()}.${last.toLowerCase()}${i}@${domain}`, + tier: pick(rng, TIERS_WEIGHTED), + }); + } + return customers; +} + +function pickProducts(rng: () => number, products: readonly Product[], n: number): Product[] { + const chosen: Product[] = []; + const used = new Set(); + while (chosen.length < n) { + const idx = skewedIndex(rng, products.length); + if (used.has(idx)) continue; + used.add(idx); + chosen.push(products[idx]!); + } + return chosen; +} + +export function ordersCorpus(count: number, seed: number): z.output[] { + const rng = mulberry32(seed); + const products = buildProducts(rng); + const customers = buildCustomers(rng); + const base = 1755700000000; + const orders: z.output[] = []; + for (let i = 0; i < count; i++) { + const customer = skewedPick(rng, customers); + const location = skewedPick(rng, CITIES); + const itemCount = intIn(rng, 2, 8); + const items = pickProducts(rng, products, itemCount).map((p) => ({ + sku: p.sku, + title: p.title, + qty: intIn(rng, 1, 20), + unitPrice: p.unitPrice, + taxRate: p.taxRate, + })); + + let subtotal = 0; + let tax = 0; + for (const item of items) { + const lineSubtotal = round2(item.unitPrice * item.qty); + const lineTax = round2(lineSubtotal * item.taxRate); + subtotal = round2(subtotal + lineSubtotal); + tax = round2(tax + lineTax); + } + const total = round2(subtotal + tax); + + orders.push({ + route: "order", + id: `ord-${String(intIn(rng, 0, 9999)).padStart(4, "0")}-${String(i).padStart(6, "0")}`, + status: pick(rng, STATUSES_WEIGHTED), + currency: pick(rng, CURRENCIES_WEIGHTED), + customer, + shipping: { + country: location.country, + city: location.city, + postcode: location.postcode, + }, + items, + subtotal, + tax, + total, + placedAt: base - intIn(rng, 0, 7776000000), + note: pick(rng, NOTES), + }); + } + return orders; +} diff --git a/apps/bench/src/profiles.ts b/apps/bench/src/profiles.ts index 12ffa79..6c5dac2 100644 --- a/apps/bench/src/profiles.ts +++ b/apps/bench/src/profiles.ts @@ -1,12 +1,14 @@ -import { brotliCompressSync, constants } from "node:zlib"; -import { compile, toIR } from "hyperfly/zod"; +import { brotliCompressSync, constants, gzipSync } from "node:zlib"; import { train } from "hyperfly"; -import { DeviceResponse } from "./corpora/devices.js"; -import { FeedResponse } from "./corpora/feed.js"; -import { devicesTraffic, feedTraffic } from "./traffic.js"; +import { compile, toIR } from "hyperfly/zod"; +import { EventResponse, eventsCorpus } from "./corpora/events.js"; +import { OrderResponse, ordersCorpus } from "./corpora/orders.js"; +import { candlesProto, devicesProto, eventsProto, feedProto, ordersProto, type ProtoCodec } from "./proto.js"; +import { candlesCorpus, devicesCorpus, feedCorpus, CandleResponse, DeviceResponse, FeedResponse } from "./traffic.js"; const enc = new TextEncoder(); -const br = (bytes: Uint8Array, mode: number) => + +const brotli = (bytes: Uint8Array, mode: number) => brotliCompressSync(bytes, { params: { [constants.BROTLI_PARAM_QUALITY]: 4, @@ -15,51 +17,118 @@ const br = (bytes: Uint8Array, mode: number) => }, }).length; +export interface CorpusResult { + route: string; + messages: number; + json: number; + gzip: number; + brotli: number; + protobuf: number; + columnar: number; + profiled: number; + full: number; + dictEntries: number; + dictBytes: number; + /** Requests until the out-of-band dictionary has paid for itself, or null if it never does. */ + breakEven: number | null; +} + +export interface CorpusSuite { + route: string; + schema: unknown; + corpus: readonly unknown[]; + proto: ProtoCodec; +} + /** - * Profiles are trained on one slice of sampled traffic and measured on a held-out - * slice, so what is reported is generalization rather than memorization. + * Per-message averages over a corpus of independent responses from one route. + * + * The profile is trained on the corpus and serves the corpus, which is what a real + * deployment does: you train on your route's traffic and then serve that route. The + * dictionary is an out-of-band artifact, so its size is reported alongside — a + * dictionary that costs more than it saves is not a win, and the reader should be + * able to see that for themselves. */ -export function runProfileSuite(): void { - const suites = [ - { name: "devices", schema: DeviceResponse, traffic: devicesTraffic(60, 50, 0xd7) }, - { name: "feed", schema: FeedResponse, traffic: feedTraffic(60, 12, 0xf7) }, - ] as const; +export function measureCorpus(suite: CorpusSuite): CorpusResult { + const ir = toIR(suite.schema as never); + const profile = train(ir, suite.corpus); + const columnar = compile(suite.schema as never, { plan: "columnar" }); + const profiled = profile ? compile(suite.schema as never, { plan: "columnar", profile }) : columnar; - console.log("\nprofiles — trained on 80% of sampled responses, measured on the held-out 20%"); - console.log(" corpus json json+br4 columnar col+br4 profiled prof+br4 dict"); + let json = 0; + let gzip = 0; + let br = 0; + let proto = 0; + let col = 0; + let prof = 0; + let full = 0; - for (const suite of suites) { - const ir = toIR(suite.schema as never); - const profile = train(ir, suite.traffic.train); - const columnar = compile(suite.schema as never, { plan: "columnar" }); - const profiled = compile(suite.schema as never, { plan: "columnar", profile }); + for (const message of suite.corpus) { + const j = enc.encode(JSON.stringify(message)); + json += j.length; + gzip += gzipSync(j, { level: 6 }).length; + br += brotli(j, constants.BROTLI_MODE_TEXT); - let json = 0; - let jsonBr = 0; - let col = 0; - let colBr = 0; - let prof = 0; - let profBr = 0; + proto += suite.proto.encode(message).length; + col += columnar.encode(message as never).length; + const p = profiled.encode(message as never); + prof += p.length; + full += brotli(p, constants.BROTLI_MODE_GENERIC); - for (const response of suite.traffic.holdout) { - const j = enc.encode(JSON.stringify(response)); - json += j.length; - jsonBr += br(j, constants.BROTLI_MODE_TEXT); - const c = columnar.encode(response as never); - col += c.length; - colBr += br(c, constants.BROTLI_MODE_GENERIC); - const p = profiled.encode(response as never); - prof += p.length; - profBr += br(p, constants.BROTLI_MODE_GENERIC); - if (!Bun.deepEquals(profiled.decode(p), response, false)) { - throw new Error(`${suite.name}: profiled round-trip mismatch`); - } + if (!Bun.deepEquals(profiled.decode(p), message, false)) { + throw new Error(`${suite.route}: profiled round-trip mismatch`); } + } + + const columns = profile?.shared.columns ?? []; + const n = suite.corpus.length; + const mean = (total: number) => Math.round(total / n); + + const dictBytes = columns.reduce( + (sum, c) => sum + c.dict.reduce((m, e) => m + enc.encode(e).length + 1, 0), + 0, + ); + const savedPerMessage = mean(col) - mean(prof); + + return { + route: suite.route, + messages: n, + json: mean(json), + gzip: mean(gzip), + brotli: mean(br), + protobuf: mean(proto), + columnar: mean(col), + profiled: mean(prof), + full: mean(full), + dictEntries: columns.reduce((sum, c) => sum + c.dict.length, 0), + dictBytes, + breakEven: savedPerMessage > 0 ? Math.ceil(dictBytes / savedPerMessage) : null, + }; +} + +export function defaultSuites(messages = 500): CorpusSuite[] { + return [ + { route: "GET /v1/candles", schema: CandleResponse, corpus: candlesCorpus(messages, 0xc9), proto: candlesProto() }, + { route: "GET /v1/devices", schema: DeviceResponse, corpus: devicesCorpus(messages, 0xd9), proto: devicesProto() }, + { route: "GET /v1/feed", schema: FeedResponse, corpus: feedCorpus(messages, 0xf9), proto: feedProto() }, + { route: "GET /v1/events", schema: EventResponse, corpus: eventsCorpus(messages, 0xe9), proto: eventsProto() }, + { route: "GET /v1/orders/:id", schema: OrderResponse, corpus: ordersCorpus(messages, 0x09), proto: ordersProto() }, + ]; +} + +export function runProfileSuite(suites: CorpusSuite[] = defaultSuites()): CorpusResult[] { + const results = suites.map(measureCorpus); - const entries = profile?.shared.columns.reduce((n, c) => n + c.dict.length, 0) ?? 0; - const cell = (v: number) => `${String(v).padStart(7)} `; + 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"); + for (const r of results) { + const cell = (v: number) => String(v).padStart(6); console.log( - ` ${suite.name.padEnd(9)} ${cell(json)} ${cell(jsonBr)} ${cell(col)} ${cell(colBr)} ${cell(prof)} ${cell(profBr)} ${entries}`, + ` ${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.dictEntries} entries / ${r.dictBytes}B` + + (r.breakEven ? ` (pays for itself after ${r.breakEven} requests)` : ""), ); } + return results; } diff --git a/apps/bench/src/proto.ts b/apps/bench/src/proto.ts index a2211a0..d5ad79f 100644 --- a/apps/bench/src/proto.ts +++ b/apps/bench/src/proto.ts @@ -206,3 +206,194 @@ export function feedProto(): ProtoCodec { }), ); } + +const [ORDER_STATUS_TO, ORDER_STATUS_FROM] = enumMaps([ + ["pending", "PENDING"], ["paid", "PAID"], ["packed", "PACKED"], + ["shipped", "SHIPPED"], ["delivered", "DELIVERED"], ["refunded", "REFUNDED"], +]); +const [CURRENCY_TO, CURRENCY_FROM] = enumMaps([ + ["USD", "USD"], ["EUR", "EUR"], ["GBP", "GBP"], ["JPY", "JPY"], ["CHF", "CHF"], +]); +const [TIER_TO, TIER_FROM] = enumMaps([ + ["free", "FREE"], ["standard", "STANDARD"], ["plus", "PLUS"], ["enterprise", "ENTERPRISE"], +]); +const [COUNTRY_TO, COUNTRY_FROM] = enumMaps([ + ["US", "US"], ["GB", "GB"], ["DE", "DE"], ["FR", "FR"], ["JP", "JP"], ["CA", "CA"], ["AU", "AU"], ["NL", "NL"], +]); + +export function ordersProto(): ProtoCodec { + const root = protobuf.Root.fromJSON({ + nested: { + OrderStatus: { + values: { + ORDER_STATUS_UNSPECIFIED: 0, PENDING: 1, PAID: 2, PACKED: 3, + SHIPPED: 4, DELIVERED: 5, REFUNDED: 6, + }, + }, + Currency: { values: { CURRENCY_UNSPECIFIED: 0, USD: 1, EUR: 2, GBP: 3, JPY: 4, CHF: 5 } }, + Tier: { values: { TIER_UNSPECIFIED: 0, FREE: 1, STANDARD: 2, PLUS: 3, ENTERPRISE: 4 } }, + Country: { + values: { + COUNTRY_UNSPECIFIED: 0, US: 1, GB: 2, DE: 3, FR: 4, JP: 5, CA: 6, AU: 7, NL: 8, + }, + }, + Customer: { + fields: { + id: { type: "string", id: 1 }, + name: { type: "string", id: 2 }, + email: { type: "string", id: 3 }, + tier: { type: "Tier", id: 4 }, + }, + }, + Shipping: { + fields: { + country: { type: "Country", id: 1 }, + city: { type: "string", id: 2 }, + postcode: { type: "string", id: 3 }, + }, + }, + Item: { + fields: { + sku: { type: "string", id: 1 }, + title: { type: "string", id: 2 }, + qty: { type: "int32", id: 3 }, + unitPrice: { type: "double", id: 4 }, + taxRate: { type: "double", id: 5 }, + }, + }, + Order: { + fields: { + route: { type: "string", id: 1 }, + id: { type: "string", id: 2 }, + status: { type: "OrderStatus", id: 3 }, + currency: { type: "Currency", id: 4 }, + customer: { type: "Customer", id: 5 }, + shipping: { type: "Shipping", id: 6 }, + items: { rule: "repeated", type: "Item", id: 7 }, + subtotal: { type: "double", id: 8 }, + tax: { type: "double", id: 9 }, + total: { type: "double", id: 10 }, + placedAt: { type: "int64", id: 11 }, + note: { type: "string", id: 12 }, + }, + }, + }, + }); + type Customer = { tier: string } & Record; + type Shipping = { country: string } & Record; + type Payload = { + status: string; + currency: string; + customer: Customer; + shipping: Shipping; + note: string | null; + } & Record; + return codec( + root, + "Order", + (p) => ({ + ...(p as Payload), + status: ORDER_STATUS_TO[(p as Payload).status]!, + currency: CURRENCY_TO[(p as Payload).currency]!, + customer: { ...(p as Payload).customer, tier: TIER_TO[(p as Payload).customer.tier]! }, + shipping: { ...(p as Payload).shipping, country: COUNTRY_TO[(p as Payload).shipping.country]! }, + // notes are full sentences or null; the corpus never emits "", so unset round-trips as null + note: (p as Payload).note ?? "", + }), + (o) => ({ + ...o, + status: ORDER_STATUS_FROM[o.status as string]!, + currency: CURRENCY_FROM[o.currency as string]!, + customer: { ...(o.customer as Customer), tier: TIER_FROM[(o.customer as Customer).tier]! }, + shipping: { ...(o.shipping as Shipping), country: COUNTRY_FROM[(o.shipping as Shipping).country]! }, + note: o.note === "" ? null : o.note, + }), + ); +} + +const [EVENT_TYPE_TO, EVENT_TYPE_FROM] = enumMaps([ + ["user.login", "USER_LOGIN"], ["user.logout", "USER_LOGOUT"], + ["file.upload", "FILE_UPLOAD"], ["file.delete", "FILE_DELETE"], + ["billing.charge", "BILLING_CHARGE"], ["billing.refund", "BILLING_REFUND"], + ["project.create", "PROJECT_CREATE"], ["project.archive", "PROJECT_ARCHIVE"], + ["member.invite", "MEMBER_INVITE"], ["member.remove", "MEMBER_REMOVE"], +]); +const [RESOURCE_TYPE_TO, RESOURCE_TYPE_FROM] = enumMaps([ + ["user", "USER"], ["file", "FILE"], ["project", "PROJECT"], + ["invoice", "INVOICE"], ["member", "MEMBER"], ["apikey", "APIKEY"], +]); + +export function eventsProto(): ProtoCodec { + const root = protobuf.Root.fromJSON({ + nested: { + EventType: { + values: { + EVENT_TYPE_UNSPECIFIED: 0, USER_LOGIN: 1, USER_LOGOUT: 2, FILE_UPLOAD: 3, FILE_DELETE: 4, + BILLING_CHARGE: 5, BILLING_REFUND: 6, PROJECT_CREATE: 7, PROJECT_ARCHIVE: 8, + MEMBER_INVITE: 9, MEMBER_REMOVE: 10, + }, + }, + ResourceType: { + values: { + RESOURCE_TYPE_UNSPECIFIED: 0, USER: 1, FILE: 2, PROJECT: 3, INVOICE: 4, MEMBER: 5, APIKEY: 6, + }, + }, + Region: { + values: { + REGION_UNSPECIFIED: 0, US_EAST: 1, US_WEST: 2, EU_CENTRAL: 3, EU_WEST: 4, + AP_SOUTH: 5, AP_NORTHEAST: 6, SA_EAST: 7, AF_SOUTH: 8, + }, + }, + Event: { + fields: { + id: { type: "string", id: 1 }, + type: { type: "EventType", id: 2 }, + actorId: { type: "string", id: 3 }, + actorEmail: { type: "string", id: 4 }, + resourceId: { type: "string", id: 5 }, + resourceType: { type: "ResourceType", id: 6 }, + ip: { type: "string", id: 7 }, + userAgent: { type: "string", id: 8 }, + region: { type: "Region", id: 9 }, + durationMs: { type: "int32", id: 10 }, + ok: { type: "bool", id: 11 }, + at: { type: "int64", id: 12 }, + }, + }, + EventsResponse: { + fields: { + route: { type: "string", id: 1 }, + cursor: { type: "string", id: 2 }, + events: { rule: "repeated", type: "Event", id: 3 }, + }, + }, + }, + }); + type Event = { type: string; resourceType: string; region: string } & Record; + type Payload = { cursor: string | null; events: Event[] } & Record; + return codec( + root, + "EventsResponse", + (p) => ({ + ...(p as Payload), + // cursors are 16 hex chars or absent; the corpus never emits "", so unset round-trips as null + cursor: (p as Payload).cursor ?? "", + events: (p as Payload).events.map((e) => ({ + ...e, + type: EVENT_TYPE_TO[e.type]!, + resourceType: RESOURCE_TYPE_TO[e.resourceType]!, + region: REGION_TO[e.region]!, + })), + }), + (o) => ({ + ...o, + cursor: o.cursor === "" ? null : o.cursor, + events: (o.events as Event[]).map((e) => ({ + ...e, + type: EVENT_TYPE_FROM[e.type]!, + resourceType: RESOURCE_TYPE_FROM[e.resourceType]!, + region: REGION_FROM[e.region]!, + })), + }), + ); +} diff --git a/apps/bench/src/traffic.ts b/apps/bench/src/traffic.ts index 6e1bbc6..bbbbab4 100644 --- a/apps/bench/src/traffic.ts +++ b/apps/bench/src/traffic.ts @@ -1,28 +1,32 @@ -import { intIn, mulberry32, pick } from "./prng.js"; +import type { z } from "zod"; +import { CandleResponse } from "./corpora/candles.js"; import { DeviceResponse } from "./corpora/devices.js"; import { FeedResponse } from "./corpora/feed.js"; -import type { z } from "zod"; +import { intIn, mulberry32, pick } from "./prng.js"; /** - * Sampled traffic for one route: many responses drawn from a stable universe, the - * way a real endpoint behaves. Profiles are trained on one slice and measured on a - * held-out slice, so the numbers reflect generalization rather than memorization. + * A corpus is many independent response messages from one route, drawn from a stable + * entity universe — the same devices, the same authors, the same instruments — because + * that is what a real endpoint returns. Per-message sizes stay in the range APIs + * actually serve (a page of records, not a ten-thousand-row dump). + * + * Dictionaries exist to exploit repetition ACROSS messages, so a corpus is the only + * setting in which they can be measured honestly: one message cannot exhibit the + * property being tested. */ -export interface Traffic { - train: T[]; - holdout: T[]; + +/** Skewed toward the low index, the way real traffic concentrates on a few entities. */ +function zipfIndex(rng: () => number, size: number): number { + const r = rng(); + return Math.min(size - 1, Math.floor(r * r * size)); } const REGIONS = ["us-east", "us-west", "eu-central", "eu-west", "ap-south", "ap-northeast", "sa-east", "af-south"] as const; const STATUSES = ["online", "online", "online", "online", "offline", "degraded", "provisioning", "unknown"] as const; const TAGS = ["fleet-a", "fleet-b", "fleet-a", "pilot", "lab", null, null, null]; -/** A fixed device fleet: the same ids and tags recur across every response. */ -export function devicesTraffic( - responses: number, - perResponse: number, - seed: number, -): Traffic> { +/** GET /v1/devices — a page of telemetry from a fixed fleet. */ +export function devicesCorpus(count: number, seed: number): z.output[] { const rng = mulberry32(seed); const fleet = Array.from({ length: 400 }, (_, i) => ({ id: `dev-${String(intIn(rng, 0, 99)).padStart(2, "0")}-${String(i).padStart(5, "0")}`, @@ -31,13 +35,13 @@ export function devicesTraffic( firmwareMajor: intIn(rng, 1, 4), firmwareMinor: intIn(rng, 0, 27), })); - const base = 1754000000000; - const all = Array.from({ length: responses }, (_, r) => ({ + + return Array.from({ length: count }, (_, message) => ({ route: "devices" as const, - page: 0, - devices: Array.from({ length: perResponse }, () => { - const unit = fleet[intIn(rng, 0, fleet.length - 1)]!; + page: message % 20, + devices: Array.from({ length: intIn(rng, 20, 50) }, () => { + const unit = fleet[zipfIndex(rng, fleet.length)]!; return { ...unit, status: pick(rng, STATUSES), @@ -47,44 +51,74 @@ export function devicesTraffic( tempC: Math.round((15 + rng() * 45) * 10) / 10, alarms: rng() < 0.85 ? 0 : intIn(rng, 1, 12), shadowSynced: rng() < 0.93, - lastSeen: base + r * 60000 - intIn(rng, 0, 86400000), + lastSeen: base + message * 60000 - intIn(rng, 0, 86400000), }; }), })); - - const split = Math.floor(all.length * 0.8); - return { train: all.slice(0, split), holdout: all.slice(split) }; } -const HANDLES = Array.from({ length: 120 }, (_, i) => { - const first = ["ada", "linus", "grace", "alan", "edsger", "barbara", "donald", "radia", "ken", "margaret"][i % 10]!; - const last = ["hopper", "torvalds", "lovelace", "turing", "dijkstra", "liskov", "knuth", "perlman", "thompson", "hamilton"][ - Math.floor(i / 10) % 10 - ]!; - return { handle: `@${first}${last}${i}`, name: `${first[0]!.toUpperCase()}${first.slice(1)} ${last[0]!.toUpperCase()}${last.slice(1)}` }; -}); +const SYMBOLS = [ + "HFLY-USD", "BTC-USD", "ETH-USD", "SOL-USD", "AAPL", "MSFT", "NVDA", "TSLA", + "EUR-USD", "GBP-USD", "USD-JPY", "XAU-USD", +]; +const INTERVALS = ["1m", "5m", "5m", "15m", "1h", "4h", "1d"] as const; +/** GET /v1/candles — a chart window, not a full history dump. */ +export function candlesCorpus(count: number, seed: number): z.output[] { + const rng = mulberry32(seed); + const opens = new Map(SYMBOLS.map((s) => [s, 20 + rng() * 300])); + + return Array.from({ length: count }, () => { + const symbol = SYMBOLS[zipfIndex(rng, SYMBOLS.length)]!; + let price = opens.get(symbol)!; + let t = 1735689600000 + intIn(rng, 0, 5000) * 300000; + const candles = Array.from({ length: intIn(rng, 20, 50) }, () => { + const o = Math.round(price * 100) / 100; + const c = Math.round((o + (rng() - 0.5) * 0.8) * 100) / 100; + const row = { + t, + o, + h: Math.round((Math.max(o, c) + rng() * 0.3) * 100) / 100, + l: Math.round((Math.min(o, c) - rng() * 0.3) * 100) / 100, + c, + v: Math.round(rng() * 50000 * 100) / 100, + trades: intIn(rng, 10, 4000), + }; + price = c; + t += 300000; + return row; + }); + return { route: "candles" as const, symbol, interval: pick(rng, INTERVALS), candles }; + }); +} + +const FIRST = ["Ada", "Linus", "Grace", "Alan", "Edsger", "Barbara", "Donald", "Radia", "Ken", "Margaret"]; +const LAST = ["Hopper", "Torvalds", "Lovelace", "Turing", "Dijkstra", "Liskov", "Knuth", "Perlman", "Thompson", "Hamilton"]; const LEXICON = ( "the of and to in is that it was for on are as with his they at be this have from or had by hot word " + "but what some we can out other were all there when up use your how said an each she which do their time " + "deploy latency cluster rollout incident postmortem throughput regression release migration schema payload" ).split(" "); -/** A recurring cast of authors posting about a recurring vocabulary. */ -export function feedTraffic( - responses: number, - perResponse: number, - seed: number, -): Traffic> { +/** GET /v1/feed — a page of posts from a recurring cast of authors. */ +export function feedCorpus(count: number, seed: number): z.output[] { const rng = mulberry32(seed); - const base = 1754500000000; const hex = (n: number) => Array.from({ length: n }, () => Math.floor(rng() * 16).toString(16)).join(""); - const authors = HANDLES.map((h) => ({ ...h, id: hex(12), verified: rng() < 0.2 })); + const authors = Array.from({ length: 120 }, (_, i) => { + const first = FIRST[i % FIRST.length]!; + const last = LAST[Math.floor(i / FIRST.length) % LAST.length]!; + return { + id: hex(12), + name: `${first} ${last}`, + handle: `@${first.toLowerCase()}${last.toLowerCase()}${i}`, + verified: rng() < 0.2, + }; + }); + const base = 1754500000000; - const all = Array.from({ length: responses }, () => ({ + return Array.from({ length: count }, () => ({ route: "feed" as const, - posts: Array.from({ length: perResponse }, () => { - const author = authors[intIn(rng, 0, authors.length - 1)]!; + posts: Array.from({ length: intIn(rng, 10, 25) }, () => { const sentences = Array.from({ length: intIn(rng, 1, 3) }, () => { const words = Array.from({ length: intIn(rng, 8, 24) }, () => pick(rng, LEXICON)); const s = words.join(" "); @@ -92,7 +126,7 @@ export function feedTraffic( }); return { id: hex(16), - author, + author: authors[zipfIndex(rng, authors.length)]!, body: sentences.join(" "), lang: pick(rng, ["en", "en", "en", "de", "fr", "es", "ja"] as const), likes: intIn(rng, 0, 50000), @@ -103,9 +137,6 @@ export function feedTraffic( }; }), })); - - const split = Math.floor(all.length * 0.8); - return { train: all.slice(0, split), holdout: all.slice(split) }; } -export { DeviceResponse, FeedResponse }; +export { CandleResponse, DeviceResponse, FeedResponse }; diff --git a/apps/web/app/benchmark.tsx b/apps/web/app/benchmark.tsx index 03e35be..cb3bda9 100644 --- a/apps/web/app/benchmark.tsx +++ b/apps/web/app/benchmark.tsx @@ -6,7 +6,7 @@ import { useInView } from "./reveal"; type Row = { label: string; bytes: number; - kind?: "baseline" | "generic" | "binary" | "hyperfly" | "profile"; + kind?: "baseline" | "generic" | "binary" | "hyperfly" | "profile" | "full"; }; type Payload = { @@ -18,43 +18,74 @@ type Payload = { const PAYLOADS: Payload[] = [ { - route: "GET /v1/candles", - shape: "1 000 OHLCV rows · 7 numeric columns · monotonic timestamps", + route: "GET /v1/events", + shape: "500 messages · 20–50 audit records each · recurring actors and user agents", rows: [ - { label: "JSON", bytes: 88209, kind: "baseline" }, - { label: "JSON + gzip", bytes: 21705, kind: "generic" }, - { label: "JSON + Brotli — edge q4", bytes: 21315, kind: "generic" }, - { label: "Protobuf", bytes: 56996, kind: "binary" }, - { label: "Hyperfly · columnar", bytes: 12628, kind: "hyperfly" }, - { label: "Hyperfly + Brotli", bytes: 7915, kind: "profile" }, + { label: "JSON", bytes: 12687, kind: "baseline" }, + { 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" }, ], - note: "Columns ride separately: timestamps become deltas, and prices that are exact decimals travel as integer mantissas instead of eight raw bytes. No entropy coder is involved yet — layout alone, uncompressed, undercuts what the edge actually serves. It clears Brotli's offline q11 ceiling too; the harness in the repo has the receipts.", + 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.", }, { route: "GET /v1/devices", - shape: "500 telemetry records · enums · bounded integers · repeated ids", + shape: "500 messages · 20–50 telemetry records each · fixed 400-device fleet", + rows: [ + { label: "JSON", bytes: 7994, kind: "baseline" }, + { 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" }, + ], + 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.", + }, + { + route: "GET /v1/orders/:id", + shape: "500 messages · one order each · fixed catalogue and customer base", rows: [ - { label: "JSON", bytes: 113443, kind: "baseline" }, - { label: "JSON + gzip", bytes: 17026, kind: "generic" }, - { label: "JSON + Brotli — edge q4", bytes: 16823, kind: "generic" }, - { label: "Protobuf", bytes: 29056, kind: "binary" }, - { label: "Hyperfly · columnar", bytes: 11945, kind: "hyperfly" }, - { label: "Hyperfly + Brotli", bytes: 9726, kind: "profile" }, + { label: "JSON", bytes: 782, kind: "baseline" }, + { 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" }, ], - note: "An enum with six members is an index, not a string. Bounded integers ship as offsets from their declared minimum, booleans pack into bitmaps, and repetitive id columns deflate inside the codec — so the uncompressed wire already undercuts what the edge serves.", + 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.", }, { route: "GET /v1/feed", - shape: "50 posts · nested authors · free-form text bodies", + shape: "500 messages · 10–25 posts each · recurring cast of 120 authors", + rows: [ + { label: "JSON", bytes: 6863, kind: "baseline" }, + { 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" }, + ], + 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.", + }, + { + route: "GET /v1/candles", + shape: "500 messages · 20–50 OHLCV rows each · monotonic timestamps", rows: [ - { label: "JSON", bytes: 22245, kind: "baseline" }, - { label: "JSON + gzip", bytes: 7775, kind: "generic" }, - { label: "JSON + Brotli — edge q4", bytes: 7691, kind: "generic" }, - { label: "Protobuf", bytes: 15232, kind: "binary" }, - { label: "Hyperfly · columnar", bytes: 6513, kind: "hyperfly" }, - { label: "Hyperfly + Brotli", bytes: 6443, kind: "profile" }, + { label: "JSON", bytes: 3225, kind: "baseline" }, + { 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" }, ], - note: "Prose does not vanish under a schema — so text columns pack through deflate inside the codec, with shared context across every row, and unpack bit-exactly. Structure travels as columns around them. The all-text route now lands ahead of Brotli's offline ceiling instead of behind it.", + 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.", }, ]; @@ -137,7 +168,7 @@ export function Benchmark() { ))} - measured — synthetic corpora, reference implementation + measured — per message, 500-message corpora

{payload.shape}

diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 7df2ba0..fcd6a30 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -465,7 +465,7 @@ h3 { .bar { display: grid; - grid-template-columns: 10.5rem minmax(0, 1fr) 6rem 3rem; + grid-template-columns: 12rem minmax(0, 1fr) 6rem 3rem; align-items: center; gap: 14px; padding: 7px 0; @@ -518,12 +518,17 @@ h3 { } .bar[data-kind="profile"] .bar-fill { - background: rgba(95, 217, 255, 0.62); - box-shadow: 0 0 26px rgba(95, 217, 255, 0.28); + background: rgba(95, 217, 255, 0.55); +} + +.bar[data-kind="full"] .bar-fill { + background: rgba(95, 217, 255, 0.85); + box-shadow: 0 0 26px rgba(95, 217, 255, 0.3); } .bar[data-kind="hyperfly"] .bar-label, -.bar[data-kind="profile"] .bar-label { +.bar[data-kind="profile"] .bar-label, +.bar[data-kind="full"] .bar-label { color: var(--text); } @@ -544,7 +549,8 @@ h3 { } .bar[data-kind="hyperfly"] .bar-ratio, -.bar[data-kind="profile"] .bar-ratio { +.bar[data-kind="profile"] .bar-ratio, +.bar[data-kind="full"] .bar-ratio { color: var(--accent); } diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 4fc16d1..d4b9a2a 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -163,13 +163,14 @@ export default function Home() {

- Measured with the TypeScript reference implementation on deterministic synthetic - corpora — reproduce with `bun run bench` in the repo. The Brotli row is q4, the - level edges actually run on dynamic responses; q6 (nginx's default) and the q11 - offline ceiling are in the harness — and uncompressed columnar output clears even - q11 on all three corpora. Protobuf gets proper enums and int64. Results still - depend on the payload; the feed tab is where the margin is thinnest, and it says - why. + 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.