From 0390f8b77e7d6156d0fdf2334bfd5a41251da556 Mon Sep 17 00:00:00 2001 From: Christian Theune Date: Tue, 9 Dec 2025 16:11:36 +0100 Subject: [PATCH 1/2] rbd: make whole-object diff configurable We noticed that without image-map and exclusive-lock this has a substantial negative effect on performance. Co-authored: Oliver Schmidt Re PL-134246 --- .../20251209_162100_whole_object_configurable.rst | 3 +++ src/backy/sources/ceph/rbd.py | 13 +++++++++++-- src/backy/sources/ceph/source.py | 10 +++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 changelog.d/20251209_162100_whole_object_configurable.rst diff --git a/changelog.d/20251209_162100_whole_object_configurable.rst b/changelog.d/20251209_162100_whole_object_configurable.rst new file mode 100644 index 0000000..8f42083 --- /dev/null +++ b/changelog.d/20251209_162100_whole_object_configurable.rst @@ -0,0 +1,3 @@ +.. A new scriv changelog fragment. + +- Make whole object diff configurable (PL-134246) diff --git a/src/backy/sources/ceph/rbd.py b/src/backy/sources/ceph/rbd.py index 7bd74a0..50c078d 100644 --- a/src/backy/sources/ceph/rbd.py +++ b/src/backy/sources/ceph/rbd.py @@ -13,9 +13,16 @@ class RBDClient(object): log: BoundLogger - - def __init__(self, log: BoundLogger): + use_whole_object_diff: bool + + def __init__( + self, + log: BoundLogger, + # disable by default due to performance issues when not using exclusive-lock in cluster (PL-134255) + use_whole_object_diff=False, + ): self.log = log.bind(subsystem="rbd") + self.use_whole_object_diff = use_whole_object_diff def _ceph_cli(self, cmdline, encoding="utf-8") -> str: # This wrapper function for the `rbd` command is only used for @@ -67,6 +74,8 @@ def _rbd_stream(self, cmd: list[str]) -> Iterator[IO[bytes]]: @functools.cached_property def _supports_whole_object(self): + if not self.use_whole_object_diff: + return False return "--whole-object" in self._rbd(["help", "export-diff"]) def exists(self, snapspec): diff --git a/src/backy/sources/ceph/source.py b/src/backy/sources/ceph/source.py index cd4c3d1..ccc8056 100644 --- a/src/backy/sources/ceph/source.py +++ b/src/backy/sources/ceph/source.py @@ -28,7 +28,9 @@ def __init__(self, config: dict, log: BoundLogger): self.image = config["image"] self.always_full = config.get("full-always", False) self.log = log.bind(subsystem="ceph") - self.rbd = RBDClient(self.log) + self.rbd = RBDClient( + self.log, config.get("use-full-object-diff", False) + ) def ready(self) -> bool: """Check whether the source can be backed up. @@ -146,8 +148,10 @@ def verify(self, target) -> bool: return backy.utils.files_are_roughly_equal( source, target, - report=lambda s, t, o: self.revision.backup.quarantine.add_report( - QuarantineReport(s, t, o) + report=( + lambda s, t, o: self.revision.backup.quarantine.add_report( + QuarantineReport(s, t, o) + ) ), ) From 6d840f6d057fb6dea5a2347bc8ed4c00f06a63cf Mon Sep 17 00:00:00 2001 From: Oliver Schmidt Date: Tue, 31 Mar 2026 17:31:54 +0200 Subject: [PATCH 2/2] rbd: tests for _supports_whole_object overrides and detection PL-134275 --- src/backy/sources/ceph/tests/test_rbd.py | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/backy/sources/ceph/tests/test_rbd.py b/src/backy/sources/ceph/tests/test_rbd.py index 7eeef0d..d4103d0 100644 --- a/src/backy/sources/ceph/tests/test_rbd.py +++ b/src/backy/sources/ceph/tests/test_rbd.py @@ -159,3 +159,36 @@ def test_rbd_export(popen, rbdclient, tmpdir): ), ] ) + + +RBD_HELP_WHOLE_OBJECT = """\ +usage: rbd export-diff [--pool ] [--namespace ] + [--image ] [--snap ] [--path ] + [--from-snap ] [--whole-object] + [--no-progress] + +""" + + +def test_rbd_whole_object_support_detection(log): + rbd_mock = mock.Mock() + rbd_mock.return_value = RBD_HELP_WHOLE_OBJECT + client1 = RBDClient(log) # assume default: `use_whole_object_diff=False` + client1._rbd = rbd_mock + assert not client1._supports_whole_object + + client2 = RBDClient(log, use_whole_object_diff=False) + client2._rbd = rbd_mock + assert not client2._supports_whole_object + + client3 = RBDClient(log, use_whole_object_diff=True) + client3._rbd = rbd_mock + assert client3._supports_whole_object + + rbd_mock_no_whole_object = mock.Mock() + rbd_mock_no_whole_object.return_value = ( + "usage: rbd export-diff [--pool ] [--namespace ]" + ) + client4 = RBDClient(log, use_whole_object_diff=True) + client4._rbd = rbd_mock_no_whole_object + assert not client4._supports_whole_object