This is an automated email from the ASF dual-hosted git repository.

wyxxxcat pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new d9d60bee9fe [fix](cloud) Add configurable GCS object deletion (#70119)
d9d60bee9fe is described below

commit d9d60bee9fea41273206052e7eba9b331c7f9959
Author: Yixuan Wang <[email protected]>
AuthorDate: Fri Nov 6 11:46:49 2026 +0800

    [fix](cloud) Add configurable GCS object deletion (#70119)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: None
    
    Problem Summary: Recycler previously used the generic S3 accessor for
    GCS. Add a GCS accessor that deletes objects individually for file lists
    and prefixes while preserving expiration filtering and missing-object
    handling.
    
    The enable_gcs_batch_delete_object startup configuration selects the GCS
    accessor and is enabled by default. Disabling it retains S3Accessor.
    
    ### Release note
    
    GCS recycling uses individual-object deletion by default. Set
    enable_gcs_batch_delete_object=false to retain the generic S3 accessor.
    
    ### Check List (For Author)
    
    - Test: Not run, per user instruction; no build, formatting checks, or
    unit tests
    - Behavior changed: Yes, GCS uses GcsAccessor by default with an opt-out
    configuration
    - Does this need documentation: No
    
    ### What problem does this PR solve?
    
    Issue Number: close #xxx
    
    Related PR: #xxx
    
    Problem Summary:
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [ ] Regression test
        - [ ] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason <!-- Add your reason?  -->
    
    - Behavior changed:
        - [ ] No.
        - [ ] Yes. <!-- Explain the behavior change -->
    
    - Does this need documentation?
        - [ ] No.
    - [ ] Yes. <!-- Add document PR link here. eg:
    https://github.com/apache/doris-website/pull/1214 -->
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label <!-- Add branch pick label that this PR
    should merge into -->
---
 cloud/src/common/config.h          |  2 +
 cloud/src/recycler/s3_accessor.cpp | 88 +++++++++++++++++++++++++++++++++++++-
 cloud/src/recycler/s3_accessor.h   | 11 +++++
 3 files changed, 100 insertions(+), 1 deletion(-)

diff --git a/cloud/src/common/config.h b/cloud/src/common/config.h
index 949f4633960..92282d1b4c0 100644
--- a/cloud/src/common/config.h
+++ b/cloud/src/common/config.h
@@ -326,6 +326,8 @@ CONF_String(priority_networks, "");
 
 CONF_Bool(enable_cluster_name_check, "false");
 
+CONF_Bool(enable_gcs_batch_delete_object, "true");
+
 // Default HTTP scheme used by S3Client when the endpoint has no scheme.
 CONF_String(s3_client_http_scheme, "https");
 CONF_Validator(s3_client_http_scheme, [](const std::string& config) -> bool {
diff --git a/cloud/src/recycler/s3_accessor.cpp 
b/cloud/src/recycler/s3_accessor.cpp
index 0413044d3fa..b84a2e4c64a 100644
--- a/cloud/src/recycler/s3_accessor.cpp
+++ b/cloud/src/recycler/s3_accessor.cpp
@@ -329,7 +329,17 @@ std::string S3Accessor::to_uri(const std::string& 
relative_path) const {
 
 int S3Accessor::create(S3Conf conf, std::shared_ptr<S3Accessor>* accessor) {
     TEST_SYNC_POINT_RETURN_WITH_VALUE("S3Accessor::init.s3_init_failed", 
(int)-1);
-    *accessor = std::make_shared<S3Accessor>(conf);
+    switch (conf.provider) {
+    case S3Conf::GCS:
+        if (!config::enable_gcs_batch_delete_object) {
+            *accessor = std::make_shared<GcsAccessor>(conf);
+            break;
+        }
+    default:
+        *accessor = std::make_shared<S3Accessor>(conf);
+        break;
+    }
+
     return (*accessor)->init();
 }
 
@@ -613,4 +623,80 @@ int S3Accessor::check_versioning() {
     return obj_client_->check_versioning(conf_.bucket).status.code;
 }
 
+int GcsAccessor::delete_prefix_impl(const std::string& path_prefix, int64_t 
expiration_time) {
+    LOG_INFO("begin delete prefix").tag("uri", to_uri(path_prefix));
+
+    int ret = 0;
+    int cnt = 0;
+    int skip = 0;
+    int64_t del_nonexisted = 0;
+    int del = 0;
+    auto iter = obj_client_->list_objects({.bucket = conf_.bucket, .prefix = 
get_key(path_prefix)});
+    for (;;) {
+        auto result = iter->next();
+        if (!result.object.has_value()) {
+            if (result.resp.status.code != ObjStorageStatus::NOT_FOUND &&
+                result.resp.status.code != ObjStorageStatus::OK) {
+                ret = result.resp.status.code;
+            }
+            break;
+        }
+        auto& obj = *result.object;
+        if (!(++cnt % 100)) {
+            LOG_INFO("loop delete prefix")
+                    .tag("uri", to_uri(path_prefix))
+                    .tag("total_obj_cnt", cnt)
+                    .tag("deleted", del)
+                    .tag("del_nonexisted", del_nonexisted)
+                    .tag("skipped", skip);
+        }
+        if (expiration_time > 0 && obj.mtime_s > expiration_time) {
+            skip++;
+            continue;
+        }
+        del++;
+
+        // FIXME(plat1ko): Delete objects by batch with genuine GCS client
+        int del_ret =
+                obj_client_->delete_object({.bucket = conf_.bucket, .key = 
obj.key}).status.code;
+        del_nonexisted += (del_ret == ObjStorageStatus::NOT_FOUND);
+        static_assert(ObjStorageStatus::OK == 0);
+        if (del_ret != ObjStorageStatus::OK && del_ret != 
ObjStorageStatus::NOT_FOUND) {
+            ret = del_ret;
+        }
+    }
+
+    LOG_INFO("finish delete prefix")
+            .tag("uri", to_uri(path_prefix))
+            .tag("total_obj_cnt", cnt)
+            .tag("deleted", del)
+            .tag("del_nonexisted", del_nonexisted)
+            .tag("skipped", skip);
+
+    if (!iter->is_valid()) {
+        return -1;
+    }
+
+    return ret;
+}
+
+int GcsAccessor::delete_files(const std::vector<std::string>& paths) {
+    std::vector<int> delete_rets(paths.size());
+#ifdef USE_LIBCPP
+    std::transform(paths.begin(), paths.end(), delete_rets.begin(),
+#else
+    std::transform(std::execution::par, paths.begin(), paths.end(), 
delete_rets.begin(),
+#endif
+                   [this](const std::string& path) { return delete_file(path); 
});
+
+    int ret = 0;
+    for (int delete_ret : delete_rets) {
+        if (delete_ret != 0) {
+            ret = delete_ret;
+            break;
+        }
+    }
+    return ret;
+}
+
 } // namespace doris::cloud
diff --git a/cloud/src/recycler/s3_accessor.h b/cloud/src/recycler/s3_accessor.h
index 5a0b48628a0..adee55e2195 100644
--- a/cloud/src/recycler/s3_accessor.h
+++ b/cloud/src/recycler/s3_accessor.h
@@ -160,5 +160,16 @@ protected:
     std::string _ca_cert_file_path;
 };
 
+class GcsAccessor final : public S3Accessor {
+public:
+    explicit GcsAccessor(S3Conf conf) : S3Accessor(std::move(conf)) {}
+    ~GcsAccessor() override = default;
+
+    int delete_files(const std::vector<std::string>& paths) override;
+
+private:
+    int delete_prefix_impl(const std::string& path_prefix, int64_t 
expiration_time = 0) override;
+};
+
 } // namespace cloud
 } // namespace doris


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to