Skip to content

feat: allow replicas on full disks reclaim the space and squash all data into the heads - #572

Open
apoorvajagtap wants to merge 5 commits into
longhorn:masterfrom
apoorvajagtap:task-13636
Open

feat: allow replicas on full disks reclaim the space and squash all data into the heads#572
apoorvajagtap wants to merge 5 commits into
longhorn:masterfrom
apoorvajagtap:task-13636

Conversation

@apoorvajagtap

@apoorvajagtap apoorvajagtap commented Aug 5, 2026

Copy link
Copy Markdown

Which issue(s) this PR fixes:

Issue longhorn/longhorn#13636

What this PR does / why we need it:

This PR so far:

  • Scan all snap & head files to build a sector location list/map that indicates which snap file contains the latest data chunk at a specific offset.
  • For each sector [offset, length], if there is the latest data in a snap based on the above sector location map, directly and blindly punches holes for all snaps older than the snap.
  • moves the data from the snap file to the head then punches hole for those specific sectors in the snap file.

Special notes for your reviewer:

Additional documentation or context

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 428 lines in your changes missing coverage. Please review.
✅ Project coverage is 2.80%. Comparing base (d209d2c) to head (46482f6).

Files with missing lines Patch % Lines
pkg/local/replica/recoverer/sectormap/chain.go 0.00% 81 Missing ⚠️
pkg/local/replica/recoverer/coalesce/coalesce.go 0.00% 69 Missing ⚠️
pkg/local/replica/recoverer/prune/punch.go 0.00% 69 Missing ⚠️
.../local/replica/recoverer/sectormap/location_map.go 0.00% 51 Missing ⚠️
pkg/local/replica/recoverer/sectormap/output.go 0.00% 45 Missing ⚠️
pkg/local/replica/recoverer_linux.go 0.00% 43 Missing ⚠️
cmd/local/subcmd/recover.go 0.00% 38 Missing ⚠️
pkg/local/replica/recoverer/sectormap/load.go 0.00% 25 Missing ⚠️
pkg/local/replica/recoverer/common/common.go 0.00% 6 Missing ⚠️
cmd/local/longhornctl-local.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##           master    #572      +/-   ##
=========================================
- Coverage    3.14%   2.80%   -0.34%     
=========================================
  Files          42      51       +9     
  Lines        3629    4057     +428     
=========================================
  Hits          114     114              
- Misses       3512    3940     +428     
  Partials        3       3              
Flag Coverage Δ
unittests 2.80% <0.00%> (-0.34%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@apoorvajagtap apoorvajagtap changed the title Allows replicas on full disks reclaim the space and squash all data into the heads [WIP] Allows replicas on full disks reclaim the space and squash all data into the heads Aug 5, 2026
@apoorvajagtap
apoorvajagtap marked this pull request as draft August 5, 2026 08:41
@apoorvajagtap
apoorvajagtap force-pushed the task-13636 branch 3 times, most recently from 684faa7 to 217130e Compare August 6, 2026 15:54
@apoorvajagtap
apoorvajagtap marked this pull request as ready for review August 6, 2026 15:55
@apoorvajagtap apoorvajagtap changed the title [WIP] Allows replicas on full disks reclaim the space and squash all data into the heads Allows replicas on full disks reclaim the space and squash all data into the heads Aug 7, 2026
@apoorvajagtap

Copy link
Copy Markdown
Author

@derekbit @shuo-wu I also had to update the vendored packages. I've kept those changes in separate commits to make the review easier. Let me know if there's a preferred approach for handling vendored package updates. Thanks!

@derekbit

derekbit commented Aug 7, 2026

Copy link
Copy Markdown
Member

@derekbit @shuo-wu I also had to update the vendored packages. I've kept those changes in separate commits to make the review easier. Let me know if there's a preferred approach for handling vendored package updates. Thanks!

We usually put a commit for updating vendor packages in the same PR. The commit title can be chore(vendor): update dependencies.

@apoorvajagtap apoorvajagtap changed the title Allows replicas on full disks reclaim the space and squash all data into the heads feat: allow replicas on full disks reclaim the space and squash all data into the heads Aug 7, 2026
@apoorvajagtap
apoorvajagtap force-pushed the task-13636 branch 6 times, most recently from 1e30f3a to 46482f6 Compare August 14, 2026 12:46
@mergify

mergify Bot commented Aug 16, 2026

Copy link
Copy Markdown

This pull request is now in conflict. Could you fix it @apoorvajagtap? 🙏

Comment thread cmd/local/subcmd/recover.go Outdated
Comment thread cmd/local/subcmd/recover.go Outdated
Comment thread cmd/local/subcmd/recover.go Outdated
Comment thread pkg/local/replica/recoverer/sectormap/load.go
Comment thread pkg/local/replica/recoverer/sectormap/load.go
Comment on lines +37 to +50
func dumpExtentsForFile(name string, f *os.File, totalSectors int64) error {
extents, err := getAllExtents(f, uint64(totalSectors)*sectorSize)
if err != nil {
return fmt.Errorf("failed to read extents for %s: %w", name, err)
}
fmt.Printf("=== %s: %d extents ===\n", name, len(extents))
for _, e := range extents {
startSector := int64(e.Logical) / sectorSize
lengthSectors := int64(e.Length) / sectorSize
fmt.Printf(" logical=%d length=%d -> sectors [%d, %d)\n",
e.Logical, e.Length, startSector, startSector+lengthSectors)
}
return nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we directly ask SectorMapping to dump its ExtentCache[fName] when it is not empty? getAllExtents is resource-consuming and should not be called as long as there is cache already.

Besides, whenExtentCache[fName] is empty, it means that the corresponding snapshot file has all data expired. There is no need to intentionally print out its extent. Instead, you can print a single line directly, like file xxx all data (size xxx) is expired, will skip retrieve its extents.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually introduced a separate field to maintain the list of obsoleteFiles, to make the deletion of these files easier. Given that, modified the dumpExtentsForFile logic as well to print the range per ExtentCache[fName], and print the warning for all files in smap.ObsoleteFiles (for better checks). Let me know in case the printing of obsoleteFiles does not seem very relevant anymore.

Comment thread pkg/local/replica/recoverer/sectormap/output.go Outdated
location, names := smap.Location, smap.Names

runStart := int64(0)
runOwner := Owner(location, names, 0, fallbackName)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fine with directly skipping this location if it's not in snap/head files. There is no need to figure out if it is in the backing file

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in the latest commit. PTAL.

fmt.Println("[dry-run] No holes to punch.")
return nil
}
if !common.Confirm("Do you want to proceed with hole punching?") {

@shuo-wu shuo-wu Aug 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if we need to cache ops for this confirmation. The SectorMapping may already have consumed lots of memory if the file data is scattered (e.g., the data is like 4k hole 4k hole...)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we wanna skip the caching of ops, then scanning the extents twice could be an alternate, with just maintaining the opCount (instead of whole offset, length details).
This might be cheaper given the location & ExtentCache are already in memory?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried scanning twice, so we don't pile up the memory with ops cache, will push the changes for further review. Along with this, I am planning to eliminate the extra dry-run scan for PromoteToHead(), as that seems a little overkill, given the sectors have already been punched, and it's now just about squashing everything at one place. So, probably, just a question about Do you want to proceed with promoting sectors to head? would do at this stage, without printing each sector being copied. WDYT?

}

for _, ancestor := range ancestors {
allocated := intersect(runStart, runEnd, smap.ExtentCache[ancestor])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This operation in this loop in total may be expensive. But currently, I have no better idea

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a binary search for func intersect? Then each intersect() takes O(log n) time.

    // first interval whose runEnd > smap.ExtentCache[ancestor].Start — the 1st candidate that can overlap
    i := sort.Search(len(smap.ExtentCache[ancestor]), func(i int) bool { return smap.ExtentCache[ancestor][i].End > runStart })

Signed-off-by: Apoorva Jagtap <apoorva.jagtap@suse.com>
Signed-off-by: Apoorva Jagtap <apoorva.jagtap@suse.com>
Signed-off-by: Apoorva Jagtap <apoorva.jagtap@suse.com>
@apoorvajagtap

Copy link
Copy Markdown
Author

The following are still WIP, I'll update the comment once all the comments are addressed.
#572 (comment)
#572 (comment)

If b/w permits in the meantime, please feel free to look through the latest commits with rest of the changes. Thanks!

Signed-off-by: Apoorva Jagtap <apoorva.jagtap@suse.com>
Signed-off-by: Apoorva Jagtap <apoorva.jagtap@suse.com>
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.

3 participants