feat(site): tutorial chapter for durable and resident storage modes
- new chapter 7, "Storage modes: durable and resident", covering what master gains with the databasev2 cherry-pick: durable: false for a RAM-only table, resident: keys for a table that outgrows RAM - states the parts a reader would otherwise hit as surprises: a keys-resident table is REFUSED at startup without WO_DATA, an update appends a delta rather than rewriting the row, and the chain is bounded at 16 links so a hot row does not degrade reads or replay - quotes the measured 2.55x smaller resident set, not an estimate - actors/deps/serving shift to ord 8/9/10; seeding is ord-driven so an existing WO_DATA keeps its rows and only a fresh boot reseeds - home card says a table can be RAM-only or outgrow RAM - two gate legs pin the new chapter; site-accept 23 checks, 0 failures Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 3b503c0db50aa737dd06ef6d17151c654a42c59b)
This commit is contained in:
parent
710325b94a
commit
aee78c2296
5 changed files with 23 additions and 6 deletions
|
|
@ -9,7 +9,7 @@ samples now read the same way.
|
||||||
| file | layer | what it owns |
|
| file | layer | what it owns |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `types.wo` | MODEL | the `Chapter` `@table`, the `ChapterLink` projection, `Chapters.links()`, and `seed_if_empty()` |
|
| `types.wo` | MODEL | the `Chapter` `@table`, the `ChapterLink` projection, `Chapters.links()`, and `seed_if_empty()` |
|
||||||
| `content.wo` | MODEL (content) | the nine chapter bodies as fragment-returning functions, plus `seed_chapters()` |
|
| `content.wo` | MODEL (content) | the ten chapter bodies as fragment-returning functions, plus `seed_chapters()` |
|
||||||
| `layout/app.wo` | VIEW (chrome) | `AppShell` — the component that fills writeonce-view's `Layout` — the two named widths, and `html_error` |
|
| `layout/app.wo` | VIEW (chrome) | `AppShell` — the component that fills writeonce-view's `Layout` — the two named widths, and `html_error` |
|
||||||
| `layout/header.wo`, `layout/footer.wo` | VIEW (chrome) | the shared nav bar (brand = mark + wordmark) and footer |
|
| `layout/header.wo`, `layout/footer.wo` | VIEW (chrome) | the shared nav bar (brand = mark + wordmark) and footer |
|
||||||
| `layout/logo.wo` | VIEW (chrome) | the mark as inline SVG, plus the `<head>` links |
|
| `layout/logo.wo` | VIEW (chrome) | the mark as inline SVG, plus the `<head>` links |
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ MVC, laid out exactly like the program template
|
||||||
| this app | layer |
|
| this app | layer |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `types.wo` | MODEL — the `Chapter` `@table`, and seed-if-empty |
|
| `types.wo` | MODEL — the `Chapter` `@table`, and seed-if-empty |
|
||||||
| `content.wo` | the nine chapter bodies + `seed_chapters()` |
|
| `content.wo` | the ten chapter bodies + `seed_chapters()` |
|
||||||
| `layout/` | the chrome: `AppShell` (+ the two named widths), header, footer, `html_error` |
|
| `layout/` | the chrome: `AppShell` (+ the two named widths), header, footer, `html_error` |
|
||||||
| `home/`, `chapter/`, `install/`, `packages/` | one module per feature: its `view.wo` (components: fields in, Text out) and its `controller.wo` (query the model, fill the components, answer a `Resp`) |
|
| `home/`, `chapter/`, `install/`, `packages/` | one module per feature: its `view.wo` (components: fields in, Text out) and its `controller.wo` (query the model, fill the components, answer a `Resp`) |
|
||||||
| `admin/`, `health/`, `favicon/` | controller-only features — a redirect, a text probe, an SVG |
|
| `admin/`, `health/`, `favicon/` | controller-only features — a redirect, a text probe, an SVG |
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,20 @@ fn ch_tables() -> Text {
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ch_storage() -> Text {
|
||||||
|
let b = el("p", "leading-relaxed mb-4",
|
||||||
|
"Two optional <code>@table</code> keys decide where a table's rows LIVE. Both default " .. "to today's behaviour, so every table above keeps working untouched. " .. "<code>durable: false</code> keeps a table entirely in RAM — a full table in-process, " .. "same indexes, same <code>@unique</code>, same queries — that is simply empty after a " .. "restart. That is what a session store or a rate-limit counter wants: losing it on " .. "reboot is correct, and paying to make it durable is waste.");
|
||||||
|
b = b .. code_block("@table(name: \"sessions\", durable: false)\nclass Session {\n sid: Text @unique\n hits: Int\n}\n\n@table(name: \"catalog\", resident: keys, index: [sku])\nclass Product {\n sku: Text @unique\n price: Int\n blurb: Text\n}");
|
||||||
|
b = b .. el("p", "leading-relaxed mt-4 mb-4",
|
||||||
|
"<code>resident: keys</code> is the other direction: only the KEY stays in memory. The " .. "row body lives in the write-ahead log and is read back on demand, so a table can be " .. "far larger than RAM without a page-fault cliff. Measured on a product catalogue, the " .. "resident set is <strong>2.55× smaller</strong> than the same table fully resident. " .. "Such a table has nowhere to keep its rows without a log, so declaring it and starting " .. "without <code>WO_DATA</code> is REFUSED at startup rather than silently ignored.");
|
||||||
|
b = b .. el("p", "leading-relaxed mb-4",
|
||||||
|
"Reads and writes are unchanged — the storage mode is not a different API:");
|
||||||
|
b = b .. code_block("let hits = from x in Product where x.sku == \"a-1\" take 1 select x;\nif len(hits) == 1 {\n hits[0].price = 1299; -- an ordinary assignment\n}");
|
||||||
|
b = b .. el("p", "leading-relaxed mt-4",
|
||||||
|
"Underneath, that assignment appends a <em>delta</em> — which field changed, plus a " .. "back-pointer to the row's previous record — instead of rewriting the whole row. A read " .. "walks that chain backward, newest wins. Left alone the chain would grow forever on a " .. "hot row, so it is BOUNDED: past 16 links an update writes a full row image and the " .. "chain restarts at zero. A row updated a million times still costs a bounded number of " .. "record reads, on read and on replay alike.");
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
fn ch_actors() -> Text {
|
fn ch_actors() -> Text {
|
||||||
let b = el("p", "leading-relaxed mb-4",
|
let b = el("p", "leading-relaxed mb-4",
|
||||||
"Concurrency is actors on fibers: <code>spawn</code> makes an actor from a class with a " .. "<code>receive</code> method, <code>send</code> delivers one message at a time, and " .. "ownership MOVES with the message — no locks, no shared mutable state, no data races " .. "by construction. Blocking calls park the fiber; the shard serves others meanwhile. " .. "One VM per core by default; mailboxes are bounded (a full one is a catchable trap).");
|
"Concurrency is actors on fibers: <code>spawn</code> makes an actor from a class with a " .. "<code>receive</code> method, <code>send</code> delivers one message at a time, and " .. "ownership MOVES with the message — no locks, no shared mutable state, no data races " .. "by construction. Blocking calls park the fiber; the shard serves others meanwhile. " .. "One VM per core by default; mailboxes are bounded (a full one is a catchable trap).");
|
||||||
|
|
@ -94,7 +108,8 @@ pub fn seed_chapters() {
|
||||||
insert Chapter { slug: "classes", ord: 4, title: "Classes and interfaces", body: ch_classes() };
|
insert Chapter { slug: "classes", ord: 4, title: "Classes and interfaces", body: ch_classes() };
|
||||||
insert Chapter { slug: "optionals", ord: 5, title: "Optionals and traps", body: ch_optionals() };
|
insert Chapter { slug: "optionals", ord: 5, title: "Optionals and traps", body: ch_optionals() };
|
||||||
insert Chapter { slug: "tables", ord: 6, title: "@table: the built-in database", body: ch_tables() };
|
insert Chapter { slug: "tables", ord: 6, title: "@table: the built-in database", body: ch_tables() };
|
||||||
insert Chapter { slug: "actors", ord: 7, title: "Actors and fibers", body: ch_actors() };
|
insert Chapter { slug: "storage", ord: 7, title: "Storage modes: durable and resident", body: ch_storage() };
|
||||||
insert Chapter { slug: "deps", ord: 8, title: "Dependencies", body: ch_deps() };
|
insert Chapter { slug: "actors", ord: 8, title: "Actors and fibers", body: ch_actors() };
|
||||||
insert Chapter { slug: "serving", ord: 9, title: "Serving the web (this site)", body: ch_serving() };
|
insert Chapter { slug: "deps", ord: 9, title: "Dependencies", body: ch_deps() };
|
||||||
|
insert Chapter { slug: "serving", ord: 10, title: "Serving the web (this site)", body: ch_serving() };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ pub class HomePage {
|
||||||
-- why-cards (2x2 grid, collapses on small screens)
|
-- why-cards (2x2 grid, collapses on small screens)
|
||||||
let cards = card("One binary", "woc build emits a self-contained executable: VM, your bytecode, the database engine. Deploys are a file copy; the runtime swaps code in place.");
|
let cards = card("One binary", "woc build emits a self-contained executable: VM, your bytecode, the database engine. Deploys are a file copy; the runtime swaps code in place.");
|
||||||
cards = cards .. card("Memory safety, no tax", "Rust-shaped ownership checked at compile time; where ownership cannot express the shape, the compiler infers GC — per shard, no global pause.");
|
cards = cards .. card("Memory safety, no tax", "Rust-shaped ownership checked at compile time; where ownership cannot express the shape, the compiler infers GC — per shard, no global pause.");
|
||||||
cards = cards .. card("The database is built in", "Every class is a table. Inserts are WAL-logged before they acknowledge; restart replays. No server to operate, no connection string.");
|
cards = cards .. card("The database is built in", "Every class is a table. Inserts are WAL-logged before they acknowledge; restart replays. No server to operate, no connection string — and a table can declare itself RAM-only, or keep only its keys in memory and outgrow RAM.");
|
||||||
cards = cards .. card("Actors on every core", "spawn returns an address, send moves ownership. Fibers park on io_uring instead of blocking threads — no async/await, ever.");
|
cards = cards .. card("Actors on every core", "spawn returns an address, send moves ownership. Fibers park on io_uring instead of blocking threads — no async/await, ever.");
|
||||||
let grid = el("div", "grid grid-cols-2 gap-6 mb-8", cards);
|
let grid = el("div", "grid grid-cols-2 gap-6 mb-8", cards);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,8 @@ expect "tailwind sheet inlined" "$(hit /)" 200 ".btn{"
|
||||||
expect "chapter renders a code sample" "$(hit /ch/hello)" 200 "fn main"
|
expect "chapter renders a code sample" "$(hit /ch/hello)" 200 "fn main"
|
||||||
expect "escaped interpolation visible" "$(hit /ch/values)" 200 '${port}'
|
expect "escaped interpolation visible" "$(hit /ch/values)" 200 '${port}'
|
||||||
expect "unknown chapter is a 404 page" "$(hit /ch/nope)" 404 "No such chapter"
|
expect "unknown chapter is a 404 page" "$(hit /ch/nope)" 404 "No such chapter"
|
||||||
|
expect "storage chapter documents both modes" "$(hit /ch/storage)" 200 "resident: keys"
|
||||||
|
expect "storage chapter states the chain bound" "$(hit /ch/storage)" 200 "past 16 links"
|
||||||
expect "install guide renders" "$(hit /install)" 200 "tar -C /usr/local"
|
expect "install guide renders" "$(hit /install)" 200 "tar -C /usr/local"
|
||||||
expect "packages index lists both" "$(hit /packages)" 200 "/packages/porch"
|
expect "packages index lists both" "$(hit /packages)" 200 "/packages/porch"
|
||||||
expect "package detail shows its dep" "$(hit /packages/view)" 200 "writeonce-view"
|
expect "package detail shows its dep" "$(hit /packages/view)" 200 "writeonce-view"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue