- docs/examples/residency: one program, two tables filled by the same loop, differing only in the annotation. Run twice against one WO_DATA and orders replay while sessions do not - the example checks its own claim (exits 1 if a durable:false table survives, or a durable:true one fails to replay) rather than narrating it in a print - resident: keys is written out as a commented block with the loader's exact refusal, so the frontier is visible in the example rather than only in a story. It documents WHERE the refusal happens: woc compiles it and emits a .wob; wovm exits 2, because the annotation is a load-time property - residency-accept gains two legs: the example runs and its restart claim holds, and the refusal message the README quotes is checked so doc and code cannot drift apart - the gate writes the example's output to /tmp/residency.log, banner-separated, for tail -F - README commands verified verbatim; they needed mkdir -p because wovm will not create WO_DATA Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit c9c7e03e62c3918cb65ef7994d1e33a0c5337b71)
109 lines
3.8 KiB
Text
109 lines
3.8 KiB
Text
-- residency — databasev2 2's per-table storage, demonstrated across a
|
|
-- restart.
|
|
--
|
|
-- Before this iteration, durability was ONE environment variable for a whole
|
|
-- process: WO_DATA set and every @table is WAL-logged, or unset and none are.
|
|
-- Real applications are not uniform. A session table is disposable, an orders
|
|
-- table is precious. One global switch forces "everything is precious" or
|
|
-- "nothing is", and the developer pays for the wrong one.
|
|
--
|
|
-- Run it twice against the same WO_DATA directory:
|
|
--
|
|
-- mkdir -p /tmp/residency-data -- WO_DATA must exist already
|
|
-- WO_DATA=/tmp/residency-data wovm residency.wob seed
|
|
-- WO_DATA=/tmp/residency-data wovm residency.wob
|
|
--
|
|
-- The second run inserts nothing. Orders come back from the log; Sessions do
|
|
-- not, because they were never written to it.
|
|
|
|
-- Precious: WAL-logged, replayed at boot. `durable: true` is the default, and
|
|
-- is written out here only because this example is about the annotation.
|
|
@table(name: "orders", index: [sku], durable: true, resident: all)
|
|
class Order {
|
|
sku: Text
|
|
cents: Int
|
|
}
|
|
|
|
-- Scratch: never written to the WAL, so it costs no disk and no fsync, and it
|
|
-- is EMPTY after a restart. That is the point — not a bug to work around.
|
|
@table(name: "sessions", index: [token], durable: false)
|
|
class Session {
|
|
token: Text
|
|
uid: Int
|
|
}
|
|
|
|
-- WHAT DOES NOT COMPILE YET, and why it is written here rather than omitted:
|
|
--
|
|
-- @table(name: "audit", index: [at], durable: true, resident: keys)
|
|
-- class AuditEntry {
|
|
-- at: Int
|
|
-- what: Text
|
|
-- }
|
|
--
|
|
-- `resident: keys` is the mode for a table too large for RAM: the id map stays
|
|
-- resident, the row payload lives in the WAL and is read back by offset. The
|
|
-- storage, the read paths, deletes and checkpoint survival all work today.
|
|
-- UPDATING such a row does not — the row has no slab slot to mutate, and doing
|
|
-- it properly means read-modify-append. The loader therefore REFUSES the
|
|
-- annotation outright rather than honouring it in name only:
|
|
--
|
|
-- wovm: class 0 declares `resident: keys`, which is INCOMPLETE: rows are
|
|
-- stored and read keys-only, but UPDATING one is not implemented (it needs
|
|
-- read-modify-append). Remove it until databasev2 2 lands updates;
|
|
-- `resident: all` is what runs
|
|
--
|
|
-- Note WHERE it is refused: the compiler accepts it and emits a .wob — the
|
|
-- annotation is a load-time property, so `woc` is green and `wovm` exits 2.
|
|
--
|
|
-- Refusing at load rather than at the first update is deliberate: a developer
|
|
-- who declared a 120 GB table keys-resident, saw it compile, and shipped would
|
|
-- discover the gap in production.
|
|
|
|
fn count_orders() -> Int {
|
|
let n = 0;
|
|
for _o in from o in Order select o { n = n + 1; }
|
|
return n;
|
|
}
|
|
|
|
fn count_sessions() -> Int {
|
|
let n = 0;
|
|
for _s in from s in Session select s { n = n + 1; }
|
|
return n;
|
|
}
|
|
|
|
fn report(label: Text) {
|
|
print("${label}: orders=${count_orders()} sessions=${count_sessions()}");
|
|
}
|
|
|
|
fn main(args: multi Text) -> Int {
|
|
if len(args) > 0 {
|
|
if args[0] == "seed" {
|
|
-- Both tables get the same number of rows, from the same code path.
|
|
-- Only the annotation differs, so anything that differs after the
|
|
-- restart is the annotation's doing and nothing else's.
|
|
let i = 0;
|
|
while i < 3 {
|
|
insert Order { sku: "sku-${i}", cents: 100 + i };
|
|
insert Session { token: "tok-${i}", uid: i };
|
|
i = i + 1;
|
|
}
|
|
report("seeded");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
-- Second run: no inserts at all. Whatever is here came from the log.
|
|
report("after restart");
|
|
|
|
-- The claim this example exists to make, checked rather than narrated.
|
|
if count_sessions() != 0 {
|
|
print("UNEXPECTED: a durable:false table survived a restart");
|
|
return 1;
|
|
}
|
|
if count_orders() == 0 {
|
|
print("UNEXPECTED: a durable:true table did not replay");
|
|
return 1;
|
|
}
|
|
print("ok: durable replayed, volatile did not");
|
|
return 0;
|
|
}
|