]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: extract multi-delete OLH grouping for use by lifecycle
authorMatthew N. Heler <matthew.heler@hotmail.com>
Fri, 6 Mar 2026 04:21:41 +0000 (22:21 -0600)
committerMatthew N. Heler <matthew.heler@hotmail.com>
Thu, 14 May 2026 20:43:49 +0000 (15:43 -0500)
Move the OLH-aware dispatch logic out of RGWDeleteMultiObj into a
standalone rgw::multi_delete::dispatch() so lifecycle expiration
can group versioned deletes of the same key and skip redundant
OLH updates.

Signed-off-by: Matthew N. Heler <matthew.heler@hotmail.com>
src/rgw/rgw_multi_del.cc
src/rgw/rgw_multi_del.h
src/rgw/rgw_op.cc
src/rgw/rgw_op.h

index 4a02212581dc5805d78862e0841971f0d7f61f69..1e903f3d070544c75fd5d4284ffb3629ffbde99c 100644 (file)
@@ -3,8 +3,11 @@
 
 #include <string.h>
 
+#include <algorithm>
 #include <iostream>
+#include <unordered_map>
 
+#include "common/async/spawn_throttle.h"
 #include "common/strtol.h" // for strict_strtoll()
 #include "include/types.h"
 
@@ -95,3 +98,65 @@ XMLObj *RGWMultiDelXMLParser::alloc_obj(const char *el) {
   return obj;
 }
 
+void rgw::multi_delete::dispatch(const std::vector<Item>& items,
+                                 bool bucket_versioned,
+                                 uint32_t max_aio,
+                                 boost::asio::yield_context yield,
+                                 Exec exec,
+                                 OnDispatch on_dispatch)
+{
+  auto group = ceph::async::spawn_throttle{yield, std::max<uint32_t>(1, max_aio)};
+
+  if (!bucket_versioned) {
+    for (size_t i = 0; i < items.size(); ++i) {
+      group.spawn([&exec, &items, i] (boost::asio::yield_context y) {
+        exec(items[i], false, y);
+      });
+      if (on_dispatch) {
+        on_dispatch();
+      }
+    }
+    group.wait();
+    return;
+  }
+
+  // Preserve first-seen order within each key group so callers can keep
+  // request/result ordering stable while coalescing intermediate OLH updates.
+  std::vector<std::vector<size_t>> grouped_items;
+  grouped_items.reserve(items.size());
+  std::unordered_map<std::string, size_t> group_index;
+  group_index.reserve(items.size());
+
+  for (size_t i = 0; i < items.size(); ++i) {
+    const auto& name = items[i].key.name;
+    auto [it, inserted] = group_index.emplace(name, grouped_items.size());
+    if (inserted) {
+      grouped_items.emplace_back();
+    }
+    grouped_items[it->second].push_back(i);
+  }
+
+  for (const auto& indexes : grouped_items) {
+    for (size_t i = 0; i + 1 < indexes.size(); ++i) {
+      const auto index = indexes[i];
+      group.spawn([&exec, &items, index] (boost::asio::yield_context y) {
+        exec(items[index], true, y);
+      });
+      if (on_dispatch) {
+        on_dispatch();
+      }
+    }
+  }
+  group.wait();
+
+  for (const auto& indexes : grouped_items) {
+    const auto index = indexes.back();
+    group.spawn([&exec, &items, index] (boost::asio::yield_context y) {
+      exec(items[index], false, y);
+    });
+    if (on_dispatch) {
+      on_dispatch();
+    }
+  }
+  group.wait();
+}
index 7f78c73cd704c261463e607522d21f4df1149f24..748e0cf6a4d4def6af2f69bde4b091f83391e875 100644 (file)
@@ -3,7 +3,11 @@
 
 #pragma once
 
+#include <functional>
 #include <vector>
+
+#include <boost/asio/spawn.hpp>
+
 #include "rgw_xml.h"
 #include "rgw_common.h"
 
@@ -66,3 +70,24 @@ public:
   RGWMultiDelXMLParser() {}
   ~RGWMultiDelXMLParser() override {}
 };
+
+namespace rgw::multi_delete {
+
+struct Item {
+  rgw_obj_key key;
+  size_t index{0};
+};
+
+using Exec = std::function<void(const Item& item,
+                                bool skip_update_olh,
+                                boost::asio::yield_context yield)>;
+using OnDispatch = std::function<void()>;
+
+void dispatch(const std::vector<Item>& items,
+              bool bucket_versioned,
+              uint32_t max_aio,
+              boost::asio::yield_context yield,
+              Exec exec,
+              OnDispatch on_dispatch = {});
+
+} // namespace rgw::multi_delete
index b9529e2524c5e6dac4d281e2b96b6a31d3c64da2..7feb78ff7d1aeec0a81c5e92d2741364759b9500 100644 (file)
@@ -8069,68 +8069,33 @@ void RGWDeleteMultiObj::handle_individual_object(const RGWMultiDelObject& object
   send_partial_response(o, del_op->result.delete_marker, del_op->result.version_id, op_ret);
 }
 
-void RGWDeleteMultiObj::handle_versioned_objects(const std::vector<RGWMultiDelObject>& objects,
-                                                 uint32_t max_aio,
-                                                 boost::asio::yield_context yield)
-{
-  auto group = ceph::async::spawn_throttle{yield, max_aio};
-  std::map<std::string, std::vector<RGWMultiDelObject>> grouped_objects;
-
-  // group objects by their keys
-  for (const auto& object : objects) {
-    const std::string& key = object.get_key();
-    grouped_objects[key].push_back(object);
-  }
-
-  // for each group of objects, handle all but the last object and skip update_olh
-  for (const auto& [_, objects] : grouped_objects) {
-    for (size_t i = 0; i + 1 < objects.size(); ++i) { // skip the last element
-      group.spawn([this, &objects, i] (boost::asio::yield_context yield) {
-        handle_individual_object(objects[i], yield, true /* skip_olh_obj_update */);
-      });
-
-      rgw_flush_formatter(s, s->formatter);
-    }
-  }
-  group.wait();
-
-  // Now handle the last object of each group with update_olh
-  for (const auto& [_, objects] : grouped_objects) {
-    const auto& object = objects.back();
-    group.spawn([this, &object] (boost::asio::yield_context yield) {
-      handle_individual_object(object, yield);
-    });
-
-    rgw_flush_formatter(s, s->formatter);
-  }
-  group.wait();
-}
-
-void RGWDeleteMultiObj::handle_non_versioned_objects(const std::vector<RGWMultiDelObject>& objects,
-                                                     uint32_t max_aio,
-                                                     boost::asio::yield_context yield)
-{
-  auto group = ceph::async::spawn_throttle{yield, max_aio};
-
-  for (const auto& object : objects) {
-    group.spawn([this, &object] (boost::asio::yield_context yield) {
-                  handle_individual_object(object, yield);
-                });
-
-    rgw_flush_formatter(s, s->formatter);
-  }
-  group.wait();
-}
-
 void RGWDeleteMultiObj::handle_objects(const std::vector<RGWMultiDelObject>& objects,
                                        uint32_t max_aio,
                                        boost::asio::yield_context yield)
 {
-  if (bucket->versioned()) {
-    handle_versioned_objects(objects, max_aio, yield);
-  } else {
-    handle_non_versioned_objects(objects, max_aio, yield);
+  std::vector<rgw::multi_delete::Item> items;
+  items.reserve(objects.size());
+
+  for (size_t i = 0; i < objects.size(); ++i) {
+    items.push_back(rgw::multi_delete::Item{
+      rgw_obj_key(objects[i].get_key(), objects[i].get_version_id()),
+      i,
+    });
   }
+
+  rgw::multi_delete::dispatch(
+      items,
+      bucket->versioned(),
+      max_aio,
+      yield,
+      [this, &objects] (const rgw::multi_delete::Item& item,
+                        bool skip_update_olh,
+                        boost::asio::yield_context y) {
+        handle_individual_object(objects[item.index], y, skip_update_olh);
+      },
+      [this] {
+        rgw_flush_formatter(s, s->formatter);
+      });
 }
 
 void RGWDeleteMultiObj::execute(optional_yield y)
index 6748f6b08fab6418a216c80aa668cd5710e862f2..9120bccc3738f52fa91c0320131fb651e1b6a657 100644 (file)
@@ -2265,10 +2265,6 @@ class RGWDeleteMultiObj : public RGWOp {
                                 optional_yield y,
                                 const bool skip_olh_obj_update = false);
 
-  void handle_versioned_objects(const std::vector<RGWMultiDelObject>& objects,
-                                uint32_t max_aio, boost::asio::yield_context yield);
-  void handle_non_versioned_objects(const std::vector<RGWMultiDelObject>& objects,
-                                    uint32_t max_aio, boost::asio::yield_context yield);
   void handle_objects(const std::vector<RGWMultiDelObject>& objects,
                       uint32_t max_aio, boost::asio::yield_context yield);