Skip to content

v0.1.7 research: the chunk-predecessor mechanism's time cost — the space win was measured, the price was not #208

Description

@yifanxuaaa

Status: Research; the feature's time cost has never been measured — that is the gap this issue fills. Filed 2026-09-20 from the #205/#190 attribution round (ledger L53).

Related: #190 (retained-history read/scale) · #186 (the feature) · #205 (where the cost surfaced).

The question

LAYERFS_HISTORY_CHUNK_PREDECESSORS was introduced by 795fb1a2f ("C1 chunk cursor, C2 persisted similarity index, lean row grammar", closes #186) and defaults ON because it was measured and it wins on space. What was never measured is what it costs in time, on any row. This issue measures both axes across all three strides and then states a judgement.

What is already established — do not re-derive

The feature is a space feature, and it is a large win. From 795fb1a2f's own record, against the v0.1.6 reference:

row states apparent v0.1.6 ratio
history-stride10 17 49,053,696 49,315,840 0.99468
history-stride3 53 61,767,680 64,000,000 0.96512
history-stride1 157 80,273,408 82,685,952 0.97082

All three PASS and all three are below the v0.1.6 reference; the registered lane fell 128,864,256 → 49,053,696, a 61.9 % reduction (2.6130× → 0.99468×). Its switch documentation records the enabling measurement: 51,347,456 → 49,672,192 B apparent (−1,675,264), the native lane falling to 3,957,829 B — 228 B below v0.1.6's own 3,958,057 B — with the selection becoming byte-identical to v0.1.6's (448 FULL / 650 PREFIX). delta.trials rose 37,886 → 38,538, the first chunk trials on this lane.

And the commit is explicit that time was not part of the claim:

"No timing claim is made anywhere in this commit. The machine was not quiet (load ~5) for the lane runs; bytes and RSS are load-independent and stand."

The time cost, measured this round on stride10 alone: operation 16.296 s (predecessors ON) against 10.882 s (OFF, LAYERFS_HISTORY_CHUNK_PREDECESSORS=0). Same workload, same 17 states, same 52,032 objects, same build. content 1.585 → 1.039 s, filesystem 5.146 → 3.490 s, accept_loop 8.820 → 5.847 s.

Store cost of the same control arm, measured: apparent 49,324,032 → 51,040,256 B (+1,716,224, +3.48 %); allocated 50,249,728 → 51,298,304 B (+1,048,576, +2.09 %); pack BLOB bytes 45,297,954 → 47,036,475 (+3.84 %); packs 255 → 262; objects 52,032 → 52,032 (unchanged); value groups 1,737 → 1,737 (unchanged).

The correction this issue records

An earlier reading of the stride10 control — reported in this lane as "declining prior-state bases cut the operation 33 %, at the cost of a larger Store" — had the causality backwards. The control does not remove a cost; it removes a feature. CHUNK_PREDECESSORS=0 reproduces the arm every earlier measurement was taken on, byte for byte, so:

So the trade under examination is the reverse of how it was first stated: how much time does the space win cost, and is that price worth paying?

The experiment

One switch, two arms, three strides, time and space on both axes for every row:

row states arm switch
history-stride10 17 ON (shipped) default
history-stride10 17 OFF LAYERFS_HISTORY_CHUNK_PREDECESSORS=0
history-stride3 53 ON default
history-stride3 53 OFF LAYERFS_HISTORY_CHUNK_PREDECESSORS=0
history-stride1 157 ON default
history-stride1 157 OFF LAYERFS_HISTORY_CHUNK_PREDECESSORS=0

Reported per row: operation ns, content / filesystem / accept_loop node ns, apparent bytes, allocated bytes, pack BLOB bytes, packs created, object count, and the harness's own gate outcome.

Why stride1 matters most. Reuse volume dominates there — 121,301 reuse occurrences, 16.544 s, 34.6 % of scope — so if the predecessor cursor's per-chunk base resolution grows with history, stride1 is where it shows. The space cost should also be largest there. A stride10-only verdict would be the wrong basis for a policy that ships on all three rows.

Two limits this experiment cannot remove

  1. The switch is confounded by construction. LAYERFS_HISTORY_CHUNK_PREDECESSORS=0 disables both the predecessor offering and the chunked-construction path that consumes it. The measured delta is therefore the cost of the whole v0.1.7: Stage 6 - retained history storage families (C1+C2, stride 10/3/1) #186 chunk-cursor mechanism, not of base resolution alone. Separating them needs a narrower switch, which is not proposed here.
  2. The OFF arm's Store is not byte-identical to the recorded constant, by declaration. Byte-identity is not a criterion for the OFF arm and its absence is not a defect.

What a judgement would need

  • The time price per row (this experiment).
  • The space saving per row (this experiment, plus 795fb1a2f's v0.1.6 comparison).
  • Whether the time cost grows with history faster than the space saving does. If the cost is flat while the saving grows, ON is comfortable; if the cost grows, there is a history length beyond which the trade inverts.
  • Whether the space win is still needed. It took the registered lane from 2.61× v0.1.6 to 0.99× — i.e. it is the reason the lane now sits below the v0.1.6 reference. If that standing is a requirement, the time price is not optional regardless of its size.

Owner rulings that apply

  • One second of stride10 operation reduction is worthwhile. If the time price is ≥ 1 s on stride10, it is not a rounding error and needs an explicit ruling.
  • A small allocated-storage overage is acceptable when accompanied by good time reduction. Here the axes point in opposite directions — time up, space down — so the ruling's premise does not directly apply and an explicit decision is required rather than an inference.
  • No automatic cache growth; no invented cold stance; no pre-touched inputs; no shrunk selection.
  • Report both axes; never relabel a historical miss as a pass.

Measurement protocol

One sample per case per arm; fresh --output per run; receipts append-only; failures and deferrals retained. Both global flocks held for every resource command; a held lock defers and never waits. Quiet preflight — no named cargo/rustc/fs-bench competitor and ≥ 70 % CPU idle on the second of two one-second observations; a busy preflight consumes no sample. Rust 1.85.1, --locked, one construction worker. Diagnostic caps unchanged: 120 s / 240 s / 720 s.

All history.* rows are diagnostics — admission INELIGIBLE, O3 INCOMPLETE, every budget class NOT_RUN. Nothing here is admission evidence, and no pin may be hand-edited.

Deliverable

A three-row, two-axis table with the mechanism's time price stated per row, a judgement on whether the shipped default is right, and — if the price grows with history — the crossover point stated as a finding rather than left implicit. #186, #190 and #205 are not closed by this.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions