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

liaoxin01 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 68938c5ca31 [fix](s3) Add S3 rate limit observability (#64038)
68938c5ca31 is described below

commit 68938c5ca31646686fffcb1e2dd153567539db47
Author: Gavin Chou <[email protected]>
AuthorDate: Wed Jul 15 21:51:38 2026 +0800

    [fix](s3) Add S3 rate limit observability (#64038)
    
    ### What problem does this PR solve?
    
    Add observability for S3 local rate limiting and S3 429 responses so
    throttling-related performance issues can be diagnosed and alerted more
    directly.
    
    ### What is changed?
    
    - Add explicit S3 local rate limiter bvars for sleep duration, sleep
    count, and rejected count.
    - Rename the old ambiguous S3 local rate limiter bvars so their meaning
    is clear.
    - Log the first and every N local S3 rate limiter throttled/rejected
    requests without reading bvar values for decisions.
    - Add S3 429 retry/failure bvars for S3/Azure object clients.
    - Update S3 rate limiter unit coverage for sleep and rejected metrics.
    
    ### New or renamed bvar metrics
    
    Renamed local S3 GET limiter metrics:
    
    - `get_rate_limit_ns` -> `s3_get_rate_limit_sleep_ns`
    - `get_rate_limit_exceed_req_num` -> `s3_get_rate_limit_sleep_count`
    
    Renamed local S3 PUT limiter metrics:
    
    - `put_rate_limit_ns` -> `s3_put_rate_limit_sleep_ns`
    - `put_rate_limit_exceed_req_num` -> `s3_put_rate_limit_sleep_count`
    
    New local S3 limiter rejection metrics:
    
    - `s3_get_rate_limit_rejected_count`
    - `s3_put_rate_limit_rejected_count`
    
    New S3 429 metrics:
    
    - `s3_request_retry_too_many_requests_count`
    - `s3_request_failed_too_many_requests_count`
    
    Metric examples:
    
    ```text
    s3_get_rate_limit_sleep_ns : 123456789
    s3_get_rate_limit_sleep_count : 42
    s3_get_rate_limit_rejected_count : 3
    s3_put_rate_limit_sleep_ns : 987654321
    s3_put_rate_limit_sleep_count : 27
    s3_put_rate_limit_rejected_count : 1
    s3_request_retry_too_many_requests_count : 8
    s3_request_failed_too_many_requests_count : 2
    ```
    
    Example local limiter logs:
    
    ```text
    S3 GET request is throttled by local rate limiter, sleep_ms=12, 
sleep_count=1000, token_per_second=1000, bucket_tokens=2000, token_limit=5000
    S3 PUT request is rejected by local rate limiter, rejected_count=1, 
token_per_second=1000, bucket_tokens=2000, token_limit=5000
    ```
    
    ### Tests
    
    - `sh run-cloud-ut.sh --run --filter=s3_rate_limiter_test:*`
    - `sh run-be-ut.sh --run --filter=S3FileWriterTest.*`
    
    Note: an earlier BE run used an incorrect lowercase filter
    (`s3_file_writer_test:*`), which matched no suite and started running
    the full BE suite; it was stopped and replaced by the correct
    `S3FileWriterTest.*` run above.
    
    ---------
    
    Co-authored-by: gavinchou <[email protected]>
---
 be/src/common/config.cpp                  |  3 +
 be/src/common/config.h                    |  1 +
 be/src/io/fs/azure_obj_storage_client.cpp |  5 +-
 be/src/io/fs/s3_obj_storage_client.cpp    | 20 ++++++-
 be/src/util/s3_util.cpp                   | 16 +++---
 be/src/util/s3_util.h                     |  1 +
 cloud/src/common/config.h                 |  3 +
 cloud/src/recycler/azure_obj_client.cpp   |  9 ++-
 cloud/src/recycler/s3_accessor.cpp        | 23 +++-----
 cloud/src/recycler/s3_obj_client.cpp      | 29 +++++++---
 cloud/test/s3_rate_limiter_test.cpp       | 81 ++++++++++++++++++++++++---
 common/cpp/obj_retry_strategy.cpp         | 17 ++++++
 common/cpp/obj_retry_strategy.h           |  2 +
 common/cpp/token_bucket_rate_limiter.cpp  | 91 +++++++++++++++++++++++++++++--
 common/cpp/token_bucket_rate_limiter.h    | 38 +++++++++----
 15 files changed, 280 insertions(+), 59 deletions(-)

diff --git a/be/src/common/config.cpp b/be/src/common/config.cpp
index ca20cb63990..b2d51ecbb6d 100644
--- a/be/src/common/config.cpp
+++ b/be/src/common/config.cpp
@@ -1539,6 +1539,9 @@ DEFINE_mInt64(s3_put_token_per_second, 
"1000000000000000000");
 DEFINE_Validator(s3_put_token_per_second, [](int64_t config) -> bool { return 
config > 0; });
 
 DEFINE_mInt64(s3_put_token_limit, "0");
+// Log active S3 rate limiter every N throttled/rejected requests, 0 means no 
log.
+DEFINE_mInt64(s3_rate_limiter_log_interval, "1000");
+DEFINE_Validator(s3_rate_limiter_log_interval, [](int64_t config) -> bool { 
return config >= 0; });
 
 DEFINE_String(trino_connector_plugin_dir, "${DORIS_HOME}/plugins/connectors");
 
diff --git a/be/src/common/config.h b/be/src/common/config.h
index 2a6b21ccf8c..25c7a8ef89d 100644
--- a/be/src/common/config.h
+++ b/be/src/common/config.h
@@ -1617,6 +1617,7 @@ DECLARE_mInt64(s3_get_token_limit);
 DECLARE_mInt64(s3_put_bucket_tokens);
 DECLARE_mInt64(s3_put_token_per_second);
 DECLARE_mInt64(s3_put_token_limit);
+DECLARE_mInt64(s3_rate_limiter_log_interval);
 // max s3 client retry times
 DECLARE_mInt32(max_s3_client_retry);
 // When meet s3 429 error, the "get" request will
diff --git a/be/src/io/fs/azure_obj_storage_client.cpp 
b/be/src/io/fs/azure_obj_storage_client.cpp
index 85b81c1deb2..4ac9117fad9 100644
--- a/be/src/io/fs/azure_obj_storage_client.cpp
+++ b/be/src/io/fs/azure_obj_storage_client.cpp
@@ -43,6 +43,7 @@
 #include "common/exception.h"
 #include "common/logging.h"
 #include "common/status.h"
+#include "cpp/obj_retry_strategy.h"
 #include "io/fs/obj_storage_client.h"
 #include "util/bvar_helper.h"
 #include "util/coding.h"
@@ -74,7 +75,7 @@ auto s3_rate_limit(doris::S3RateLimitType op, Func callback) 
-> decltype(callbac
     if (!doris::config::enable_s3_rate_limiter) {
         return callback();
     }
-    auto sleep_duration = 
doris::S3ClientFactory::instance().rate_limiter(op)->add(1);
+    auto sleep_duration = doris::apply_s3_rate_limit(op);
     if (sleep_duration < 0) {
         throw std::runtime_error("Azure exceeds request limit");
     }
@@ -124,6 +125,7 @@ ObjectStorageResponse do_azure_client_call(Func f, const 
ObjectStoragePathOption
     try {
         f();
     } catch (Azure::Core::RequestFailedException& e) {
+        doris::record_object_request_failed(static_cast<int>(e.StatusCode));
         auto tls_debug_suffix = build_azure_tls_debug_suffix(
                 fmt::format("{} {}", e.what(), e.Message), tls_debug_context);
         auto msg = fmt::format(
@@ -186,6 +188,7 @@ struct AzureBatchDeleter {
                     0 == strcmp(e.ErrorCode.c_str(), BlobNotFound)) {
                     continue;
                 }
+                
doris::record_object_request_failed(static_cast<int>(e.StatusCode));
                 auto msg = fmt::format(
                         "Azure request failed because {}, error msg {}, http 
code {}, path msg "
                         "{}{}",
diff --git a/be/src/io/fs/s3_obj_storage_client.cpp 
b/be/src/io/fs/s3_obj_storage_client.cpp
index 1b8bf7b473c..f9ed8e155ff 100644
--- a/be/src/io/fs/s3_obj_storage_client.cpp
+++ b/be/src/io/fs/s3_obj_storage_client.cpp
@@ -66,6 +66,7 @@
 
 #include "common/logging.h"
 #include "common/status.h"
+#include "cpp/obj_retry_strategy.h"
 #include "cpp/sync_point.h"
 #include "io/fs/err_utils.h"
 #include "io/fs/s3_common.h"
@@ -82,7 +83,7 @@ auto s3_rate_limit(doris::S3RateLimitType op, Func callback) 
-> decltype(callbac
     if (!doris::config::enable_s3_rate_limiter) {
         return callback();
     }
-    auto sleep_duration = 
doris::S3ClientFactory::instance().rate_limiter(op)->add(1);
+    auto sleep_duration = doris::apply_s3_rate_limit(op);
     if (sleep_duration < 0) {
         return T(s3_error_factory());
     }
@@ -98,6 +99,10 @@ template <typename Func>
 auto s3_put_rate_limit(Func callback) -> decltype(callback()) {
     return s3_rate_limit(doris::S3RateLimitType::PUT, std::move(callback));
 }
+
+void record_s3_request_failed(const Aws::S3::S3Error& error) {
+    
doris::record_object_request_failed(static_cast<int>(error.GetResponseCode()));
+}
 } // namespace
 
 namespace Aws::S3::Model {
@@ -140,6 +145,7 @@ ObjectStorageUploadResponse 
S3ObjStorageClient::create_multipart_upload(
             << ", request_id=" << request_id << ", bucket=" << opts.bucket << 
", key=" << opts.key;
 
     if (!outcome.IsSuccess()) {
+        record_s3_request_failed(outcome.GetError());
         auto st = s3fs_error(outcome.GetError(), fmt::format("failed to 
CreateMultipartUpload: {} ",
                                                              
opts.path.native()));
         LOG(WARNING) << st << " request_id=" << request_id;
@@ -177,6 +183,7 @@ ObjectStorageResponse S3ObjStorageClient::put_object(const 
ObjectStoragePathOpti
                                                  : 
outcome.GetError().GetRequestId();
 
     if (!outcome.IsSuccess()) {
+        record_s3_request_failed(outcome.GetError());
         auto st = s3fs_error(outcome.GetError(),
                              fmt::format("failed to put object: {}", 
opts.path.native()));
         LOG(WARNING) << st << ", request_id=" << request_id;
@@ -222,6 +229,7 @@ ObjectStorageUploadResponse 
S3ObjStorageClient::upload_part(const ObjectStorageP
 
     TEST_SYNC_POINT_CALLBACK("S3FileWriter::_upload_one_part", &outcome);
     if (!outcome.IsSuccess()) {
+        record_s3_request_failed(outcome.GetError());
         auto st = Status::IOError(
                 "failed to UploadPart bucket={}, key={}, part_num={}, 
upload_id={}, message={}, "
                 "exception_name={}, response_code={}, request_id={}",
@@ -275,6 +283,7 @@ ObjectStorageResponse 
S3ObjStorageClient::complete_multipart_upload(
                                                  : 
outcome.GetError().GetRequestId();
 
     if (!outcome.IsSuccess()) {
+        record_s3_request_failed(outcome.GetError());
         auto st = s3fs_error(outcome.GetError(),
                              fmt::format("failed to CompleteMultipartUpload: 
{}, upload_id={}",
                                          opts.path.native(), *opts.upload_id));
@@ -305,6 +314,7 @@ ObjectStorageHeadResponse 
S3ObjStorageClient::head_object(const ObjectStoragePat
     } else if (outcome.GetError().GetResponseCode() == 
Aws::Http::HttpResponseCode::NOT_FOUND) {
         return {.resp = 
{convert_to_obj_response(Status::Error<ErrorCode::NOT_FOUND, false>(""))}};
     } else {
+        record_s3_request_failed(outcome.GetError());
         return {.resp = {convert_to_obj_response(
                                  s3fs_error(outcome.GetError(),
                                             fmt::format("failed to check 
exists {}", opts.key))),
@@ -324,6 +334,7 @@ ObjectStorageResponse S3ObjStorageClient::get_object(const 
ObjectStoragePathOpti
     SCOPED_BVAR_LATENCY(s3_bvar::s3_get_latency);
     auto outcome = s3_get_rate_limit([&]() { return 
_client->GetObject(request); });
     if (!outcome.IsSuccess()) {
+        record_s3_request_failed(outcome.GetError());
         return {convert_to_obj_response(s3fs_error(
                         outcome.GetError(), fmt::format("failed to read from 
{}", opts.key))),
                 static_cast<int>(outcome.GetError().GetResponseCode()),
@@ -362,6 +373,7 @@ ObjectStorageResponse 
S3ObjStorageClient::list_objects(const ObjectStoragePathOp
                 return ObjectStorageResponse::OK();
             }
 
+            record_s3_request_failed(outcome.GetError());
             return {convert_to_obj_response(s3fs_error(
                             outcome.GetError(), fmt::format("failed to list 
{}", opts.prefix))),
                     static_cast<int>(outcome.GetError().GetResponseCode()),
@@ -406,6 +418,7 @@ ObjectStorageResponse 
S3ObjStorageClient::delete_objects(const ObjectStoragePath
     auto delete_outcome =
             s3_put_rate_limit([&]() { return 
_client->DeleteObjects(delete_request); });
     if (!delete_outcome.IsSuccess()) {
+        record_s3_request_failed(delete_outcome.GetError());
         return {convert_to_obj_response(
                         s3fs_error(delete_outcome.GetError(),
                                    fmt::format("failed to delete dir {}", 
opts.key))),
@@ -433,6 +446,7 @@ ObjectStorageResponse 
S3ObjStorageClient::delete_object(const ObjectStoragePathO
         outcome.GetError().GetResponseCode() == 
Aws::Http::HttpResponseCode::NOT_FOUND) {
         return ObjectStorageResponse::OK();
     }
+    record_s3_request_failed(outcome.GetError());
     return {convert_to_obj_response(s3fs_error(outcome.GetError(),
                                                fmt::format("failed to delete 
file {}", opts.key))),
             static_cast<int>(outcome.GetError().GetResponseCode()),
@@ -453,6 +467,7 @@ ObjectStorageResponse 
S3ObjStorageClient::delete_objects_recursively(
             outcome = s3_get_rate_limit([&]() { return 
_client->ListObjectsV2(request); });
         }
         if (!outcome.IsSuccess()) {
+            record_s3_request_failed(outcome.GetError());
             return {convert_to_obj_response(s3fs_error(
                             outcome.GetError(),
                             fmt::format("failed to list objects when delete 
dir {}", opts.prefix))),
@@ -473,6 +488,7 @@ ObjectStorageResponse 
S3ObjStorageClient::delete_objects_recursively(
             auto delete_outcome =
                     s3_put_rate_limit([&]() { return 
_client->DeleteObjects(delete_request); });
             if (!delete_outcome.IsSuccess()) {
+                record_s3_request_failed(delete_outcome.GetError());
                 return {convert_to_obj_response(
                                 s3fs_error(delete_outcome.GetError(),
                                            fmt::format("failed to delete dir 
{}", opts.key))),
@@ -502,4 +518,4 @@ std::string 
S3ObjStorageClient::generate_presigned_url(const ObjectStoragePathOp
                                          expiration_secs);
 }
 
-} // namespace doris::io
\ No newline at end of file
+} // namespace doris::io
diff --git a/be/src/util/s3_util.cpp b/be/src/util/s3_util.cpp
index 2021b80fda6..c6e86a7f4b2 100644
--- a/be/src/util/s3_util.cpp
+++ b/be/src/util/s3_util.cpp
@@ -158,11 +158,6 @@ constexpr char S3_EXTERNAL_ID[] = "AWS_EXTERNAL_ID";
 constexpr char S3_CREDENTIALS_PROVIDER_TYPE[] = 
"AWS_CREDENTIALS_PROVIDER_TYPE";
 } // namespace
 
-bvar::Adder<int64_t> get_rate_limit_ns("get_rate_limit_ns");
-bvar::Adder<int64_t> 
get_rate_limit_exceed_req_num("get_rate_limit_exceed_req_num");
-bvar::Adder<int64_t> put_rate_limit_ns("put_rate_limit_ns");
-bvar::Adder<int64_t> 
put_rate_limit_exceed_req_num("put_rate_limit_exceed_req_num");
-
 static std::atomic<int64_t> last_s3_get_token_bucket_tokens {0};
 static std::atomic<int64_t> last_s3_get_token_limit {0};
 static std::atomic<int64_t> last_s3_get_token_per_second {0};
@@ -230,6 +225,11 @@ int reset_s3_rate_limiter(S3RateLimitType type, size_t 
max_speed, size_t max_bur
     return S3ClientFactory::instance().rate_limiter(type)->reset(max_speed, 
max_burst, limit);
 }
 
+int64_t apply_s3_rate_limit(S3RateLimitType type) {
+    return doris::apply_s3_rate_limit(type, 
S3ClientFactory::instance().rate_limiter(type),
+                                      config::s3_rate_limiter_log_interval);
+}
+
 S3ClientFactory::S3ClientFactory() {
     _aws_options = Aws::SDKOptions {};
     auto logLevel = 
static_cast<Aws::Utils::Logging::LogLevel>(config::aws_log_level);
@@ -242,12 +242,10 @@ S3ClientFactory::S3ClientFactory() {
     _rate_limiters = {
             std::make_unique<S3RateLimiterHolder>(
                     config::s3_get_token_per_second, 
config::s3_get_bucket_tokens,
-                    config::s3_get_token_limit,
-                    metric_func_factory(get_rate_limit_ns, 
get_rate_limit_exceed_req_num)),
+                    config::s3_get_token_limit, 
s3_rate_limiter_metric_func(S3RateLimitType::GET)),
             std::make_unique<S3RateLimiterHolder>(
                     config::s3_put_token_per_second, 
config::s3_put_bucket_tokens,
-                    config::s3_put_token_limit,
-                    metric_func_factory(put_rate_limit_ns, 
put_rate_limit_exceed_req_num))};
+                    config::s3_put_token_limit, 
s3_rate_limiter_metric_func(S3RateLimitType::PUT))};
 
 #ifdef USE_AZURE
     auto azureLogLevel =
diff --git a/be/src/util/s3_util.h b/be/src/util/s3_util.h
index 5546db857c9..064f88accd8 100644
--- a/be/src/util/s3_util.h
+++ b/be/src/util/s3_util.h
@@ -64,6 +64,7 @@ extern bvar::LatencyRecorder s3_copy_object_latency;
 
 std::string hide_access_key(const std::string& ak);
 int reset_s3_rate_limiter(S3RateLimitType type, size_t max_speed, size_t 
max_burst, size_t limit);
+int64_t apply_s3_rate_limit(S3RateLimitType type);
 // Rebuild the S3 GET/PUT rate limiters if the related configs have changed.
 // Safe to call periodically; it is a no-op when nothing changed.
 void check_s3_rate_limiter_config_changed();
diff --git a/cloud/src/common/config.h b/cloud/src/common/config.h
index 926a73a2c4c..a4a2c17441b 100644
--- a/cloud/src/common/config.h
+++ b/cloud/src/common/config.h
@@ -282,6 +282,9 @@ CONF_mBool(enable_s3_rate_limiter, "false");
 // s3_rate_limit_inject_probility is the probability (0-100) of injecting a 
rate limit error.
 CONF_mBool(enable_s3_rate_limit_inject, "false");
 CONF_mInt32(s3_rate_limit_inject_probility, "30");
+// Log active S3 rate limiter every N throttled/rejected requests, 0 means no 
log.
+CONF_mInt64(s3_rate_limiter_log_interval, "1000");
+CONF_Validator(s3_rate_limiter_log_interval, [](int64_t config) -> bool { 
return config >= 0; });
 CONF_mInt64(s3_get_bucket_tokens, "1000000000000000000");
 CONF_Validator(s3_get_bucket_tokens, [](int64_t config) -> bool { return 
config > 0; });
 
diff --git a/cloud/src/recycler/azure_obj_client.cpp 
b/cloud/src/recycler/azure_obj_client.cpp
index 94901cde48b..5390c1c5f99 100644
--- a/cloud/src/recycler/azure_obj_client.cpp
+++ b/cloud/src/recycler/azure_obj_client.cpp
@@ -35,6 +35,7 @@
 #include "common/config.h"
 #include "common/logging.h"
 #include "common/stopwatch.h"
+#include "cpp/obj_retry_strategy.h"
 #include "cpp/sync_point.h"
 #include "cpp/token_bucket_rate_limiter.h"
 #include "recycler/s3_accessor.h"
@@ -56,7 +57,9 @@ auto s3_rate_limit(S3RateLimitType op, Func callback) -> 
decltype(callback()) {
     if (!config::enable_s3_rate_limiter) {
         return callback();
     }
-    auto sleep_duration = 
AccessorRateLimiter::instance().rate_limiter(op)->add(1);
+    auto sleep_duration =
+            doris::apply_s3_rate_limit(op, 
AccessorRateLimiter::instance().rate_limiter(op),
+                                       config::s3_rate_limiter_log_interval);
     if (sleep_duration < 0) {
         throw std::runtime_error("Azure exceeds request limit");
     }
@@ -81,6 +84,7 @@ ObjectStorageResponse do_azure_client_call(Func f, 
std::string_view url, std::st
     try {
         f();
     } catch (Azure::Core::RequestFailedException& e) {
+        doris::record_object_request_failed(static_cast<int>(e.StatusCode));
         auto msg = fmt::format(
                 "Azure request failed because {}, http_code: {}, request_id: 
{}, url: {}, "
                 "key: {}",
@@ -280,6 +284,7 @@ ObjectStorageResponse AzureObjClient::delete_objects(const 
std::string& bucket,
                     0 == strcmp(e.ErrorCode.c_str(), BlobNotFound)) {
                     continue;
                 }
+                
doris::record_object_request_failed(static_cast<int>(e.StatusCode));
                 auto msg = fmt::format(
                         "Azure request failed because {}, http code {}, 
request id {}, url {}",
                         e.Message, static_cast<int>(e.StatusCode), 
e.RequestId, client_->GetUrl());
@@ -333,4 +338,4 @@ ObjectStorageResponse 
AzureObjClient::abort_multipart_upload(ObjectStoragePathRe
     return delete_object(path);
 }
 
-} // namespace doris::cloud
\ No newline at end of file
+} // namespace doris::cloud
diff --git a/cloud/src/recycler/s3_accessor.cpp 
b/cloud/src/recycler/s3_accessor.cpp
index 8ff29694bd7..8fb85d54a2c 100644
--- a/cloud/src/recycler/s3_accessor.cpp
+++ b/cloud/src/recycler/s3_accessor.cpp
@@ -73,22 +73,15 @@ bvar::LatencyRecorder 
s3_get_bucket_version_latency("s3_get_bucket_version");
 bvar::LatencyRecorder s3_copy_object_latency("s3_copy_object");
 }; // namespace s3_bvar
 
-bvar::Adder<int64_t> get_rate_limit_ns("get_rate_limit_ns");
-bvar::Adder<int64_t> 
get_rate_limit_exceed_req_num("get_rate_limit_exceed_req_num");
-bvar::Adder<int64_t> put_rate_limit_ns("put_rate_limit_ns");
-bvar::Adder<int64_t> 
put_rate_limit_exceed_req_num("put_rate_limit_exceed_req_num");
-
 AccessorRateLimiter::AccessorRateLimiter()
-        : _rate_limiters(
-                  {std::make_unique<S3RateLimiterHolder>(
-                           config::s3_get_token_per_second, 
config::s3_get_bucket_tokens,
-                           config::s3_get_token_limit,
-                           metric_func_factory(get_rate_limit_ns, 
get_rate_limit_exceed_req_num)),
-                   std::make_unique<S3RateLimiterHolder>(
-                           config::s3_put_token_per_second, 
config::s3_put_bucket_tokens,
-                           config::s3_put_token_limit,
-                           metric_func_factory(put_rate_limit_ns,
-                                               
put_rate_limit_exceed_req_num))}) {}
+        : _rate_limiters({std::make_unique<S3RateLimiterHolder>(
+                                  config::s3_get_token_per_second, 
config::s3_get_bucket_tokens,
+                                  config::s3_get_token_limit,
+                                  
s3_rate_limiter_metric_func(S3RateLimitType::GET)),
+                          std::make_unique<S3RateLimiterHolder>(
+                                  config::s3_put_token_per_second, 
config::s3_put_bucket_tokens,
+                                  config::s3_put_token_limit,
+                                  
s3_rate_limiter_metric_func(S3RateLimitType::PUT))}) {}
 
 S3RateLimiterHolder* AccessorRateLimiter::rate_limiter(S3RateLimitType type) {
     CHECK(type == S3RateLimitType::GET || type == S3RateLimitType::PUT) << 
to_string(type);
diff --git a/cloud/src/recycler/s3_obj_client.cpp 
b/cloud/src/recycler/s3_obj_client.cpp
index c45a6aae168..3f299d0fef0 100644
--- a/cloud/src/recycler/s3_obj_client.cpp
+++ b/cloud/src/recycler/s3_obj_client.cpp
@@ -32,6 +32,7 @@
 #include "common/config.h"
 #include "common/logging.h"
 #include "common/stopwatch.h"
+#include "cpp/obj_retry_strategy.h"
 #include "cpp/sync_point.h"
 #include "cpp/token_bucket_rate_limiter.h"
 #include "recycler/s3_accessor.h"
@@ -43,6 +44,10 @@ namespace doris::cloud {
     return {Aws::S3::S3Errors::INTERNAL_FAILURE, "exceeds limit", "exceeds 
limit", false};
 }
 
+void record_s3_request_failed(const Aws::S3::S3Error& error) {
+    
doris::record_object_request_failed(static_cast<int>(error.GetResponseCode()));
+}
+
 template <typename Func>
 auto s3_rate_limit(S3RateLimitType op, Func callback) -> decltype(callback()) {
     using T = decltype(callback());
@@ -55,7 +60,9 @@ auto s3_rate_limit(S3RateLimitType op, Func callback) -> 
decltype(callback()) {
     if (!config::enable_s3_rate_limiter) {
         return callback();
     }
-    auto sleep_duration = 
AccessorRateLimiter::instance().rate_limiter(op)->add(1);
+    auto sleep_duration =
+            doris::apply_s3_rate_limit(op, 
AccessorRateLimiter::instance().rate_limiter(op),
+                                       config::s3_rate_limiter_log_interval);
     if (sleep_duration < 0) {
         return T(s3_error_factory());
     }
@@ -118,6 +125,7 @@ public:
                 return false;
             }
 
+            record_s3_request_failed(outcome.GetError());
             LOG_WARNING("failed to list objects")
                     .tag("endpoint", endpoint_)
                     .tag("bucket", req_.GetBucket())
@@ -210,6 +218,7 @@ ObjectStorageResponse 
S3ObjClient::put_object(ObjectStoragePathRef path, std::st
         return s3_client_->PutObject(request);
     });
     if (!outcome.IsSuccess()) {
+        record_s3_request_failed(outcome.GetError());
         LOG_WARNING("failed to put object")
                 .tag("endpoint", endpoint_)
                 .tag("bucket", path.bucket)
@@ -237,6 +246,7 @@ ObjectStorageResponse 
S3ObjClient::head_object(ObjectStoragePathRef path, Object
     } else if (outcome.GetError().GetResponseCode() == 
Aws::Http::HttpResponseCode::NOT_FOUND) {
         return 1;
     } else {
+        record_s3_request_failed(outcome.GetError());
         LOG_WARNING("failed to head object")
                 .tag("endpoint", endpoint_)
                 .tag("bucket", path.bucket)
@@ -276,6 +286,7 @@ ObjectStorageResponse S3ObjClient::delete_objects(const 
std::string& bucket,
             return s3_client_->DeleteObjects(delete_request);
         });
         if (!delete_outcome.IsSuccess()) {
+            record_s3_request_failed(delete_outcome.GetError());
             LOG_WARNING("failed to delete objects")
                     .tag("endpoint", endpoint_)
                     .tag("bucket", bucket)
@@ -326,6 +337,10 @@ ObjectStorageResponse 
S3ObjClient::delete_object(ObjectStoragePathRef path) {
     });
     TEST_SYNC_POINT_CALLBACK("S3ObjClient::delete_object", &outcome);
     if (!outcome.IsSuccess()) {
+        if (outcome.GetError().GetResponseCode() == 
Aws::Http::HttpResponseCode::NOT_FOUND) {
+            return {ObjectStorageResponse::NOT_FOUND, 
outcome.GetError().GetMessage()};
+        }
+        record_s3_request_failed(outcome.GetError());
         LOG_WARNING("failed to delete object")
                 .tag("endpoint", endpoint_)
                 .tag("bucket", path.bucket)
@@ -334,9 +349,6 @@ ObjectStorageResponse 
S3ObjClient::delete_object(ObjectStoragePathRef path) {
                 .tag("error", outcome.GetError().GetMessage())
                 .tag("exception", outcome.GetError().GetExceptionName())
                 .tag("request_id", outcome.GetError().GetRequestId());
-        if (outcome.GetError().GetResponseCode() == 
Aws::Http::HttpResponseCode::NOT_FOUND) {
-            return {ObjectStorageResponse::NOT_FOUND, 
outcome.GetError().GetMessage()};
-        }
         return {ObjectStorageResponse::UNDEFINED, 
outcome.GetError().GetMessage()};
     }
     return {ObjectStorageResponse::OK};
@@ -365,6 +377,7 @@ ObjectStorageResponse S3ObjClient::get_life_cycle(const 
std::string& bucket,
             }
         }
     } else {
+        record_s3_request_failed(outcome.GetError());
         LOG_WARNING("Err for check interval: failed to get bucket lifecycle")
                 .tag("endpoint", endpoint_)
                 .tag("bucket", bucket)
@@ -397,6 +410,7 @@ ObjectStorageResponse S3ObjClient::check_versioning(const 
std::string& bucket) {
             return -1;
         }
     } else {
+        record_s3_request_failed(outcome.GetError());
         LOG_WARNING("Err for check interval: failed to get status of bucket 
versioning")
                 .tag("endpoint", endpoint_)
                 .tag("bucket", bucket)
@@ -414,6 +428,10 @@ ObjectStorageResponse 
S3ObjClient::abort_multipart_upload(ObjectStoragePathRef p
     request.WithBucket(path.bucket).WithKey(path.key).WithUploadId(upload_id);
     auto outcome = s3_put_rate_limit([&]() { return 
s3_client_->AbortMultipartUpload(request); });
     if (!outcome.IsSuccess()) {
+        if (outcome.GetError().GetResponseCode() == 
Aws::Http::HttpResponseCode::NOT_FOUND) {
+            return {ObjectStorageResponse::OK};
+        }
+        record_s3_request_failed(outcome.GetError());
         LOG_WARNING("failed to abort multipart upload")
                 .tag("endpoint", endpoint_)
                 .tag("bucket", path.bucket)
@@ -423,9 +441,6 @@ ObjectStorageResponse 
S3ObjClient::abort_multipart_upload(ObjectStoragePathRef p
                 .tag("error", outcome.GetError().GetMessage())
                 .tag("exception", outcome.GetError().GetExceptionName())
                 .tag("request_id", outcome.GetError().GetRequestId());
-        if (outcome.GetError().GetResponseCode() == 
Aws::Http::HttpResponseCode::NOT_FOUND) {
-            return {ObjectStorageResponse::OK};
-        }
         return {ObjectStorageResponse::UNDEFINED, 
outcome.GetError().GetMessage()};
     }
     return {ObjectStorageResponse::OK};
diff --git a/cloud/test/s3_rate_limiter_test.cpp 
b/cloud/test/s3_rate_limiter_test.cpp
index b1fd5a8115f..05fbb2ce35e 100644
--- a/cloud/test/s3_rate_limiter_test.cpp
+++ b/cloud/test/s3_rate_limiter_test.cpp
@@ -29,6 +29,10 @@
 
 using namespace doris::cloud;
 
+namespace doris {
+extern bvar::Adder<int64_t> s3_put_rate_limit_rejected_count;
+} // namespace doris
+
 int main(int argc, char** argv) {
     auto conf_file = "doris_cloud.conf";
     if (!doris::cloud::config::init(conf_file, true)) {
@@ -107,18 +111,79 @@ TEST(S3RateLimiterTest, ExceedLimit) {
 }
 
 TEST(S3RateLimiterHolderTest, BvarMetric) {
-    bvar::Adder<int64_t> rate_limit_ns("rate_limit_ns");
-    bvar::Adder<int64_t> 
rate_limit_exceed_req_num("rate_limit_exceed_req_num");
+    bvar::Adder<int64_t> rate_limit_sleep_ns("rate_limit_sleep_ns");
+    bvar::Adder<int64_t> rate_limit_sleep_count("rate_limit_sleep_count");
+    bvar::Adder<int64_t> 
rate_limit_rejected_count("rate_limit_rejected_count");
 
     auto rate_limiter_holder = doris::S3RateLimiterHolder(
-            125, 250, 500, doris::metric_func_factory(rate_limit_ns, 
rate_limit_exceed_req_num));
+            125, 250, 251,
+            doris::metric_func_factory(rate_limit_sleep_ns, 
rate_limit_sleep_count,
+                                       &rate_limit_rejected_count));
     int64_t sleep_time = rate_limiter_holder.add(250);
     EXPECT_EQ(sleep_time, 0);
-    EXPECT_EQ(rate_limit_ns.get_value(), 0);
-    EXPECT_EQ(rate_limit_exceed_req_num.get_value(), 0);
+    EXPECT_EQ(rate_limit_sleep_ns.get_value(), 0);
+    EXPECT_EQ(rate_limit_sleep_count.get_value(), 0);
+    EXPECT_EQ(rate_limit_rejected_count.get_value(), 0);
 
     sleep_time = rate_limiter_holder.add(1);
-    EXPECT_GT(rate_limit_ns.get_value(), 0);
-    EXPECT_EQ(rate_limit_exceed_req_num.get_value(), 1);
     EXPECT_GT(sleep_time, 0);
-}
\ No newline at end of file
+    EXPECT_GT(rate_limit_sleep_ns.get_value(), 0);
+    EXPECT_EQ(rate_limit_sleep_count.get_value(), 1);
+    EXPECT_EQ(rate_limit_rejected_count.get_value(), 0);
+
+    sleep_time = rate_limiter_holder.add(1);
+    EXPECT_EQ(sleep_time, -1);
+    EXPECT_EQ(rate_limit_rejected_count.get_value(), 1);
+}
+
+TEST(S3RateLimiterHolderTest, ApplyS3RateLimitRecordsRejectedMetric) {
+    auto rate_limiter_holder = doris::S3RateLimiterHolder(
+            125, 250, 1, 
doris::s3_rate_limiter_metric_func(doris::S3RateLimitType::PUT));
+    auto rejected_count = doris::s3_put_rate_limit_rejected_count.get_value();
+
+    int64_t sleep_time =
+            doris::apply_s3_rate_limit(doris::S3RateLimitType::PUT, 
&rate_limiter_holder, 1);
+    EXPECT_EQ(sleep_time, 0);
+    EXPECT_EQ(doris::s3_put_rate_limit_rejected_count.get_value(), 
rejected_count);
+
+    sleep_time = doris::apply_s3_rate_limit(doris::S3RateLimitType::PUT, 
&rate_limiter_holder, 1);
+    EXPECT_EQ(sleep_time, -1);
+    EXPECT_EQ(doris::s3_put_rate_limit_rejected_count.get_value(), 
rejected_count + 1);
+}
+
+TEST(S3RateLimiterHolderTest, ConcurrentResetReturnsConsistentConfig) {
+    constexpr size_t config_a_speed = 101;
+    constexpr size_t config_a_burst = 102;
+    constexpr size_t config_a_limit = 103;
+    constexpr size_t config_b_speed = 201;
+    constexpr size_t config_b_burst = 202;
+    constexpr size_t config_b_limit = 203;
+
+    doris::S3RateLimiterHolder rate_limiter_holder(config_a_speed, 
config_a_burst, config_a_limit,
+                                                   [](int64_t) {});
+    std::atomic<bool> start {false};
+    std::thread reset_thread([&]() {
+        while (!start.load(std::memory_order_acquire)) {
+            std::this_thread::yield();
+        }
+        for (size_t i = 0; i < 10000; ++i) {
+            if (i % 2 == 0) {
+                rate_limiter_holder.reset(config_b_speed, config_b_burst, 
config_b_limit);
+            } else {
+                rate_limiter_holder.reset(config_a_speed, config_a_burst, 
config_a_limit);
+            }
+        }
+    });
+
+    start.store(true, std::memory_order_release);
+    for (size_t i = 0; i < 10000; ++i) {
+        auto result = rate_limiter_holder.add_with_config(0);
+        bool is_config_a = result.max_speed == config_a_speed &&
+                           result.max_burst == config_a_burst && result.limit 
== config_a_limit;
+        bool is_config_b = result.max_speed == config_b_speed &&
+                           result.max_burst == config_b_burst && result.limit 
== config_b_limit;
+        EXPECT_TRUE(is_config_a || is_config_b);
+    }
+
+    reset_thread.join();
+}
diff --git a/common/cpp/obj_retry_strategy.cpp 
b/common/cpp/obj_retry_strategy.cpp
index 58ca47f6a68..83992255364 100644
--- a/common/cpp/obj_retry_strategy.cpp
+++ b/common/cpp/obj_retry_strategy.cpp
@@ -24,6 +24,16 @@
 namespace doris {
 
 bvar::Adder<int64_t> object_request_retry_count("object_request_retry_count");
+bvar::Adder<int64_t> s3_request_retry_too_many_requests_count(
+        "s3_request_retry_too_many_requests_count");
+bvar::Adder<int64_t> s3_request_failed_too_many_requests_count(
+        "s3_request_failed_too_many_requests_count");
+
+void record_object_request_failed(int http_code) {
+    if (http_code == 
static_cast<int>(Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS)) {
+        s3_request_failed_too_many_requests_count << 1;
+    }
+}
 
 S3CustomRetryStrategy::S3CustomRetryStrategy(int maxRetries, bool 
retry_slow_down)
         : DefaultRetryStrategy(maxRetries), _retry_slow_down(retry_slow_down) 
{}
@@ -43,6 +53,9 @@ bool S3CustomRetryStrategy::ShouldRetry(const 
Aws::Client::AWSError<Aws::Client:
 
     if (Aws::Http::IsRetryableHttpResponseCode(error.GetResponseCode()) || 
error.ShouldRetry()) {
         object_request_retry_count << 1;
+        if (error.GetResponseCode() == 
Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS) {
+            s3_request_retry_too_many_requests_count << 1;
+        }
         LOG(INFO) << "retry due to error: " << error << ", attempt: " << 
attemptedRetries + 1 << "/"
                   << m_maxRetries;
         return true;
@@ -65,6 +78,10 @@ std::unique_ptr<Azure::Core::Http::RawResponse> 
AzureRetryRecordPolicy::Send(
         static_cast<int>(response->GetStatusCode()) < 200) {
         if (retry_count > 0) {
             object_request_retry_count << 1;
+            if (static_cast<int>(response->GetStatusCode()) ==
+                
static_cast<int>(Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS)) {
+                s3_request_retry_too_many_requests_count << 1;
+            }
         }
 
         // If the response is not successful, we log the retry attempt and 
status code.
diff --git a/common/cpp/obj_retry_strategy.h b/common/cpp/obj_retry_strategy.h
index aaf35f6b100..2eafe114564 100644
--- a/common/cpp/obj_retry_strategy.h
+++ b/common/cpp/obj_retry_strategy.h
@@ -25,6 +25,8 @@
 #endif
 
 namespace doris {
+void record_object_request_failed(int http_code);
+
 class S3CustomRetryStrategy final : public Aws::Client::DefaultRetryStrategy {
 public:
     S3CustomRetryStrategy(int maxRetries, bool retry_slow_down = true);
diff --git a/common/cpp/token_bucket_rate_limiter.cpp 
b/common/cpp/token_bucket_rate_limiter.cpp
index 0e30f870de3..128501ae7cf 100644
--- a/common/cpp/token_bucket_rate_limiter.cpp
+++ b/common/cpp/token_bucket_rate_limiter.cpp
@@ -20,6 +20,7 @@
 #include <bthread/bthread.h>
 #include <glog/logging.h> // IWYU pragma: export
 
+#include <atomic>
 #include <chrono>
 #include <mutex>
 #include <thread>
@@ -32,6 +33,18 @@ namespace doris {
 // Just 10^6.
 static constexpr auto NS = 1000000000UL;
 
+bvar::Adder<int64_t> s3_get_rate_limit_sleep_ns("s3_get_rate_limit_sleep_ns");
+bvar::Adder<int64_t> 
s3_get_rate_limit_sleep_count("s3_get_rate_limit_sleep_count");
+bvar::Adder<int64_t> 
s3_get_rate_limit_rejected_count("s3_get_rate_limit_rejected_count");
+bvar::Adder<int64_t> s3_put_rate_limit_sleep_ns("s3_put_rate_limit_sleep_ns");
+bvar::Adder<int64_t> 
s3_put_rate_limit_sleep_count("s3_put_rate_limit_sleep_count");
+bvar::Adder<int64_t> 
s3_put_rate_limit_rejected_count("s3_put_rate_limit_rejected_count");
+
+static std::atomic<int64_t> s3_get_rate_limit_sleep_log_count {0};
+static std::atomic<int64_t> s3_get_rate_limit_rejected_log_count {0};
+static std::atomic<int64_t> s3_put_rate_limit_sleep_log_count {0};
+static std::atomic<int64_t> s3_put_rate_limit_rejected_log_count {0};
+
 class TokenBucketRateLimiter::SimpleSpinLock {
 public:
     SimpleSpinLock() = default;
@@ -118,13 +131,20 @@ 
TokenBucketRateLimiterHolder::TokenBucketRateLimiterHolder(size_t max_speed, siz
           metric_func(std::move(metric_func)) {}
 
 int64_t TokenBucketRateLimiterHolder::add(size_t amount) {
-    int64_t sleep;
+    return add_with_config(amount).sleep_duration;
+}
+
+TokenBucketRateLimiterResult 
TokenBucketRateLimiterHolder::add_with_config(size_t amount) {
+    TokenBucketRateLimiterResult result;
     {
         std::shared_lock read {rate_limiter_rw_lock};
-        sleep = rate_limiter->add(amount);
+        result = {.sleep_duration = rate_limiter->add(amount),
+                  .max_speed = rate_limiter->get_max_speed(),
+                  .max_burst = rate_limiter->get_max_burst(),
+                  .limit = rate_limiter->get_limit()};
     }
-    metric_func(sleep);
-    return sleep;
+    metric_func(result.sleep_duration);
+    return result;
 }
 
 int TokenBucketRateLimiterHolder::reset(size_t max_speed, size_t max_burst, 
size_t limit) {
@@ -135,6 +155,21 @@ int TokenBucketRateLimiterHolder::reset(size_t max_speed, 
size_t max_burst, size
     return 0;
 }
 
+size_t TokenBucketRateLimiterHolder::get_max_speed() const {
+    std::shared_lock read {rate_limiter_rw_lock};
+    return rate_limiter->get_max_speed();
+}
+
+size_t TokenBucketRateLimiterHolder::get_max_burst() const {
+    std::shared_lock read {rate_limiter_rw_lock};
+    return rate_limiter->get_max_burst();
+}
+
+size_t TokenBucketRateLimiterHolder::get_limit() const {
+    std::shared_lock read {rate_limiter_rw_lock};
+    return rate_limiter->get_limit();
+}
+
 std::string to_string(S3RateLimitType type) {
     switch (type) {
     case S3RateLimitType::GET:
@@ -154,4 +189,52 @@ S3RateLimitType 
string_to_s3_rate_limit_type(std::string_view value) {
     }
     return S3RateLimitType::UNKNOWN;
 }
+
+std::function<void(int64_t)> s3_rate_limiter_metric_func(S3RateLimitType type) 
{
+    switch (type) {
+    case S3RateLimitType::GET:
+        return metric_func_factory(s3_get_rate_limit_sleep_ns, 
s3_get_rate_limit_sleep_count,
+                                   &s3_get_rate_limit_rejected_count);
+    case S3RateLimitType::PUT:
+        return metric_func_factory(s3_put_rate_limit_sleep_ns, 
s3_put_rate_limit_sleep_count,
+                                   &s3_put_rate_limit_rejected_count);
+    default:
+        return [](int64_t) {};
+    }
+}
+
+int64_t apply_s3_rate_limit(S3RateLimitType type, S3RateLimiterHolder* 
rate_limiter,
+                            int64_t log_interval) {
+    auto result = rate_limiter->add_with_config(1);
+    auto sleep_duration = result.sleep_duration;
+    if (log_interval <= 0 || sleep_duration == 0) {
+        return sleep_duration;
+    }
+
+    auto is_get = type == S3RateLimitType::GET;
+    auto* sleep_log_count =
+            is_get ? &s3_get_rate_limit_sleep_log_count : 
&s3_put_rate_limit_sleep_log_count;
+    auto* rejected_log_count =
+            is_get ? &s3_get_rate_limit_rejected_log_count : 
&s3_put_rate_limit_rejected_log_count;
+
+    if (sleep_duration > 0) {
+        int64_t count = sleep_log_count->fetch_add(1, 
std::memory_order_relaxed) + 1;
+        if (count == 1 || count % log_interval == 0) {
+            LOG(INFO) << "S3 " << to_string(type) << " request is throttled by 
local rate limiter"
+                      << ", sleep_ms=" << sleep_duration / 1000000 << ", 
sleep_count=" << count
+                      << ", token_per_second=" << result.max_speed
+                      << ", bucket_tokens=" << result.max_burst << ", 
token_limit=" << result.limit;
+        }
+    } else {
+        int64_t count = rejected_log_count->fetch_add(1, 
std::memory_order_relaxed) + 1;
+        if (count == 1 || count % log_interval == 0) {
+            LOG(WARNING) << "S3 " << to_string(type) << " request is rejected 
by local rate limiter"
+                         << ", rejected_count=" << count
+                         << ", token_per_second=" << result.max_speed
+                         << ", bucket_tokens=" << result.max_burst
+                         << ", token_limit=" << result.limit;
+        }
+    }
+    return sleep_duration;
+}
 } // namespace doris
diff --git a/common/cpp/token_bucket_rate_limiter.h 
b/common/cpp/token_bucket_rate_limiter.h
index dbeca3b897d..57f9205527a 100644
--- a/common/cpp/token_bucket_rate_limiter.h
+++ b/common/cpp/token_bucket_rate_limiter.h
@@ -21,6 +21,8 @@
 #include <functional>
 #include <memory>
 #include <shared_mutex>
+#include <string>
+#include <string_view>
 
 namespace doris {
 enum class S3RateLimitType : int {
@@ -32,11 +34,15 @@ enum class S3RateLimitType : int {
 extern std::string to_string(S3RateLimitType type);
 extern S3RateLimitType string_to_s3_rate_limit_type(std::string_view value);
 
-inline auto metric_func_factory(bvar::Adder<int64_t>& ns_bvar, 
bvar::Adder<int64_t>& req_num_bvar) {
-    return [&](int64_t ns) {
+inline auto metric_func_factory(bvar::Adder<int64_t>& sleep_ns_bvar,
+                                bvar::Adder<int64_t>& sleep_count_bvar,
+                                bvar::Adder<int64_t>* rejected_count_bvar = 
nullptr) {
+    return [&, rejected_count_bvar](int64_t ns) {
         if (ns > 0) {
-            ns_bvar << ns;
-            req_num_bvar << 1;
+            sleep_ns_bvar << ns;
+            sleep_count_bvar << 1;
+        } else if (ns < 0 && rejected_count_bvar != nullptr) {
+            *rejected_count_bvar << 1;
         }
     };
 }
@@ -71,6 +77,13 @@ private:
     long _prev_ns_count {0}; // Previous `add` call time (in nanoseconds).
 };
 
+struct TokenBucketRateLimiterResult {
+    int64_t sleep_duration;
+    size_t max_speed;
+    size_t max_burst;
+    size_t limit;
+};
+
 class TokenBucketRateLimiterHolder {
 public:
     TokenBucketRateLimiterHolder(size_t max_speed, size_t max_burst, size_t 
limit,
@@ -78,17 +91,16 @@ public:
     ~TokenBucketRateLimiterHolder();
 
     int64_t add(size_t amount);
+    TokenBucketRateLimiterResult add_with_config(size_t amount);
 
     int reset(size_t max_speed, size_t max_burst, size_t limit);
 
-    size_t get_max_speed() const { return rate_limiter->get_max_speed(); }
-
-    size_t get_max_burst() const { return rate_limiter->get_max_burst(); }
-
-    size_t get_limit() const { return rate_limiter->get_limit(); }
+    size_t get_max_speed() const;
+    size_t get_max_burst() const;
+    size_t get_limit() const;
 
 private:
-    std::shared_mutex rate_limiter_rw_lock;
+    mutable std::shared_mutex rate_limiter_rw_lock;
     std::unique_ptr<TokenBucketRateLimiter> rate_limiter;
     // Record the correspoding sleeping time(unit is ms)
     std::function<void(int64_t)> metric_func;
@@ -96,4 +108,8 @@ private:
 
 using S3RateLimiter = TokenBucketRateLimiter;
 using S3RateLimiterHolder = TokenBucketRateLimiterHolder;
-} // namespace doris
\ No newline at end of file
+
+std::function<void(int64_t)> s3_rate_limiter_metric_func(S3RateLimitType type);
+int64_t apply_s3_rate_limit(S3RateLimitType type, S3RateLimiterHolder* 
rate_limiter,
+                            int64_t log_interval);
+} // namespace doris


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


Reply via email to