From: "Denis V. Lunev" <den@openvz.org>
To: svt-core@virtuozzo.com
Cc: andrey.drobyshev@virtuozzo.com, den@virtuozzo.com
Subject: [PATCH hci-8.0 8/9] block/backup: pre-fill zero_bitmap for full/bitmap #VSTOR-134085
Date: Wed, 30 Sep 2026 13:17:38 +0200 [thread overview]
Message-ID: <20260930111739.3866427-9-den@openvz.org> (raw)
In-Reply-To: <20260930111739.3866427-1-den@openvz.org>
From: Denis V. Lunev <den@openvz.org>
Only sync=top runs an up-front scan, so the previous patch did nothing
for the other modes. FULL and BITMAP know their complete copy_bitmap
before copying starts, and the same copy-before-write guarantee holds:
a cluster still dirty when the copy loop reaches it has not been written
since the scan.
Add block_copy_calculate_zero_bitmap(), driven one query at a time from
backup_scan_source() so the graph rdlock and the pause point are taken
per query, and query each confirmed span once however many dirty areas
it covers. The scan only fills zero_bitmap and never clears copy_bitmap,
so sync=full still writes zeroes for clusters the source never
allocated; it just stops re-querying to find that out. Unlike the
per-task queries it replaces the scan is serial, so a sparsely dirtied
job against a slow source may spend more time here than it saves. The
sync=top loop had the same shape, so both fold into
backup_scan_source().
sync=none keeps the per-task query. Its bitmap says "anything may be
copied", not "this will be copied", and it only ever copies what the
guest writes during the fleecing window, so scanning the whole image at
attach is the wrong trade.
hci-8.0: sync=full already runs block_copy_reset_unallocated() since
6b1a4754713, so it stays on that path and only sync=bitmap moves to
block_copy_calculate_zero_bitmap(). Unallocated clusters are not
written for sync=full here.
Reviewed-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
CC: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
CC: John Snow <jsnow@redhat.com>
CC: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
block/backup.c | 95 +++++++++++++++++++++++---------------
block/block-copy.c | 41 ++++++++++++++++
include/block/block-copy.h | 4 ++
3 files changed, 104 insertions(+), 36 deletions(-)
diff --git a/block/backup.c b/block/backup.c
index 03ad1ad350b..eef4512b8bb 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -250,60 +250,83 @@ static void backup_init_bcs_bitmap(BackupBlockJob *job)
job_progress_set_remaining(&job->common.job, estimate);
}
-static int coroutine_fn backup_run(Job *job, Error **errp)
+/*
+ * Walk the source before copying starts: fill zero_bitmap and, for sync=top
+ * and sync=full, drop the clusters that are not allocated.
+ */
+static int coroutine_fn backup_scan_source(BackupBlockJob *s)
{
- BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
- int ret;
-
- backup_init_bcs_bitmap(s);
-
- if (s->sync_mode == MIRROR_SYNC_MODE_TOP ||
- s->sync_mode == MIRROR_SYNC_MODE_FULL)
- {
- int64_t offset = 0;
- int64_t count;
+ Job *job = &s->common.job;
+ bool skip = s->sync_mode == MIRROR_SYNC_MODE_TOP ||
+ s->sync_mode == MIRROR_SYNC_MODE_FULL;
+ int64_t offset, cached_end = 0, count;
+ int ret = 0;
- for (offset = 0; offset < s->len; ) {
- if (job_is_cancelled(job)) {
- return -ECANCELED;
- }
+ for (offset = 0; offset < s->len; offset += count) {
+ if (job_is_cancelled(job)) {
+ return -ECANCELED;
+ }
- job_pause_point(job);
+ job_pause_point(job);
- if (job_is_cancelled(job)) {
- return -ECANCELED;
- }
+ if (job_is_cancelled(job)) {
+ return -ECANCELED;
+ }
- /* rdlock protects the subsequent call to bdrv_is_allocated() */
- bdrv_graph_co_rdlock();
+ /* rdlock protects the block-status queries below */
+ bdrv_graph_co_rdlock();
+ if (skip) {
ret = block_copy_reset_unallocated(s->bcs, offset, &count);
- bdrv_graph_co_rdunlock();
- if (ret < 0) {
- return ret;
- }
+ } else {
+ block_copy_calculate_zero_bitmap(s->bcs, offset, &cached_end,
+ &count);
+ }
+ bdrv_graph_co_rdunlock();
- offset += count;
+ if (ret < 0) {
+ return ret;
}
- block_copy_set_zero_bitmap_valid(s->bcs);
}
- if (s->sync_mode == MIRROR_SYNC_MODE_NONE) {
+ block_copy_set_zero_bitmap_valid(s->bcs);
+
+ return 0;
+}
+
+static int coroutine_fn backup_run(Job *job, Error **errp)
+{
+ BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
+ int ret;
+
+ backup_init_bcs_bitmap(s);
+
+ switch (s->sync_mode) {
+ case MIRROR_SYNC_MODE_TOP:
+ case MIRROR_SYNC_MODE_FULL:
+ case MIRROR_SYNC_MODE_BITMAP:
+ ret = backup_scan_source(s);
+ if (ret < 0) {
+ return ret;
+ }
+ break;
+
+ case MIRROR_SYNC_MODE_NONE:
/*
* All bits are set in bcs bitmap to allow any cluster to be copied.
- * This does not actually require them to be copied.
+ * This does not actually require them to be copied. Yield until the
+ * job is cancelled and let the before_write notify callback service
+ * CoW requests.
*/
while (!job_is_cancelled(job)) {
- /*
- * Yield until the job is cancelled. We just let our before_write
- * notify callback service CoW requests.
- */
job_yield(job);
}
- } else {
- return backup_loop(s);
+ return 0;
+
+ default:
+ g_assert_not_reached();
}
- return 0;
+ return backup_loop(s);
}
static void coroutine_fn backup_pause(Job *job)
diff --git a/block/block-copy.c b/block/block-copy.c
index 27a5d1a41b2..a35fba45183 100644
--- a/block/block-copy.c
+++ b/block/block-copy.c
@@ -843,6 +843,47 @@ int64_t coroutine_fn block_copy_reset_unallocated(BlockCopyState *s,
return ret;
}
+/*
+ * One scan step, like block_copy_reset_unallocated(); @cached_end
+ * tracks progress across calls, @count how far @offset should move.
+ */
+void coroutine_fn GRAPH_RDLOCK
+block_copy_calculate_zero_bitmap(BlockCopyState *s, int64_t offset,
+ int64_t *cached_end, int64_t *count)
+{
+ int64_t dirty_offset, dirty_bytes, dirty_end;
+ bool found;
+
+ /* CBW mutates copy_bitmap from its own AioContext meanwhile. */
+ WITH_QEMU_LOCK_GUARD(&s->lock) {
+ found = bdrv_dirty_bitmap_next_dirty_area(s->copy_bitmap, offset,
+ s->len, INT64_MAX,
+ &dirty_offset, &dirty_bytes);
+ }
+
+ if (!found) {
+ *count = s->len - offset;
+ return;
+ }
+ dirty_end = dirty_offset + dirty_bytes;
+
+ if (*cached_end < dirty_end) {
+ int64_t clusters;
+ int64_t query_offset = MAX(dirty_offset, *cached_end);
+ int ret = block_copy_is_cluster_allocated(s, query_offset, &clusters);
+
+ if (ret >= 0) {
+ *cached_end = query_offset + clusters * s->cluster_size;
+ } else {
+ /* Best-effort: leave this area unmarked; it copies as data. */
+ *cached_end = dirty_end;
+ }
+ }
+
+ /* 0 if the query above didn't yet reach dirty_end: try again next call. */
+ *count = *cached_end >= dirty_end ? dirty_end - offset : 0;
+}
+
/*
* Decide how @task is copied: COPY_WRITE_ZEROES if it reads as zero. May
* shrink @task. Returns false if @task is to be skipped (already ended,
diff --git a/include/block/block-copy.h b/include/block/block-copy.h
index 57e13f82f78..40dd5432735 100644
--- a/include/block/block-copy.h
+++ b/include/block/block-copy.h
@@ -43,6 +43,10 @@ void block_copy_reset(BlockCopyState *s, int64_t offset, int64_t bytes);
int64_t coroutine_fn GRAPH_RDLOCK
block_copy_reset_unallocated(BlockCopyState *s, int64_t offset, int64_t *count);
+void coroutine_fn GRAPH_RDLOCK
+block_copy_calculate_zero_bitmap(BlockCopyState *s, int64_t offset,
+ int64_t *cached_end, int64_t *count);
+
int coroutine_fn block_copy(BlockCopyState *s, int64_t offset, int64_t bytes,
bool ignore_ratelimit, uint64_t timeout_ns,
BlockCopyAsyncCallbackFunc cb,
--
2.53.0
next prev parent reply other threads:[~2026-09-30 11:18 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-30 11:17 [PATCH hci-8.0 0/9] block: cheaper zero handling in backup and commit #VSTOR-134085 Denis V. Lunev
2026-09-30 11:17 ` [PATCH hci-8.0 1/9] block/commit: pass BDRV_WANT_PRECISE to block-status #VSTOR-134085 Denis V. Lunev
2026-09-30 11:20 ` no-reply
2026-09-30 11:17 ` [PATCH hci-8.0 2/9] iotests/040: cover large and fragmented commit runs #VSTOR-134085 Denis V. Lunev
2026-09-30 11:19 ` no-reply
2026-09-30 11:17 ` [PATCH hci-8.0 3/9] block/commit: batch block-status queries #VSTOR-134085 Denis V. Lunev
2026-09-30 11:21 ` no-reply
2026-09-30 11:17 ` [PATCH hci-8.0 4/9] iotests/124: cover backup of zero clusters and holes #VSTOR-134085 Denis V. Lunev
2026-09-30 11:31 ` no-reply
2026-09-30 11:17 ` [PATCH hci-8.0 5/9] block/block-copy: don't reserve memory for zero tasks #VSTOR-134085 Denis V. Lunev
2026-09-30 11:22 ` no-reply
2026-09-30 11:17 ` [PATCH hci-8.0 6/9] block/block-copy: extract block_copy_set_task_method() #VSTOR-134085 Denis V. Lunev
2026-09-30 11:22 ` no-reply
2026-09-30 11:17 ` [PATCH hci-8.0 7/9] block/block-copy: track known-zero source clusters #VSTOR-134085 Denis V. Lunev
2026-09-30 11:30 ` no-reply
2026-09-30 11:17 ` Denis V. Lunev [this message]
2026-09-30 11:33 ` [PATCH hci-8.0 8/9] block/backup: pre-fill zero_bitmap for full/bitmap #VSTOR-134085 no-reply
2026-09-30 11:17 ` [PATCH hci-8.0 9/9] block/block-copy: coalesce write-zeroes tasks #VSTOR-134085 Denis V. Lunev
2026-09-30 11:34 ` no-reply
2026-09-30 12:49 ` [PATCH hci-8.0 0/9] block: cheaper zero handling in backup and commit #VSTOR-134085 Andrey Drobyshev
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260930111739.3866427-9-den@openvz.org \
--to=den@openvz.org \
--cc=andrey.drobyshev@virtuozzo.com \
--cc=den@virtuozzo.com \
--cc=svt-core@virtuozzo.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox