docs: postgres study — constraints/grammar + indexing cards; subagent guide
- constraints-and-grammar: gram.y PK/FK productions, pg_constraint, RI trigger semantics; writeonce direction — @key as unique alias (id stays THE key), ref actions (@on_delete), backlink-implies-index (improves on postgres' not-auto-created FK index) - indexing-and-point-lookup: AM roster + algorithms (Lehman-Yao, linear hashing), TID = row address; writeonce gap — probe walks slabs while idx_bucket exists; O(1) slice direction, non-goals - card index updated; Rust-era plan-10/11/12 links unlinked (rot) - docs/guides/database-developer-subagent.md: format, paste-ready agent definition (doctrine/file map/gates), verification, division of labor Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e0879346a4
commit
0fdd2bd672
8 changed files with 319 additions and 11 deletions
106
docs/guides/database-developer-subagent.md
Normal file
106
docs/guides/database-developer-subagent.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Guide — creating the `database-developer` subagent
|
||||
|
||||
A project subagent is one markdown file in `.claude/agents/` (this
|
||||
repo) or `~/.claude/agents/` (every repo). Claude Code loads it at
|
||||
session start; the main conversation can then delegate matching work to
|
||||
it via the Agent tool, and you can name it directly ("use the
|
||||
database-developer agent").
|
||||
|
||||
## 1. The file format
|
||||
|
||||
`.claude/agents/database-developer.md` — YAML frontmatter + a system
|
||||
prompt body:
|
||||
|
||||
- `name` — kebab-case; becomes the agent type.
|
||||
- `description` — WHEN to use it. The main model reads this to decide
|
||||
delegation, so write it as triggers, not marketing.
|
||||
- `tools` — allowlist. Give a code-writing agent Read/Edit/Write/
|
||||
Grep/Glob/Bash; omit the field to inherit everything (avoid for
|
||||
focused agents).
|
||||
- `model` (optional) — pin a tier; omit to inherit the session's.
|
||||
- Body — the agent's system prompt: doctrine, file map, gates,
|
||||
boundaries. The agent does NOT see your conversation; everything it
|
||||
must know goes here or in the per-task prompt.
|
||||
|
||||
## 2. Ready-to-paste definition
|
||||
|
||||
Save as `.claude/agents/database-developer.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: database-developer
|
||||
description: Engine work under database/src (tables, WAL, indexes, slot
|
||||
encode/decode) and the DB seams in runtime/src (db builtins, the DB
|
||||
actor RPC). Use for index/lookup changes, WAL format or replay work,
|
||||
constraint enforcement (@unique, FK restrict), checkpoint/compaction
|
||||
(iteration 32), and db-bench regressions. NOT for compiler surface,
|
||||
fibers/scheduler, or framework .wo code.
|
||||
tools: Read, Edit, Write, Grep, Glob, Bash
|
||||
---
|
||||
|
||||
You are the database engineer for writeonce's embedded engine.
|
||||
|
||||
Doctrine (non-negotiable):
|
||||
- C11 + libc only. No new dependencies, no atomics on the data path.
|
||||
- RAM is authoritative; the WAL makes it durable. An ack means the
|
||||
commit fsynced. Replay is whole-or-not-at-all; torn tails drop.
|
||||
- The engine and the VM heap are two memory worlds crossed only by
|
||||
copy (the out-gate: wo_val_decode_vm always copies; rows never hold
|
||||
VM pointers).
|
||||
- Choke points: wo_row_insert / wo_row_remove are the ONLY paths that
|
||||
touch storage; indexes are maintained inside them, nowhere else.
|
||||
- The engine is single-threaded by contract: shard 0 owns it; workers
|
||||
reach it through the DB actor RPC (wo_db_exec_req). Never add locks;
|
||||
never read another shard's VM heap.
|
||||
|
||||
File map:
|
||||
- database/src/table.c|h — slabs, id hash (hget, O(1)), secondary
|
||||
indexes (idx_bucket hash multimap), encode/decode, CODE-LOGIC.md.
|
||||
- database/src/wal.c|h — record grammar, staged batch, commit, replay.
|
||||
- database/src/db.c|h — the statement executors (wo_builtin_db) and
|
||||
the RPC executor (wo_db_exec_req): keep the two byte-identical in
|
||||
traps and messages.
|
||||
- runtime/src/vm.c — the requester half (wo_db_rpc); builtin.c routes.
|
||||
- Contracts: docs/plan/oop-vm/04-db-binding.md (normative — extend it
|
||||
when formats change). Benchmarks: docs/examples/db-bench,
|
||||
bench/baseline.json.
|
||||
|
||||
Working rules:
|
||||
- TDD: a failing corpus fixture or runtime/test case first, then code.
|
||||
- Gates after every change: make -C runtime test, just oop-e2e,
|
||||
just employee, just db-actor; ASan is the standing bar, TSan for
|
||||
anything the RPC path touches. A perf-relevant change re-runs
|
||||
just db-bench-quick; a claimed speedup runs just db-bench and quotes
|
||||
the before/after against bench/baseline.json.
|
||||
- Match existing style; comments state constraints, not narration.
|
||||
- Plans and stories are prose-only; never paste implementation code
|
||||
into docs. Commit drafts follow the repo's bullet style, ≤25 lines.
|
||||
|
||||
Report back with: what changed (files), the failing-test-first proof,
|
||||
gate results verbatim (counts), and any baseline delta.
|
||||
```
|
||||
|
||||
## 3. Verify it loads
|
||||
|
||||
New session (agents load at start), then: "use the database-developer
|
||||
agent to explain the probe path in database/src/db.c". The reply must
|
||||
come labeled as the subagent. `claude agents` (or the agents listing in
|
||||
`/help`) shows registered agents.
|
||||
|
||||
## 4. Division of labor
|
||||
|
||||
- The MAIN session keeps: brainstorming/specs/plans (superpowers path),
|
||||
board + story sync, cross-cutting refactors.
|
||||
- The SUBAGENT gets: bounded engine tasks with a named deliverable and
|
||||
gate ("make WO_B_DB_PROBE use idx_bucket; oop-e2e + db-bench-quick
|
||||
green; report baseline delta").
|
||||
- Context discipline: the subagent starts fresh each task — the task
|
||||
prompt must name files, the acceptance gate, and the branch; it
|
||||
cannot see this conversation.
|
||||
|
||||
## 5. Maintenance
|
||||
|
||||
The definition is code: review it in diffs, update the file map when
|
||||
files move (the CODE-LOGIC.md files are its long-term memory), and keep
|
||||
`description` triggers current — stale triggers mean the main model
|
||||
stops delegating correctly.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# PostgreSQL — storage subsystem reference
|
||||
|
||||
These cards exist to make the Postgres backend a useful **library of patterns** for writeonce's persistent-storage phases (10–12) without inviting a multi-process port. Each card pulls one subsystem out of [`reference/postgresql/src/backend/`](../../../../.dev/reference/postgresql/src/backend/) — paths into the Postgres tree, the underlying *idea*, and the writeonce translation.
|
||||
These cards exist to make the Postgres backend a useful **library of patterns** for writeonce's storage, constraint, and index work without inviting a multi-process port. (The storage cards originally fed the Rust-era plans 10–12, removed with that track 2026-08-18; the patterns fed the shipped C engine and remain the reference.) Each card pulls one subsystem out of [`reference/postgresql/src/backend/`](../../../../.dev/reference/postgresql/src/backend/) — paths into the Postgres tree, the underlying *idea*, and the writeonce translation.
|
||||
|
||||
The symlink is user-specific:
|
||||
|
||||
|
|
@ -18,6 +18,8 @@ Gitignored — see [`.gitignore`](../../../../.gitignore). Pair it with [`refere
|
|||
| [smgr-and-md](./smgr-and-md.md) | `storage/smgr/{md,smgr,bulk_write}.c` | one file per relation, segments capped at `RELSEG_SIZE`, immediate vs deferred fsync | multi-fork abstraction (main/fsm/vm), shared-memory descriptor cache |
|
||||
| [buffer-and-checkpoint](./buffer-and-checkpoint.md) | `storage/buffer/{bufmgr,freelist}.c` + `postmaster/{checkpointer,bgwriter}.c` | page cache + dirty bit + LRU; checkpoint flushes then advances control-file LSN | shared-buffer pinning/unpinning, separate writer processes, latches |
|
||||
| [page-format](./page-format.md) | `storage/page/{bufpage,checksum}.c` | page header (LSN, checksum, free-space markers); CRC32C trailers | MVCC visibility (xmin/xmax/ctid), access-method-specific opaque space |
|
||||
| [constraints-and-grammar](./constraints-and-grammar.md) | `parser/gram.y`, `catalog/pg_constraint.h`, `utils/adt/ri_triggers.c` | PK = blessed unique index; FK forward-only catalog + inline-check semantics; ON DELETE action set; backlink-implies-index (our improvement) | trigger machinery, deferrable constraints, MATCH PARTIAL, composite keys |
|
||||
| [indexing-and-point-lookup](./indexing-and-point-lookup.md) | `access/{nbtree,hash}/README`, `optimizer/path/costsize.c`, `storage/itemptr.h` | hash-bucket point lookup (expected O(1)), index-entry-as-row-address (TID ↔ our slot), selectivity beats seqscan by arithmetic | btree/gin/gist/spgist/brin AMs, cost-based planner, index paging |
|
||||
|
||||
## The lift-vs-skip filter
|
||||
|
||||
|
|
@ -41,8 +43,14 @@ What stays out:
|
|||
|
||||
The implementation phases that lean on this material:
|
||||
|
||||
- [`docs/plan/10-storage-foundations.md`](../../10-storage-foundations.md) — page format and segment files. Lifts ideas from `smgr/md.c` and `page/bufpage.h`.
|
||||
- [`docs/plan/11-wal-and-recovery.md`](../../11-wal-and-recovery.md) — WAL framing, group commit, recovery loop. Lifts ideas from `access/transam/xlog.c` and the xlog-recovery family.
|
||||
- [`docs/plan/12-engine-disk-cutover.md`](../../12-engine-disk-cutover.md) — buffer cache + dirty tracking. Lifts ideas from `storage/buffer/bufmgr.c` and `postmaster/checkpointer.c`.
|
||||
- The Rust-era consumers (plans 10/11/12: storage foundations, WAL and
|
||||
recovery, disk cutover) were removed with that track 2026-08-18; their
|
||||
ideas shipped in `database/src/` (typed WAL + replay) and the rest wait
|
||||
on [iteration 32](../../../stories/language-runtime-database/refine/32-wal-checkpoint.md)
|
||||
(checkpoint) — the wal/buffer cards are its entry material.
|
||||
- Current consumers: [constraints-and-grammar](./constraints-and-grammar.md)
|
||||
(the `@table` PK/FK grammar direction) and
|
||||
[indexing-and-point-lookup](./indexing-and-point-lookup.md) (the
|
||||
O(1) read-path slice iteration 22's numbers demand).
|
||||
|
||||
Pair each card with [`docs/plan/exploration/linux/12-pwrite-fsync.md`](../linux/12-pwrite-fsync.md) for the actual syscalls — these cards are about *design patterns*, that one is about *kernel calls*.
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ For the checkpoint:
|
|||
|
||||
## Used by
|
||||
|
||||
- [`docs/plan/12-engine-disk-cutover.md`](../../12-engine-disk-cutover.md) — disk-backed engine reads and dirty-row tracking.
|
||||
- [`docs/plan/11-wal-and-recovery.md`](../../11-wal-and-recovery.md) — control file write sequence (phase 11 ships the control file; checkpoint as a periodic step lands with phase 12 or shortly after).
|
||||
- `docs/plan/12-engine-disk-cutover.md` (Rust-era, removed 2026-08-18) — disk-backed engine reads and dirty-row tracking.
|
||||
- `docs/plan/11-wal-and-recovery.md` (Rust-era, removed 2026-08-18) — control file write sequence (phase 11 ships the control file; checkpoint as a periodic step lands with phase 12 or shortly after).
|
||||
|
||||
Pair with [`linux/12-pwrite-fsync.md`](../linux/12-pwrite-fsync.md) for the fsync semantics and [`linux/08-mmap.md`](../linux/08-mmap.md) for the OS page-cache backstory.
|
||||
|
|
|
|||
102
docs/plan/exploration/postgresql/constraints-and-grammar.md
Normal file
102
docs/plan/exploration/postgresql/constraints-and-grammar.md
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# Constraints & DDL grammar — PK / FK / reverse navigation
|
||||
|
||||
What Postgres' CREATE TABLE grammar and catalog do for PRIMARY KEY,
|
||||
FOREIGN KEY/REFERENCES, and reverse lookup — and the `@table` grammar
|
||||
writeonce should grow from it. Tree: post-18 master
|
||||
(`REL_18_BETA1-2871`); paths into
|
||||
[`reference/postgresql/`](../../../../.dev/reference/postgresql/).
|
||||
|
||||
## The Postgres side (facts, with paths)
|
||||
|
||||
**Grammar** (`src/backend/parser/gram.y`):
|
||||
|
||||
- Column constraints (`ColConstraintElem`, :4119): `UNIQUE` (:4140,
|
||||
with `NULLS [NOT] DISTINCT`), `PRIMARY KEY` (:4153), and
|
||||
`REFERENCES qualified_name opt_column_list key_match key_actions`
|
||||
(:4224).
|
||||
- Table-level twins (`ConstraintElem`, :4376): multi-column
|
||||
`UNIQUE (...)` :4404, `PRIMARY KEY (...)` :4439 (or adopt an
|
||||
existing index, :4457), `FOREIGN KEY (...) REFERENCES ...` :4493.
|
||||
- REFERENCES options: `key_match` = MATCH FULL | PARTIAL
|
||||
(unimplemented, errors) | SIMPLE (default) — :4620; `key_actions` =
|
||||
`ON UPDATE`/`ON DELETE` × { NO ACTION | RESTRICT | CASCADE |
|
||||
SET NULL | SET DEFAULT } — :4664–4733. Single-char codes in
|
||||
`src/include/nodes/parsenodes.h:2928`.
|
||||
|
||||
**Catalog** (`src/include/catalog/pg_constraint.h`):
|
||||
|
||||
- One row per constraint; `contype` `'p'`/`'f'`/`'u'` (:198). FK rows
|
||||
carry the FORWARD direction only: `conrelid`/`conkey[]` (referencing)
|
||||
→ `confrelid`/`confkey[]` (referenced), plus the action/match chars
|
||||
(:98–130).
|
||||
- **A PK/UNIQUE constraint IS an index**: `transformIndexConstraints`
|
||||
(`src/backend/parser/parse_utilcmd.c:2245`) rewrites the constraint
|
||||
into an `IndexStmt` (`index->primary`, `index->unique`) — the
|
||||
constraint and its unique index are one object (`conindid`,
|
||||
`index_constraint_create`, `catalog/index.c:1903`). FKs are
|
||||
transformed AFTER indexes deliberately (:3023).
|
||||
|
||||
**FK enforcement = trigger pairs** (`utils/adt/ri_triggers.c`):
|
||||
|
||||
- Referencing side: INSERT/UPDATE fire `RI_FKey_check` (:358) —
|
||||
`SELECT 1 FROM <pktable> WHERE pk = $1 FOR KEY SHARE` (a probe of the
|
||||
PK's unique index; :452 even has a direct-index fast path bypassing
|
||||
SPI).
|
||||
- Referenced side: DELETE/UPDATE fire the action triggers —
|
||||
restrict/noaction = `SELECT 1 FROM <fktable> WHERE fk = $1` (:903),
|
||||
cascade = `DELETE FROM <fktable> WHERE fk = $1` (:1089), setnull/
|
||||
setdefault = the obvious UPDATEs. NO ACTION vs RESTRICT differ only
|
||||
in deferrability + a replacement-row re-check (:872) — "the only
|
||||
difference", per the source comment.
|
||||
|
||||
**Reverse navigation — the load-bearing negative:**
|
||||
|
||||
- Postgres stores NO backlink. A reverse lookup is a plain scan of the
|
||||
referencing table (`WHERE $1 = fkatt1`); it is fast iff an index on
|
||||
the FK column exists. That index is recommended, NOT auto-created
|
||||
(`doc/src/sgml/ddl.sgml:1390`), because indexing choices vary. The
|
||||
referenced side, by contrast, ALWAYS has an index — it must be a
|
||||
PK/unique.
|
||||
|
||||
## The writeonce translation
|
||||
|
||||
What exists today: every class is a table; the auto-assigned,
|
||||
shard-interleaved `id` is the de-facto primary key (O(1) via the
|
||||
table's open-addressing id hash); `@table(name:, index: [cols])`
|
||||
declares secondary indexes; `@unique` on a column; `ref T` is a stored
|
||||
FK (restrict-only, checked by `wo_row_has_referrers` — currently a
|
||||
full scan); `backlink T.field` is a declared reverse view (currently
|
||||
an O(table) scan too).
|
||||
|
||||
The grammar this study argues for (words, no code — an iteration's
|
||||
brainstorm decides):
|
||||
|
||||
1. **Keep the id as THE primary key; add `@key` as a UNIQUE ALIAS, not
|
||||
a replacement.** Postgres' lesson: a PK is just a unique index the
|
||||
catalog blesses (`transformIndexConstraints`). writeonce already has
|
||||
the blessed unique id; a user-declared `@key` on a column should
|
||||
desugar to `@unique` + the natural-lookup index — never a second
|
||||
row-identity (slabs, WAL records, and refs all speak id).
|
||||
2. **`ref T` grows an action option, defaulting to today's behavior:**
|
||||
`ref T` = restrict (current semantics, now named); optional
|
||||
`@on_delete(cascade)` / `@on_delete(set_nil)` — the `?ref T` shape
|
||||
is the precondition for set_nil, exactly as SET NULL requires a
|
||||
nullable column in Postgres. MATCH variants: skip — single-column
|
||||
refs only, MATCH SIMPLE semantics by construction.
|
||||
3. **Backlink beats Postgres — if it implies the index.** Postgres
|
||||
makes reverse lookup fast only when the user remembers the FK-column
|
||||
index; writeonce's `backlink T.field` is a DECLARED intent, so the
|
||||
compiler should auto-require `index: [field]` on the referencing
|
||||
table (or inject it) — the study's one clear improvement over the
|
||||
reference. `wo_row_has_referrers` and backlink reads then become
|
||||
index probes, not scans (see the indexing card).
|
||||
4. **Enforcement placement:** Postgres bolts FK checks on as triggers
|
||||
because constraints arrived after the executor; writeonce's choke
|
||||
points (`wo_row_insert`/`wo_row_remove`/`wo_row_update_field`) are
|
||||
the honest home — checks stay inline, no trigger machinery, same
|
||||
observable semantics (insert probes the referenced id's existence;
|
||||
delete probes the referencing index).
|
||||
|
||||
Non-goals this study records: composite keys (no driving workload),
|
||||
deferrable constraints (need `transaction { }` = held iteration 18),
|
||||
MATCH PARTIAL (Postgres never shipped it either).
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
# Indexing & point lookup — how Postgres never full-scans for `=`
|
||||
|
||||
The access-method algorithms, and the mechanism that turns an equality
|
||||
predicate into a direct row address instead of a table walk. Tree:
|
||||
19devel; paths into
|
||||
[`reference/postgresql/`](../../../../.dev/reference/postgresql/).
|
||||
Written for the read-path finding iteration 22 measured: writeonce
|
||||
point lookups are O(table) (~1.5k reads/s at p50 600µs on 20k rows).
|
||||
|
||||
## The access-method roster (facts, with paths)
|
||||
|
||||
| AM | algorithm | serves | point lookup |
|
||||
| --- | --- | --- | --- |
|
||||
| nbtree | Lehman–Yao B-tree (`access/nbtree/README:6`) | `< <= = >= >`, IN, ordered scans, prefix LIKE | O(log N) page descents |
|
||||
| hash | Seltzer/Yigit extendible hashing (`access/hash/README:6`) | `=` only | O(1) expected: metapage + bucket-page binary search |
|
||||
| gin | inverted index: btree of keys → posting lists (`access/gin/README:17`) | containment, full-text | bitmap-only (no amgettuple) |
|
||||
| gist | generalized balanced tree, opclass `consistent` (`access/gist/README:8`) | overlap, kNN | multi-subtree descent |
|
||||
| spgist | space-partitioned tries/quadtrees, non-balanced (`access/spgist/README:3`) | points, prefixes | data-bounded depth |
|
||||
| brin | per-block-range min/max summaries (`access/brin/README:4`) | huge clustered scans | none — lossy bitmap |
|
||||
|
||||
Algorithm notes worth keeping:
|
||||
|
||||
- **btree**: Lehman–Yao's right-link + high-key lets descents run
|
||||
lock-free past concurrent splits (`nbtree/README:17-29`); equality
|
||||
and range use the SAME descent — `_bt_first` positions, `_bt_next`
|
||||
walks siblings (`nbtsearch.c:883/:1586`); heap TID is a tiebreaker
|
||||
making every key unique per level.
|
||||
- **hash**: bucket count doubles at split points; one bucket splits at
|
||||
a time (linear hashing, `hash/README:14-22,60-79`); bucket resolution
|
||||
is a MASK — `bucket = hash & highmask; if > maxbucket then & lowmask`
|
||||
(`hashutil.c:125`); entries sorted by hash within a page for binary
|
||||
search. Fully WAL-logged in this tree (the old caveat is gone);
|
||||
btree's remaining edge is capability, not durability: only btree does
|
||||
unique constraints, ordered scans, and range predicates.
|
||||
|
||||
## Why `=` never scans the table
|
||||
|
||||
1. The planner builds BOTH paths and costs them: seqscan cost is
|
||||
unconditionally whole-relation (`pages × seq_page_cost + tuples ×
|
||||
cpu_tuple_cost`, `costsize.c:270`); index cost scales by
|
||||
SELECTIVITY (`cost_index`, `:545`, delegating to the AM's
|
||||
`amcostestimate`). A selective equality wins by arithmetic, not by
|
||||
rule.
|
||||
2. An index entry stores a **TID** — `(block, offset)`, 6 bytes
|
||||
(`storage/itemptr.h:36`): the ROW'S ADDRESS. The executor path is
|
||||
IndexScan → `btgettuple` → `index_fetch_heap` reads exactly ONE
|
||||
heap page and one line pointer (`indexam.c:698`). The index answers
|
||||
"where", the heap answers "what" — nothing walks.
|
||||
|
||||
## The writeonce translation — O(1) lookups are one wiring change
|
||||
|
||||
What exists (`database/src/table.c`):
|
||||
|
||||
- The id path is ALREADY the TID story: `hget` (open-addressing hash,
|
||||
:260) maps id → global slot + 1, and the slot IS the address
|
||||
(slab base + offset — addresses stable forever). O(1), proven by
|
||||
db-bench's 297k inserts/s.
|
||||
- Secondary indexes ALREADY exist as a hash multimap —
|
||||
`db_index`/`db_ibucket` (`idx_hash`/`idx_bucket`, :302/:342), the
|
||||
same expected-O(1) shape as Postgres' hash AM (minus its paging,
|
||||
which a RAM-authoritative store does not need). Maintained inside
|
||||
the insert/remove/update choke points, exactly where they belong.
|
||||
|
||||
The measured gap: **the read path never asks the index.** Both
|
||||
`WO_B_DB_PROBE` (`db.c:125`) and its RPC twin in `wo_db_exec_req` read
|
||||
the index metadata only for the key column's KIND, then walk EVERY
|
||||
slab comparing values — Postgres' seqscan, unconditionally, on a
|
||||
column that has a live hash index. `wo_row_has_referrers` (FK
|
||||
restrict) is the same story across all tables.
|
||||
|
||||
Direction the next slice takes (words only):
|
||||
|
||||
1. Probe = `idx_bucket(ix, hash(key))`, then verify equality against
|
||||
the bucket's ids via `wo_row_ptr` (a hash is a hint, never an
|
||||
answer — the engine's own doctrine, already enforced on the unique
|
||||
path). Expected O(1); the bucket wins by the same arithmetic that
|
||||
makes Postgres pick the index.
|
||||
2. Multi-column indexes probe on the FULL column set today
|
||||
(`idx_hash` hashes all cols) — a single-column equality over a
|
||||
composite index needs either a leading-column bucket layout or a
|
||||
declared single-column index; the slice decides, the bench arbitrates.
|
||||
3. FK restrict + backlink reads ride the same probe once the
|
||||
grammar card's rule lands (backlink implies the FK-column index).
|
||||
4. Non-goals, recorded: no btree (no ordered-scan workload yet —
|
||||
`order by` sorts materialized results today), no planner (one AM,
|
||||
one rule: indexed equality probes, everything else scans), no
|
||||
paging (RAM-authoritative; the WAL is the disk story).
|
||||
|
||||
Acceptance shape for that slice: db-bench `read`/`query` move from
|
||||
~1.5k ops/s to the same order as inserts; `bench/baseline.json`
|
||||
refreshed with the delta recorded — the gate exists precisely so this
|
||||
claim gets measured.
|
||||
|
|
@ -39,7 +39,7 @@ The body of the page after this header holds **line pointers** (`ItemIdData`, 4
|
|||
|
||||
## What writeonce does instead (phase 10)
|
||||
|
||||
Variable-length records, length-prefixed. The framing is in [`docs/plan/10-storage-foundations.md`](../../10-storage-foundations.md):
|
||||
Variable-length records, length-prefixed. The framing is in `docs/plan/10-storage-foundations.md` (Rust-era, removed 2026-08-18):
|
||||
|
||||
```text
|
||||
[u32 length LE][u8 flags][u8 record_kind][u64 LSN][payload bytes][u32 CRC32C]
|
||||
|
|
@ -76,7 +76,7 @@ Slotted pages are the answer when those assumptions break. Until then, the frami
|
|||
|
||||
## Used by
|
||||
|
||||
- [`docs/plan/10-storage-foundations.md`](../../10-storage-foundations.md) — record framing borrows the **header + checksum** pattern from `bufpage.h`.
|
||||
- [`docs/plan/12-engine-disk-cutover.md`](../../12-engine-disk-cutover.md) — when reading rows back from disk, CRC verification is the silent-corruption safety net the page header gives Postgres.
|
||||
- `docs/plan/10-storage-foundations.md` (Rust-era, removed 2026-08-18) — record framing borrows the **header + checksum** pattern from `bufpage.h`.
|
||||
- `docs/plan/12-engine-disk-cutover.md` (Rust-era, removed 2026-08-18) — when reading rows back from disk, CRC verification is the silent-corruption safety net the page header gives Postgres.
|
||||
|
||||
Pair with [`wal.md`](./wal.md) for the LSN convention and [`buffer-and-checkpoint.md`](./buffer-and-checkpoint.md) for the dirty-page semantics that pages need.
|
||||
|
|
|
|||
|
|
@ -77,4 +77,4 @@ That's the core of phase 10's `SegStore`.
|
|||
|
||||
## Used by
|
||||
|
||||
[`docs/plan/10-storage-foundations.md`](../../10-storage-foundations.md) — segment file layout, append path. Pair with [`linux/09-fallocate.md`](../linux/09-fallocate.md) for preallocation, [`linux/12-pwrite-fsync.md`](../linux/12-pwrite-fsync.md) for the syscall details.
|
||||
`docs/plan/10-storage-foundations.md` (Rust-era, removed 2026-08-18) — segment file layout, append path. Pair with [`linux/09-fallocate.md`](../linux/09-fallocate.md) for preallocation, [`linux/12-pwrite-fsync.md`](../linux/12-pwrite-fsync.md) for the syscall details.
|
||||
|
|
|
|||
|
|
@ -63,4 +63,4 @@ Same effect as Postgres' group-commit fence (one `fsync` flushes many commits) w
|
|||
|
||||
## Used by
|
||||
|
||||
[`docs/plan/11-wal-and-recovery.md`](../../11-wal-and-recovery.md) — WAL framing, group commit, control file, replay loop. Pair with [`linux/12-pwrite-fsync.md`](../linux/12-pwrite-fsync.md) for syscall details.
|
||||
`docs/plan/11-wal-and-recovery.md` (Rust-era, removed 2026-08-18) — WAL framing, group commit, control file, replay loop. Pair with [`linux/12-pwrite-fsync.md`](../linux/12-pwrite-fsync.md) for syscall details.
|
||||
|
|
|
|||
Loading…
Reference in a new issue