The architecture and its long-lived invariants are documented in
docs/ARCHITECTURE.md. This file tracks implementation
order and may change as milestones are completed or reprioritized.
SELECT 1, simple query
execution on an in-memory table, error messages. Check: psql works,
pgbench -i fails meaningfully.\d works in psql. pg-engine
development starts in parallel.Planning Time: / Execution Time: footers; per-node
(actual …) counters still to come. VERBOSE, FORMAT JSON/XML/YAML,
SETTINGS, MEMORY, WAL and GENERIC_PLAN now report 0A000 rather than
being silently ignored — they would change the shape of the output, and a plan
that answers a question the client did not ask is worse than a stated gap),
automatic VACUUM/ANALYZE, logical replication (publisher) for CDC.The target design is specified in
docs/ARCHITECTURE.md §2.
The current implementation is V1 plus the write buffer: fragments carry
transaction identity in their footer, the directory listing is the live-file
index, .pending is renamed on commit, and scans are sequential. Foreground
INSERT/COPY no longer create files — they land in a WAL-logged RAM
BufferTable (the buffer access method, also selectable on its own with
CREATE TABLE ... USING buffer), which a background worker or an explicit
VACUUM flushes into one fragment. A relation therefore plans as an Append
over its two engine-internal storage leaves.
Two consequences worth stating plainly:
INSERT is durable before any file exists. It is covered by
the commit record’s fsync and rebuilt from the WAL at startup, but nothing
external should expect to see it under parquet/<rel>/ until a flush.COPY grows both the log and every
subsequent startup. Checkpoint-bounded recovery is therefore a hard
prerequisite of step 3 below, not a deferred nicety.The target design should land in compatibility-preserving slices:
BufferTable,
including recovery, snapshot reads, state transitions, memory limits, and
backpressure.ORDER BY key: a write is sorted
whole before it is cut into fragments, so the fragments of that write have
disjoint key ranges, and each says so in its row-group sorting_columns.
Two things remain. Size: a fragment is still capped at 65,535 rows by the
Tid offset, so the 64 MiB target is unreachable until the V2 footer lands.
Clustering across writes: every write is its own sorted run and the runs
overlap freely, so a relation loaded by many flushes is still unclustered as
a whole — pruning can exclude fragments only within a run until step 4’s
compaction merges the runs.