| `NOTE-statfs-unusable-divergence.md` | review flag for stripe-alloc patch 7 | -- | open question for maintainers |
| `DESIGN-3a-full-stripe-batching.md` | design note, now implemented (patches 10, 16-17, 21-23, 30-35) | -- | historical, status header added |
| `DESIGN-3b-log-relocation.md` | design note, now implemented (patches 13-15) | -- | historical, status header added |
-| `balance-resume` (no cover letter) | `btrfs: track balance progress with virtual address range` (Zygo, 6bfe2929) -- persisted vrange cursor replacing the usage=90 resume heuristic; on both lanes as `topics/balance-resume` | new topic | ready; self-contained message |
+| `balance-resume.txt` | [PATCH] btrfs: track balance progress with virtual address range (Zygo, 6bfe2929) -- persisted vrange cursor replacing the usage=90 resume heuristic | `misc-next/topics/balance-resume` (1), also on 6.18 | ready to send; directed test balance-resume-test.sh (unrun, VMs busy 2026-09-17) |
| `forensics/balance-zerofill-2026-08-07/` | forensic record of the liveness-map bug (patch 9) and its scanners | -- | historical |
Not covered here but also upstream candidates: `lib/raid6: fix the x1
--- /dev/null
+[PATCH] btrfs: track balance progress with virtual address range
+
+A paused or interrupted balance resumes today with a usage=90 filter
+that the kernel adds on its own (update_balance_args()). It was meant
+to avoid re-balancing block groups the run had already packed, but it
+applies to every block group the request has not reached yet as well:
+any of those more than 90% full is silently skipped, and the resumed
+balance finishes having done less than the original request asked for.
+A full-device convert or a profile change can leave block groups behind
+this way without saying so.
+
+This patch records progress instead. The chunk tree is walked in
+descending virtual address order, so after each chunk is considered the
+upper bound of the vrange filter drops to that chunk's address (a
+0..current range is created when the request had none). The cursor is
+written into the balance item during transaction commit, as device stats
+and device-replace progress are, and committed on pause so it survives
+an unmount or a read-only remount without another transaction. The
+request's own limit and filters are untouched; resume simply continues
+below the cursor, full block groups included. Nothing new is exposed:
+vstart/vend are the existing vrange fields, visible with
+`btrfs balance status -v`.
+
+Independent of the raid56 stripe-exclusive allocation series (it is a
+separate topic on both lanes, topics/balance-resume, and this cover is
+kept separate on purpose).
+
+Testing: balance-resume-test.sh in the raid56-wh-harness directed set --
+a single-profile filesystem with several data chunks, most ~100% full;
+a data balance started and paused after its first chunk; the cursor read
+back from `btrfs balance status -v` (vrange=0..N, and no usage= filter);
+unmount; remount with skip_balance and the cursor read again from the
+on-disk item; resume; then the set of block groups the resumed balance
+relocated (from the kernel's "relocating block group" messages) must be
+exactly the data chunks below the cursor, each once, the full ones
+included, and nothing at or above it revisited. The test skips on a
+kernel without btrfs_run_balance().
+
+ fs/btrfs/transaction.c | 3 ++
+ fs/btrfs/volumes.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++-----------
+ fs/btrfs/volumes.h | 3 ++
+ 3 files changed, 143 insertions(+), 28 deletions(-)