Skip to content

dedupe: make the extent loader order-independent (Stage 2.1) - #112

Merged
martinus merged 3 commits into
masterfrom
feat/extent-order-independent
Jul 24, 2026
Merged

dedupe: make the extent loader order-independent (Stage 2.1)#112
martinus merged 3 commits into
masterfrom
feat/extent-order-independent

Conversation

@martinus

Copy link
Copy Markdown
Owner

What & why

Prerequisite for the streaming dedupe rewrite (Stage 2). GET_DUPLICATE_EXTENTS now excludes extents whose file is a whole-file dup-group member statically — via a filedup CTE mirroring GET_DUPLICATE_FILES' membership test, applied everywhere extents is referenced.

Previously that exclusion was temporal: the whole-file pass deleted those extent rows (dbfile_remove_extent_hashes) before the extent loader ran in the same pass. Making it static means the extent load no longer depends on the whole-file pass having finished — the prerequisite for pipelining the two passes (Stage 2.2/2.3). The end state is identical, since every whole-file member's extents get deleted for real anyway.

Tests

New tests/integration/test_extent_order_independent.py (btrfs; fsync-forced extent boundary like test_extent_dedupe), written against the pre-change build first and confirmed still passing after:

  • Group survives removal of whole-file members — A,B whole-file identical + C,D sharing only the tail; the tail extent group {A,B,C,D} must still dedupe {C,D} once A,B are excluded.
  • Group dropped when only whole-file members remain — A,B whole-file + a single extent-sharer C; excluding A,B leaves one member, so C stays independent (matches the old temporal behavior).

make check clean (107 tests). No schema change (query-only).

🤖 Generated with Claude Code

GET_DUPLICATE_EXTENTS now excludes extents whose file is a whole-file
dup-group member statically, via a `filedup` CTE mirroring
GET_DUPLICATE_FILES' membership test, everywhere `extents` is referenced.
Previously that exclusion happened *temporally*: the whole-file pass deleted
those extent rows (dbfile_remove_extent_hashes) before the extent loader ran
in the same pass. Making it static means the extent load no longer depends on
whole-file completion - the prerequisite for pipelining the two passes in the
streaming rewrite. End state is identical (every whole-file member's extents
are deleted for real anyway), pinned by the new regression test.

test_extent_order_independent.py covers both a group that survives removal of
its whole-file members (>=2 extent members remain) and the degenerate case
where removing them drops it below 2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
martinus and others added 2 commits July 24, 2026 08:13
The filedup membership CTE was copy-pasted between GET_DUPLICATE_EXTENTS and
dbfile_count_dupe_bytes; if the flags bit or the digest+size grouping ever
changed, the extent loader and the progress byte-total could silently drift
(exactly the double-count the exclusion prevents). Factor the CTE body into a
single FILEDUP_CTE macro composed as "with " FILEDUP_CTE ... in both places.
No query-plan change; make check clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The filedup CTE (a full-table group-by over files) ran on every per-batch
GET_DUPLICATE_EXTENTS load. On a 3.5M-file hashfile that materialization
costs ~6 s per batch - 54 batches of producer-serialized stall while the
dedupe pool sits idle, reported as the phase being stuck at 'loading
duplicate extents'.

Replace the set with a correlated exists probe (FILEDUP_MEMBER, backed by
idx_files_digest_size) that only touches the rows the enclosing query
already examines. Measured on that hashfile: one batch extent load drops
21.7 s -> 0.2 s; the once-per-phase byte count drops 13.6 s -> 9.5 s.
Row-for-row identical loader output verified across four generation
windows (including a full-phase window, 29639 rows).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@martinus

Copy link
Copy Markdown
Owner Author

Field report on a 3.5M-file hashfile: the filedup CTE (full-table group-by) ran on every per-batch GET_DUPLICATE_EXTENTS load — ~6 s of producer-serialized stall per batch (54 batches), visible as the dedupe phase sitting at "loading duplicate extents" while the pool idled.

Pushed 0284a7c: the membership set is now a correlated exists probe (FILEDUP_MEMBER, backed by idx_files_digest_size) that only touches rows the enclosing query already examines.

  • One batch extent load on that hashfile: 21.7 s → 0.21 s
  • Once-per-phase byte count: 13.6 s → 9.5 s
  • Loader output verified row-for-row identical across four generation windows (incl. a full-phase window, 29,639 rows)
  • make check 112/112; cold bench.py -p realistic,many vs master: no regression (see dedupe: streaming pipeline with a persistent pool (Stage 2) #113 comment for the table)

🤖 Generated with Claude Code

@martinus
martinus merged commit 2d0da2b into master Jul 24, 2026
6 checks passed
@martinus
martinus deleted the feat/extent-order-independent branch July 24, 2026 07:37
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