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

sollhui 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 ed93d882847 [perf](load) Optimize write performance with improved 
backpressure control (#66847)
ed93d882847 is described below

commit ed93d882847ec18c3c3a844c1cb7d442b69bdd4c
Author: hui lai <[email protected]>
AuthorDate: Thu Aug 20 14:02:22 2026 +0800

    [perf](load) Optimize write performance with improved backpressure control 
(#66847)
    
    ### What problem does this PR solve?
    
    The previous backpressure control did not accurately represent the real
    write pipeline in two cases:
    
    1. A row-binlog `GROUP` request flushes both data and row-binlog
    memtables. These two physical tasks are counted independently by
    `flush_running_count()`, so comparing the physical count directly with
    `memtable_flush_running_count_limit` halves the intended logical flush
    concurrency.
    2. In storage-compute separation with S3, a memtable flush task can
    finish after submitting asynchronous uploads. Therefore, the
    flush-running count does not represent downstream S3 pressure. HDFS
    writes are synchronous inside the flush task and should continue using
    the flush-running count.
    
    This PR improves the backpressure control as follows:
    
    - Storage-compute integrated:
    - Use `memtable_flush_running_count_limit * 2` as the effective physical
    task limit for row-binlog `GROUP` requests.
      - Keep the existing limit unchanged for normal requests.
    - Storage-compute separated:
    - Detect the underlying filesystem for both normal and group rowset
    builders.
    - Use the S3 upload queue as the backpressure signal for S3-backed
    loads.
    - Retain flush-running-count backpressure for HDFS-backed loads,
    including the corrected effective limit for row-binlog `GROUP` requests.
    - Fall back to flush-running-count backpressure if the S3 upload thread
    pool is unavailable.
    - Check the S3 queue directly without going through the adaptive
    controller state lock.
    
    ### Performance test
    
    The baseline used `memtable_flush_running_count_limit = 4`. The new
    results are stable Prometheus throughput values from the same three-BE
    test cluster.
    
    This benchmark was run in storage-compute-separated mode with S3 as the
    remote storage, using a workload where memtable flush itself is not the
    bottleneck. The improvement comes from avoiding premature backpressure
    and allowing write-side processing to overlap with downstream upload
    work. The reported gains should not be generalized to flush-bound
    workloads: when flush is already saturated, relaxing backpressure may
    mainly increase queued memtables and memory consumption without
    producing the same throughput improvement.
    
    | Scenario | Baseline throughput | New throughput | Improvement |
    Baseline BE memory | New BE memory |
    |---|---:|---:|---:|---:|---:|
    | Row binlog off | ~450 MiB/s | ~899 MiB/s | ~100% | ~16 GiB steady, ~19
    GiB peak per BE | ~36 GiB steady, ~38.6 GiB peak per BE |
    | Row binlog on | ~360 MiB/s | ~844 MiB/s | ~134% | ~19 GiB steady/peak
    per BE | ~33 GiB steady, ~36.3 GiB peak per BE |
    
    Stable measurement windows:
    
    - Row binlog on: 17:02:38–17:08:58 CST
    - Row binlog off: 17:20:48–17:26:28 CST
    
    The higher concurrency trades memory for throughput. The observed
    steady-state cost was approximately +20 GiB per BE with row binlog off
    and +14 GiB per BE with row binlog on, or approximately +60 GiB and +42
    GiB respectively across the three-BE cluster. `BE Mem` represents total
    process allocated memory, not only memtable memory.
    
    HDFS and storage-compute integrated performance were not measured in
    this test.
---
 be/src/cloud/cloud_delta_writer.cpp       | 18 ++++++++++++++++--
 be/src/cloud/cloud_rowset_builder.cpp     |  8 ++++++++
 be/src/cloud/cloud_rowset_builder.h       |  4 ++++
 be/src/load/delta_writer/delta_writer.cpp |  6 ++++--
 4 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/be/src/cloud/cloud_delta_writer.cpp 
b/be/src/cloud/cloud_delta_writer.cpp
index 9e7641aca1f..19eaef0f00a 100644
--- a/be/src/cloud/cloud_delta_writer.cpp
+++ b/be/src/cloud/cloud_delta_writer.cpp
@@ -25,6 +25,8 @@
 #include "load/memtable/memtable_memory_limiter.h"
 #include "runtime/exec_env.h"
 #include "runtime/thread_context.h"
+#include "storage/adaptive_thread_pool_controller.h"
+#include "util/threadpool.h"
 
 namespace doris {
 
@@ -99,8 +101,20 @@ Status CloudDeltaWriter::write(const Block* block, const 
TabletAddRowsPayload& r
     CHECK(_is_init || _is_cancelled);
     {
         SCOPED_TIMER(_wait_flush_limit_timer);
-        while (_memtable_writer->flush_running_count() >=
-               config::memtable_flush_running_count_limit) {
+        auto* s3_file_upload_pool = rowset_builder()->is_s3_storage()
+                                            ? 
ExecEnv::GetInstance()->s3_file_upload_thread_pool()
+                                            : nullptr;
+        const auto need_backpressure = [this, s3_file_upload_pool] {
+            if (s3_file_upload_pool != nullptr) {
+                return s3_file_upload_pool->get_queue_size() >
+                       AdaptiveThreadPoolController::kS3QueueBusyThreshold;
+            }
+            const auto effective_flush_running_count_limit =
+                    config::memtable_flush_running_count_limit *
+                    (_req.write_req_type == WriteRequestType::GROUP ? 2 : 1);
+            return _memtable_writer->flush_running_count() >= 
effective_flush_running_count_limit;
+        };
+        while (need_backpressure()) {
             std::this_thread::sleep_for(std::chrono::milliseconds(10));
         }
     }
diff --git a/be/src/cloud/cloud_rowset_builder.cpp 
b/be/src/cloud/cloud_rowset_builder.cpp
index d36048a6646..0ab26eef6a5 100644
--- a/be/src/cloud/cloud_rowset_builder.cpp
+++ b/be/src/cloud/cloud_rowset_builder.cpp
@@ -23,6 +23,7 @@
 #include "cloud/cloud_storage_engine.h"
 #include "cloud/cloud_tablet.h"
 #include "cloud/cloud_tablet_mgr.h"
+#include "io/fs/file_system.h"
 #include "storage/rowset/group_rowset_writer.h"
 #include "storage/rowset/rowset_factory.h"
 #include "storage/rowset/rowset_writer_context.h"
@@ -236,6 +237,13 @@ const RowsetMetaSharedPtr& 
CloudRowsetBuilder::rowset_meta() {
     return _rowset_writer->rowset_meta();
 }
 
+bool CloudRowsetBuilder::is_s3_storage() const {
+    if (_rowset_writer == nullptr) {
+        return false;
+    }
+    return _rowset_writer->context().fs()->type() == io::FileSystemType::S3;
+}
+
 Status CloudRowsetBuilder::commit_rowset(const std::string& job_id, int64_t 
table_id) {
     return _engine.meta_mgr().commit_rowset(*rowset_meta(), job_id, table_id);
 }
diff --git a/be/src/cloud/cloud_rowset_builder.h 
b/be/src/cloud/cloud_rowset_builder.h
index b4a9ead311e..c65e1924448 100644
--- a/be/src/cloud/cloud_rowset_builder.h
+++ b/be/src/cloud/cloud_rowset_builder.h
@@ -37,6 +37,8 @@ public:
 
     const RowsetMetaSharedPtr& rowset_meta();
 
+    virtual bool is_s3_storage() const;
+
     virtual Status commit_rowset(const std::string& job_id, int64_t table_id);
 
     virtual Status set_txn_related_info();
@@ -92,6 +94,8 @@ public:
         return _data_builder->get_partial_update_info();
     }
 
+    bool is_s3_storage() const override { return 
_data_builder->is_s3_storage(); }
+
     CloudRowsetBuilder* data_builder() { return _data_builder.get(); }
 
     CloudRowsetBuilder* row_binlog_builder() { return 
_row_binlog_builder.get(); }
diff --git a/be/src/load/delta_writer/delta_writer.cpp 
b/be/src/load/delta_writer/delta_writer.cpp
index d831df4c8a3..abcda75afa4 100644
--- a/be/src/load/delta_writer/delta_writer.cpp
+++ b/be/src/load/delta_writer/delta_writer.cpp
@@ -178,8 +178,10 @@ Status DeltaWriter::write(const Block* block, const 
TabletAddRowsPayload& rows,
     }
     {
         SCOPED_TIMER(_wait_flush_limit_timer);
-        while (_memtable_writer->flush_running_count() >=
-               config::memtable_flush_running_count_limit) {
+        const auto effective_flush_running_count_limit =
+                config::memtable_flush_running_count_limit *
+                (_req.write_req_type == WriteRequestType::GROUP ? 2 : 1);
+        while (_memtable_writer->flush_running_count() >= 
effective_flush_running_count_limit) {
             std::this_thread::sleep_for(std::chrono::milliseconds(10));
         }
     }


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

Reply via email to