Skip to main content

Module chunk_rows

Module chunk_rows 

Source
Expand description

Rows per chunk on the background write paths (§5.1.5, D-011, D-014, D-058).

The Write Actor holds the sole write connection, so a single large statement blocks every other writer for its duration. Chunking bounds that stall; the cost is that a bulk import is not atomic across chunks, which is why it is a separate command from HighPriCommand::WriteBulkAtomic rather than a tuning parameter on it.

§Why these are four constants and not one

Through 0.5.5 this was a single CHUNK_ROWS = 1000 for all four bulk paths. The golden rule it was meant to serve is a bound on duration — a background chunk must commit fast enough that an interactive write queued behind it is not made to wait — and one row count cannot express one duration across paths whose measured per-row costs differ by 60× (D-058). At 1,000 rows the four paths took 3.5 ms, 24 ms, 89 ms and 143 ms: the same constant, four answers, three of them far outside the bound.

Each size below is derived from benches/budgets.rs’s chunk_scaling sweep against CHUNK_BUDGET, then verified by measuring that size directly. They are measurements of this machine, not universal constants — D-055’s reasoning about reference hardware applies here too, and re-deriving them on materially different storage is a cargo bench away.

§Sized for the tail, not the median

The first derivation solved f + c·n = 3 ms exactly and produced sizes whose median commit was 2.93 ms and whose upper estimate was 2.96 — inside the bound as reported and outside it for any chunk slower than typical. A latency bound is a statement about the chunk an unlucky interactive write actually queues behind, so these solve for ≈2.5 ms instead, leaving the remainder as headroom for the tail. That costs a few percent of throughput on the two linear paths and nothing on the two superlinear ones.

As measured by chunk_budget, each at its own size: edges 2.39 ms, concepts 2.35 ms, annotations 2.36 ms, embeddings 2.06 ms, no upper estimate above 2.42.

§Known limitation: these are empty-database figures

chunk_budget seeds concepts and starts with no links and no vectors, and D-059 established that per-row cost on the edge and embedding paths grows with the size of the structure being written, not with the chunk. The same 90-edge chunk takes 9.06 ms into an 8,000-edge table. So the bound is met as measured here and not met on a populated database.

That gap was published as 47.7 ms until 0.10.0 and attributed to the schema defect D-059 documents. The defect was fixed by the v5 → v6 rung and the figure was never updated. 9.08 ms is a 0.10.0 measurement, not D-059’s 8.0 ms carried forward: chunk_budget gained a seeded arm, because until it did, nothing in the bench suite wrote a chunk into a populated table and this number was unfalsifiable. It agrees with D-059 once the session is accounted for — the empty arm read 2.69 and 2.65 ms beside it against the 2.39 ms published above, so the ratio is 3.4× here and 3.35× there.

The residual is attributed as of 0.11.0 (D-142). It is not the missing index, which shipped in 0.5.6; it is the links_current write. Dropping the three links insert triggers one at a time puts effectively all of the growth in trg_links_current_sync — the single-open guard contributes none, the log trigger and the base insert ~0.35 ms of a 4.15 ms rise — and within that trigger, 89% of the growth is maintenance of idx_lc_traversal_cover and idx_lc_open_interval rather than the upsert itself, which costs 0.49 ms run directly against the same table. Page-cache size, foreign keys and the fixture’s key distribution were each tested and are each not the cause.

Knowing the cause does not by itself change the constant: the expensive index is D-042’s covering index for the traversal, so narrowing it moves cost onto the read path it exists to protect. Re-deriving these constants against the D-088 fixture matrix is the named successor.

§These are ceilings as of 0.12.0, not sizes

D-143 re-derived all four against the D-088 matrix and the edge path came back 20 against a shipped 90 — and 20 would have been wrong at 80,000 edges for the same reason 90 is wrong at 8,000, because per-row cost there grows with links_current. The finding was that no row count can bound a duration on such a path.

So the chunk loop stopped trying to pick one ahead of time. Each chunk is timed by the actor and its measured hold chooses the next size; these constants are the largest size that will ever be asked for, and every derivation below still applies to them as such. A path may run well under its constant on a populated database and at exactly it on an empty one, and both are the bound being met rather than a size being missed.

Constants§

ANNOTATIONS
Analytics annotations (write_analytics_annotations).
CONCEPTS
Concept upserts (write_concepts).
EDGES
Edge assertions (bulk_import).
EMBEDDINGS
Embedding vectors (upsert_embeddings).