Skip to content

Implement in-place seed for assembly pipeline#323

Open
folbricht wants to merge 16 commits into
masterfrom
inplace-seed
Open

Implement in-place seed for assembly pipeline#323
folbricht wants to merge 16 commits into
masterfrom
inplace-seed

Conversation

@folbricht

Copy link
Copy Markdown
Owner

Summary

Refactors the assembly pipeline into a plan-based architecture and adds in-place seed support, enabling efficient file reconstruction when the target file already contains chunk data at different offsets.

  • Introduces AssemblePlan that pre-computes all chunk placements into a DAG of steps with explicit dependencies, replacing the interleaved sequencer approach
  • Adds InPlaceSeed which rearranges chunks already present in the target file using Tarjan's SCC algorithm for cycle detection and buffer-break resolution
  • Splits assembly sources into composable types: skipInPlace, inPlaceCopy, fileSeedSource, selfSeedSegment, copyFromStore
  • Self-seed matching uses longestMatchFrom for longer contiguous sequences
  • Plan validation detects stale file seeds before execution starts
  • Concurrent validation and initial in-place scan
  • Integration test exercising all source types together (in-place skip, in-place copy with cycles, file seed, self-seed, store fetch) with variable-size chunks

Closes #312

folbricht added 11 commits July 4, 2026 13:23
Introduce AssemblePlan that separates planning from execution in file
assembly. The plan pre-computes all chunk placements (self-seed, file
seeds, store fetches, skip-in-place) into a DAG of steps with explicit
dependencies, replacing the interleaved sequencer approach.

This lays the groundwork for #312 (destination-as-seed) by making
assembly sources composable and the planning phase extensible.

Key changes:
- New AssemblePlan with functional options and step-based execution
- Split assembly sources into separate files (fileseed, selfseed, store,
  skip)
- Self-seed matching now uses longestMatchFrom for longer sequences
- Plan validation detects stale file seeds before execution
- Comprehensive tests for plan generation and in-place detection
- Remove sequencer.go, selfseed.go in favor of new plan types

Closes #312
folbricht added 5 commits July 4, 2026 18:22
- Replace deprecated os.SEEK_SET with io.SeekStart in fileSeedSegment.copy
- Fix stale NewIndexSeed doc comment on NewFileSeed
- Update test comment referring to the removed writeChunk function
- Use the min builtin instead of shadowing max in selfSeed matching
- Drop the never-failing error return from AssemblePlan.generate
- Return only the match count from maxMatchFrom; the position is the
  caller's own argument
- Extract a newSeed helper in the CLI for the in-place-vs-file seed
  decision duplicated between readSeeds and readSeedDirs
- Remove Size from the SeedSegment interface; its last interface-level
  caller went away with the sequencer
LongestMatchFrom returned a 4-tuple of byte and chunk coordinates of
which every caller used a different half: the computed byte length had
no callers at all, the null seed had to fabricate meaningless offsets,
and FileSeed.GetSegment re-derived the chunk range from the byte offset
with a binary search even though LongestMatchFrom had just computed it.

LongestMatchFrom now returns only (pos, n) in chunk coordinates and
GetSegment takes the same coordinates, slicing the seed index directly.
The nullChunkSection no longer tracks a size; WriteInto already receives
the length to write.
- Extract a shared seedIndex type (index + position lookup + matching
  kernel) used by both FileSeed and selfSeed, replacing two near-
  identical LongestMatchFrom implementations and three copies of the
  position-map construction. The kernel resolves the tie-break drift
  between the two copies: later candidates now win ties for both seeds,
  which for file seeds picks a byte-identical range
- Extract claimRun for the run-claiming loop that was duplicated
  between the self-seed and file-seed passes in generate(), and
  chunkRangeLength for the byte-length expression repeated across the
  plan and segment code
- Move fileSeedSegment's copy/clone to package-level copyRange and
  cloneOrCopyRange, and use them from selfSeedSegment.Execute too. The
  self-seed previously called CloneRange directly for its whole range,
  without the unaligned head/tail handling or the copy fallback that
  cloneOrCopyRange has, so cloning failed on unaligned segments or
  filesystems like ZFS
- The self-seed now opens its read handle per segment execution, the
  same pattern fileSeedSegment uses for its seed file. That removes the
  reader-handle pool and with it the entire Close lifecycle on selfSeed
  and AssemblePlan as well as the error return of NewPlan
- Share a chunkInPlace helper between the two seed validation paths and
  the generateSkips scan
- Drop the unused needValidation field from fileSeedSegment
- Exclude the null-chunk ID from the self-seed lookup. The self-seed
  pass runs before the seeds are matched, so runs of zero chunks were
  claimed as chained self-copies: real read/write I/O serialized by
  dependencies, allocating disk blocks on sparse targets. The null seed
  handles them as no-ops on blank targets and without dependencies
- Merge generateInPlace's two classification passes into one and find
  in-place chunks with a binary search over the (ascending) source
  offsets instead of scanning every occurrence, which was quadratic for
  chunks that repeat often in the seed
- Compute the target blocksize once per plan instead of calling stat
  through blocksizeOfFile on every seed step execution
- Run generateSkips on a fixed pool of workers with a reused read
  buffer per worker, instead of spawning one goroutine and one buffer
  allocation per chunk
- Bound self-seed matching: the overlap clamp is now applied as the
  match limit, which also bounds the work done per candidate, and at
  most 100 candidate positions are examined per match, mirroring the
  100-chunk run limit that already applies without reflinks
- Pass the in-place seed to the plan explicitly via PlanWithInPlaceSeed
  instead of fishing it out of the generic seed list with type
  assertions in AssembleFile and twice in plan generation. AssembleFile
  partitions the seeds once
- Move chunk accounting onto the assembleSource interface as
  recordStats, replacing the worker's type switch over all six concrete
  source types, which new sources would silently fall out of
- Replace the File() == "" sentinel in plan validation with an explicit
  needsValidation method on assembleSeedSource
- Replace the stepSet wrapper with a plain map and a link helper that
  sets both directions of a dependency edge, halving each linking site
- Sort SCCs with SortStableFunc and a key closure instead of three
  auxiliary index slices
- Replace the backward goto in AssembleFile's plan validation with a
  retry loop
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.

desync: use destination chunks as an additional seed source

1 participant