Skip to content

Extent maintenance: background merges and extent deletion (v0.1.0-preview.11) - #135

Merged
Hafeok merged 14 commits into
mainfrom
claude/gracious-goldberg-ufudmi
Oct 21, 2026
Merged

Hafeok merged 14 commits into
mainfrom
claude/gracious-goldberg-ufudmi

Conversation

@Hafeok

@Hafeok Hafeok commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Milestone Extent maintenance, for #110. The descriptor proposes v0.1.0-preview.11: SHACL (#123) took preview.9 and the Management plane (#134) preview.10, and main is merged into this branch. ADRs 0158–0162, with dated amendments to 0134, 0138 and 0139, were accepted by the maintainer in 1dcbe2e; ADR 0164 gains a dated amendment for the wired hook. The record is docs/traceability/2026-10-19-issue-110-extent-maintenance.md.

What this changes, and why

Object storage landed extent sets without merges, so every read of the present merged one run per checkpoint, and read cost grew without bound.

  • Background merges (ADR 0158).
    • Only the writer merges.
    • A merge writes the merged set, then swaps the catalog once under the epoch fence. It never blocks a commit or a checkpoint.
    • It rebases onto a catalog that a checkpoint or drop swapped meanwhile, or is abandoned if an input went.
    • A taken name means taking the next sequence.
    • Uploads are staged through a file in the cache and streamed in one create; the file becomes the cached copy. A merge whose output would pass the store's single-upload limit is refused before it starts (Azure 5,000 MiB, S3 5 GiB).
  • The policy (ADR 0159).
    • Size-tiered: FanOut (4) adjacent sets of similar size merge, and past the bound the smallest adjacent run merges.
    • MaxSetBytes (256 MiB) caps a set. At the defaults a chain holds at most 15 sets below half the cap.
    • Configured by DatasetOptions.ExtentMerges / Varve:Store:ExtentMerges.
  • Deletion by reader announcements (ADR 0160).
    • Every instance on an object store, the writer included, announces the interval [oldest, newest] of catalog versions it holds in readers/<id>, announcing then confirming.
    • The writer swaps, then reads the records, then deletes a set only when no record's interval overlaps the versions that named it.
    • The catalog version is the set sequence. Maintenance may poll.
    • Announcing is required. A 403 on the record fails the open and names the role. --unannounced reads without a record and fails a read of a gone extent with read-unannounced-extent-gone.
    • A forgotten reader fails with reader-forgotten and reattaches.
    • A record of an unknown format holds everything.
    • The reader record is specified as its own versioned format (storage format §8). A follower's write access is scoped to readers/ (ADR 0134 amended; Azure ABAC and an S3 prefix policy in the operator guide).
  • The shared commit index (ADR 0161). Each set carries its window's commit index as an optional ninth blob in derived format 4, so a cold start reads no log below the catalog.
  • Operations (ADR 0162).
    • varve storage, varve storage readers list and forget --reader --oldest --newest, guarded by what list showed.
    • The read commands attach to an object store.
    • /health/ready reports retention, and never turns unready because of it.
    • The operator guide's checkpoint budget is removed.

The reader contract with #105 is wired. #134 landed first, so this PR wires its hook (ADR 0164 point 6, amended 2026-10-20).

  • A tier-1 follower opens through Dataset.OpenAsync, which announces the catalog version it loads in its reader record and confirms it.
  • The no-op Hold call is removed, and Management plane, part 1: the fleet system dataset, grants as code, varve apply (v0.1.0-preview.10) #134's test is now a_follower_announces_the_catalog_version_it_opened.
  • A writer and a follower of one dataset on one server keep separate records. With Varve:ObjectStorage:ReaderId set, the follower's record is that id with -follower; the setting is now 1 to 119 characters.
  • A follower re-reads no catalog while it tails, so it retains what it opened from until it is opened again. retention reports those bytes.

What was measured

All figures are from this session's Linux container. Azurite and RustFS ran on loopback.

REC M on files, one run (--extent-maintenance M, fan-out 4, 256 MiB). The gate is relative within the run:

10 checkpoints, no merges (10 sets) 100 checkpoints, merges (5 sets) gate
lookup p50 / p99 59.0 / 123.8 µs 12.1 / 37.8 µs ≤ 1.5×: within
a subject's quads p50 / p99 9.0 / 40.9 µs 4.6 / 13.2 µs within
exact count p50 / p99 37.7 / 86.7 µs 14.9 / 31.3 µs within
as-of inside the largest set 2,358 ms (183 MiB) 2,736 ms (211 MiB) ≤ 2×: 1.16×
  • The as-of read is compared like for like, the largest set of each chain. In absolute terms it is 2,736 ms at REC M, and it grows with MaxSetBytes, because narrowing is O(set size). O(matches) narrowing is a separate issue; MaxSetBytes is unchanged for it.
  • Write amplification is 1.77 (746 MB rewritten for 420 MB checkpointed).
  • Chain length: the chain held 3 to 10 sets across the run.
  • For reference, Milestone Object storage — the log on any object store, Azure first #92's figures: lookup p50 42.0 / 98.7 µs at 10 / 100 checkpoints without merges.

First read below the catalog (--objects, M):

Tests on the rebased head (Linux):

What was found

  • The plan's tier rule stranded sizes. It needed a run of F sets in one tier. The policy property found chains where nothing ever merged, with sizes alternating either side of a tier's edge. It became the similar-size rule plus a pressure rule (ADR 0159).
  • An abandoned merge's output waited for a later round before deletion. It now goes straight to deletion.
  • A split's index kept the replaced set, holding retention high. The index rebase is generalised over merge, split and drop-merge.
  • Maintenance failed permanently when an announce failed with an IOException. It now tolerates the failure and retries next round.
  • DefaultMaxSetBytes read as 0, because of static initialisation order.
  • A test-harness artifact: two Datasets wrote one MemoryStorage while background merges ran. Tests that open a second dataset now run with merges off.

What is left


Checklist

  • Issue referenced: every commit carries Refs #110; the descriptor commit carries the closing trailer (eng/issue-refs.cs passes).
  • ADR cited: 0158–0162 are new; 0134, 0138, 0139 and 0164 are amended by dated amendment. No accepted decision is contradicted.
  • Specification updated: docs/spec/storage-format.md §7.5, §7.6 and §8.
  • API baseline updated: PublicAPI.Unshipped.txt in Varve.Store, .ObjectStorage, .Azure, .S3 and Varve.Protocol. Nothing is removed from Shipped.
  • Conformance baseline: n/a.
  • Tests: real components through public contracts; Azurite and RustFS for the services.
  • A new gate's failure path was proven: the benchmark's latency gate printed OVER on the first run's mismatched comparison before it was made like for like (quoted in the record).
  • Signed off: DCO passes, eng/dco.cs.
  • A traceability record exists.
  • dotnet run eng/ci.cs passes locally: not run as a whole. Its jobs were run separately on the merged head: the Release build (0 warnings, 0 errors); the store, server, protocol and protocol client suites; and the licence-header, CI-image, DCO, issue-reference, decision-set, release and status checks.

🤖 Generated with Claude Code

https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb

Hafeok and others added 8 commits October 19, 2026 11:35
Background merges (0158), the merge policy (0159), extent deletion by
reader announcements (0160), the shared commit index (0161), and storage
operations with the readers command (0162), filed unaccepted (ADR 0066).
0134, 0138 and 0139 amended by dated amendment. The storage format gains
the commits blob in 7.5 and the reader record as its own format in 8; two
problem types, reader-forgotten and read-unannounced-extent-gone.

Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
The writer's maintenance merges extent sets by a size-tiered policy with a
fan-out and a cap, writes the merged set, then swaps the catalog once under
the fence, rebasing onto a catalog swapped meanwhile; a taken name takes
the next sequence. The catalog version is the set sequence. Every instance
on an object store announces the interval of versions it reads in
readers/<id>, and the writer deletes a set only when no interval overlaps
the versions that named it. Each set carries its commit index, so a cold
start reads no log below the catalog. Uploads are staged through the cache
and streamed in one create, and a merge past the store's single-upload
limit is refused up front. The adapters raise
ObjectStoreAccessDeniedException on 401 and 403.

Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
varve storage prints the catalog's version, the sets per permutation, the
unnamed sets the store keeps and the bytes each reader record retains;
readers list and forget, guarded by the interval list showed. The read
commands attach to an object store, announced or with --unannounced.
/health/ready reports retention and never turns unready because of it.
Varve:Store:ExtentMerges and Varve:ObjectStorage:ReaderId.

Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
The checkpoint budget is replaced by the merge policy, its measured write
amplification and the readers command; the follower's write access to
readers/ for Azure and S3; the traceability record of #110.

Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
The descriptor, the changelog's projection, README's status, the roadmap
and AGENTS.md's state.

Closes #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
ADR 0159, the operator guide and the release notes carry the second
REC M run, where the gate compares the largest set of each chain; the
benchmarks README gains the milestone's tables beside #92's figures.

Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
The ADRs and amendments, the departure from the plan, the gate's as-of
reading for the maintainer, the properties with counts and durations,
the defects they found, and the benchmarks.

Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
ADR 0159 and the benchmarks README state the as-of read inside the
largest set at REC M, 2,736 ms, growing with MaxSetBytes since narrowing
is O(set size). ADR 0162 states that verify waits for #117 and multipart
uploads stay a later decision. The record carries the approval, the
release number rule and the reader contract with #105.

Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb

Hafeok commented Oct 19, 2026

Copy link
Copy Markdown
Owner Author

CI on b41564b is red only on CS0618, as expected for an unaccepted milestone.

Every failed job stops in its build step. The errors are the 16 citations of the not-yet-accepted decisions in background-extent-merges, extent-deletion-by-reader-announcements and the-shared-commit-index (ADRs 0158, 0160, 0161). The analyzer reads them as "Not accepted … a build with warnings as errors will not ship it". There are no other compiler, analyzer or test errors.

The failed jobs are build (ubuntu-latest), native aot, browser wasm, browser storage in Chromium, storage (services), samples, samples (eventhubs), both container legs, conformance, both auth legs and decision report. In auth (zitadel) the log tail is the Zitadel dump that the failure step prints after the build.

  • Fix: accepting ADRs 0158–0162 clears this. Nothing in the code changes. I'm not re-running anything, because a re-run gives the same result until then.
  • Windows store-step duration: the build step fails first, so CI won't report the store step until the decisions are accepted. I'll quote the Windows and Linux store-step times here and in the traceability record from the first run after acceptance, against CI: time limits, hang dumps and per-test durations for the store-test steps #126's 8 min, 29 min and 53 min.
  • agent review: this waits on the responsible human's approve <head> comment.

Generated by Claude Code

Accepts the decision sets of milestone Extent maintenance: background merges (0158), the merge policy (0159), extent deletion by reader announcements (0160), the shared commit index (0161) and storage operations (0162), with the amendment to 0134.

Refs #110

Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
…ance

SHACL took v0.1.0-preview.9, so this descriptor is renumbered to
v0.1.0-preview.10 (ADR 0102). README's status, AGENTS.md's state, the ADR
index and the benchmarks README keep both milestones, and the changelog
is projected again.

Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
@Hafeok Hafeok changed the title Extent maintenance: background merges and extent deletion (v0.1.0-preview.9) Extent maintenance: background merges and extent deletion (v0.1.0-preview.10) Oct 19, 2026
Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
@Hafeok

Hafeok commented Oct 20, 2026

Copy link
Copy Markdown
Owner Author

approve 7b40629

Hafeok and others added 2 commits October 20, 2026 10:23
…tent maintenance

#134 took v0.1.0-preview.10, so this descriptor is v0.1.0-preview.11
(ADR 0102). #134 landed first, so this merge wires its reader-contract
hook (ADR 0164 point 6, amended): a follower announces the catalog version
it opens through Dataset.OpenAsync, and the no-op Hold call is removed;
its test now reads the follower's reader record. A writer and a follower
of one dataset on one server keep separate reader records: with
Varve:ObjectStorage:ReaderId set, the follower's is the id with
-follower. Readiness carries the role and retention, not-ready its
management, datasets and retention members, and the problem catalogue
both milestones' types. The traceability record gains CI's store-step
times.

Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
#134 moved the checkpoint route to the management plane (ADR 0163).

Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
…t maintenance

Refs #110

Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014BgePW3C3ECWERkiQMVdPb
@Hafeok Hafeok changed the title Extent maintenance: background merges and extent deletion (v0.1.0-preview.10) Extent maintenance: background merges and extent deletion (v0.1.0-preview.11) Oct 21, 2026
@Hafeok

Hafeok commented Oct 21, 2026

Copy link
Copy Markdown
Owner Author

approve 0b02803

@Hafeok
Hafeok merged commit 0b02803 into main Oct 21, 2026
93 checks passed
@Hafeok
Hafeok deployed to auth-providers October 21, 2026 10:59 — with GitHub Actions Active
@Hafeok
Hafeok deployed to auth-providers October 21, 2026 10:59 — with GitHub Actions Active
Hafeok added a commit that referenced this pull request Oct 21, 2026
…art 2a

#135 took preview.11, so this part's descriptor is renumbered to
v0.1.0-preview.12 and the changelog re-projected. README Status names
preview.12 first, with preview.11 and preview.10 behind it, and the image
tags at preview.12; AGENTS.md's State keeps part 2a first, then Extent
maintenance. The benchmarks README keeps #110's section before this
part's, and states that #110's REC runs were taken on generator output
version 1 too.

Refs #138
Signed-off-by: Emil Okkels Klein <hannibal.okkels@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RFtkE4cRmAMRARHDu5Y9ZA

This branch was successfully deployed

2 active deployments
gates — 0b028032 Deployed Oct 21, 2026 by Hafeok via agent review (judge) #319
auth-providers — 0b028032 Deployed Oct 21, 2026 by Hafeok via gates / auth (providers) #36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant