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

morningman 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 7e561b98919 [fix](be) Correct the external row fetch InitReaderAvgTime 
and GetBlockAvgTime (#69583)
7e561b98919 is described below

commit 7e561b98919e5197b0720799f3733cb9391f7413
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Oct 26 08:01:00 2026 +0000

    [fix](be) Correct the external row fetch InitReaderAvgTime and 
GetBlockAvgTime (#69583)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #67005
    
    Problem Summary:
    
    **In short.** This ports the profile fix that branch-4.1/4.2 made in
    #67005 to master: the second phase of TopN lazy materialization on
    external tables reported a wrong `InitReaderAvgTime` and put the init
    time into `GetBlockAvgTime`. It also stops one request block's averages
    from leaking into the next one of the same RPC, which the 4.x fix still
    does.
    
    Original author: @zhangstar333
    
    **Background.** When a TopN query lazily materializes an external table,
    the second phase sends the row IDs to the backends that own the rows. On
    each backend, `RowIdStorageReader::read_batch_external_row` reads one
    request block (one table) by grouping its row IDs per scan range and
    reading each range with its own reader. It then reports two values per
    request block: `InitReaderAvgTime` and `GetBlockAvgTime`, the mean
    reader-init and block-read time over those ranges.
    `MaterializationOperator` lists them per backend in the query profile,
    one value per RPC response block.
    
    **The problem, and what it cost.** Both values are wrong on master:
    
    - `InitReaderAvgTime` assigns each range's init time instead of adding
    it, so it is the last range's init time divided by the number of ranges.
    - `GetBlockAvgTime` is filled with the init-time average, so the
    block-read time never reaches the profile.
    - The averages are accumulated in variables that `read_by_rowids()`
    zeroes once per RPC and passes to every request block. A query that
    lazily materializes two external tables sends two request blocks in one
    RPC, and the second block's sums start from the first block's averages.
    
    So the profile of a slow lazily materialized external read cannot tell
    whether opening the readers or reading the rows took the time. Only the
    profile and one log line are affected; the fetched rows are not. #67005
    (a Lance profile PR in 4.x) fixed the first two points; branch-4.1 and
    branch-4.2 still have the third.
    
    **How this PR fixes it.** The summary moves into
    `RowIdStorageReader::add_external_fetch_avg_time_profile()`. It sums
    both times over the block's ranges starting from zero, overwrites the
    caller's variables with the averages, and reports each average under its
    own key. The `Query stats` log line at the end of `read_by_rowids()`
    prints those variables, so it now shows the averages of the RPC's last
    external request block, the same block whose scan range count it already
    prints.
    
    **Results.** `InitReaderAvgTime / GetBlockAvgTime` for a request block
    with three ranges that took 10/100, 20/200 and 60/600 ms (init/read),
    first as the only external block of the RPC, then as the second one
    after a block that left 50/500 ms in the shared variables:
    
    | | Only request block | Second request block |
    |---|---|---|
    | master | 20ms / 20ms | 20ms / 20ms |
    | branch-4.1/4.2 after #67005 | 30ms / 300ms | 46ms / 466ms |
    | This PR | 30ms / 300ms | 30ms / 300ms |
    
    Co-authored-by: zhangstar333 <[email protected]>
---
 be/src/exec/rowid_fetcher.cpp       | 31 +++++++++++++++++++++++--------
 be/src/exec/rowid_fetcher.h         | 11 +++++++++++
 be/test/exec/rowid_fetcher_test.cpp | 32 ++++++++++++++++++++++++++++++++
 3 files changed, 66 insertions(+), 8 deletions(-)

diff --git a/be/src/exec/rowid_fetcher.cpp b/be/src/exec/rowid_fetcher.cpp
index 3f9c0efc82e..615b553990b 100644
--- a/be/src/exec/rowid_fetcher.cpp
+++ b/be/src/exec/rowid_fetcher.cpp
@@ -685,6 +685,27 @@ TFileScanRangeParams 
RowIdStorageReader::build_external_scan_params(
     return params;
 }
 
+void RowIdStorageReader::add_external_fetch_avg_time_profile(
+        const std::vector<ExternalFetchStatistics>& fetch_statistics,
+        RuntimeProfile* runtime_profile, int64_t* init_reader_avg_ms, int64_t* 
get_block_avg_ms) {
+    // read_by_rowids() reads a request block only when it has a row ID, and 
every row ID lands
+    // in a scan range, so there is at least one range to average over.
+    DORIS_CHECK(!fetch_statistics.empty());
+    int64_t init_reader_total_ms = 0;
+    int64_t get_block_total_ms = 0;
+    for (const auto& statistics : fetch_statistics) {
+        init_reader_total_ms += statistics.init_reader_ms;
+        get_block_total_ms += statistics.get_block_ms;
+    }
+    const auto range_count = static_cast<int64_t>(fetch_statistics.size());
+    *init_reader_avg_ms = init_reader_total_ms / range_count;
+    *get_block_avg_ms = get_block_total_ms / range_count;
+    runtime_profile->add_info_string(InitReaderAvgTimeProfile,
+                                     std::to_string(*init_reader_avg_ms) + 
"ms");
+    runtime_profile->add_info_string(GetBlockAvgTimeProfile,
+                                     std::to_string(*get_block_avg_ms) + "ms");
+}
+
 Status RowIdStorageReader::read_batch_external_row(
         const uint64_t workload_group_id, const PRequestBlockDesc& 
request_block_desc,
         std::shared_ptr<IdFileMap> id_file_map, std::vector<SlotDescriptor>& 
slots,
@@ -902,8 +923,6 @@ Status RowIdStorageReader::read_batch_external_row(
         size_t idx = 0;
         for (const auto& [_, scan_info] : scan_rows) {
             format_to(file_read_lines_buffer, "{}, ", scan_info.first.size());
-            *init_reader_avg_ms = fetch_statistics[idx].init_reader_ms;
-            *get_block_avg_ms += fetch_statistics[idx].get_block_ms;
             format_to(file_read_bytes_buffer, "{}, ", 
fetch_statistics[idx].file_read_bytes);
             format_to(file_read_times_buffer, "{}, ", 
fetch_statistics[idx].file_read_times);
             idx++;
@@ -913,12 +932,8 @@ Status RowIdStorageReader::read_batch_external_row(
         format_to(file_read_bytes_buffer, "]");
         format_to(file_read_times_buffer, "]");
 
-        *init_reader_avg_ms /= fetch_statistics.size();
-        *get_block_avg_ms /= fetch_statistics.size();
-        runtime_profile->add_info_string(InitReaderAvgTimeProfile,
-                                         std::to_string(*init_reader_avg_ms) + 
"ms");
-        runtime_profile->add_info_string(GetBlockAvgTimeProfile,
-                                         std::to_string(*init_reader_avg_ms) + 
"ms");
+        add_external_fetch_avg_time_profile(fetch_statistics, 
runtime_profile.get(),
+                                            init_reader_avg_ms, 
get_block_avg_ms);
         runtime_profile->add_info_string(FileReadLinesProfile,
                                          
fmt::to_string(file_read_lines_buffer));
         runtime_profile->add_info_string(FileScannerV2::FileReadBytesProfile,
diff --git a/be/src/exec/rowid_fetcher.h b/be/src/exec/rowid_fetcher.h
index 916b77ff214..ad06a12d993 100644
--- a/be/src/exec/rowid_fetcher.h
+++ b/be/src/exec/rowid_fetcher.h
@@ -35,6 +35,7 @@
 
 namespace doris {
 
+class RuntimeProfile;
 class RuntimeState;
 class TQueryOptions;
 class TupleDescriptor;
@@ -148,6 +149,16 @@ private:
                                              const 
std::function<std::string(size_t)>& make_task_id,
                                              const 
std::function<Status(size_t)>& run_task);
 
+    // Reports InitReaderAvgTime and GetBlockAvgTime of one request block: the 
mean reader-init
+    // and block-read times over its scan ranges, e.g. 20ms and 200ms for two 
ranges that took
+    // 10/100 ms and 30/300 ms. Both outputs are overwritten rather than 
accumulated, because an
+    // RPC that fetches two lazily materialized tables reads its two request 
blocks into the same
+    // caller variables.
+    static void add_external_fetch_avg_time_profile(
+            const std::vector<ExternalFetchStatistics>& fetch_statistics,
+            RuntimeProfile* runtime_profile, int64_t* init_reader_avg_ms,
+            int64_t* get_block_avg_ms);
+
     struct ExternalFetchStatistics {
         int64_t init_reader_ms = 0;
         int64_t get_block_ms = 0;
diff --git a/be/test/exec/rowid_fetcher_test.cpp 
b/be/test/exec/rowid_fetcher_test.cpp
index b0478cfc4ab..3c6189a849e 100644
--- a/be/test/exec/rowid_fetcher_test.cpp
+++ b/be/test/exec/rowid_fetcher_test.cpp
@@ -33,6 +33,7 @@
 #include "runtime/descriptor_helper.h"
 #include "runtime/descriptors.h"
 #include "runtime/exec_env.h"
+#include "runtime/runtime_profile.h"
 #include "runtime/runtime_state.h"
 #include "storage/id_manager.h"
 #include "storage/rowset_id.h"
@@ -301,6 +302,37 @@ TEST_F(RowIdStorageReaderTest, 
ExternalFetchPartitionSlotsPreserveHivePositionMa
     }
 }
 
+// A request block reads each of its scan ranges with a separate reader, and 
its profile reports the
+// mean reader-init and block-read time over those ranges. When an RPC fetches 
two lazily
+// materialized tables, the second request block starts with the first block's 
averages in the
+// caller's variables; they must not leak into its own.
+TEST_F(RowIdStorageReaderTest, 
ExternalFetchProfileAveragesInitAndGetBlockTimes) {
+    std::vector<RowIdStorageReader::ExternalFetchStatistics> 
fetch_statistics(3);
+    fetch_statistics[0].init_reader_ms = 10;
+    fetch_statistics[0].get_block_ms = 100;
+    fetch_statistics[1].init_reader_ms = 20;
+    fetch_statistics[1].get_block_ms = 200;
+    fetch_statistics[2].init_reader_ms = 60;
+    fetch_statistics[2].get_block_ms = 600;
+    // The averages a previous request block of the same RPC left behind.
+    int64_t init_reader_avg_ms = 50;
+    int64_t get_block_avg_ms = 500;
+    RuntimeProfile profile("ExternalRowIDFetcher");
+
+    RowIdStorageReader::add_external_fetch_avg_time_profile(fetch_statistics, 
&profile,
+                                                            
&init_reader_avg_ms, &get_block_avg_ms);
+
+    EXPECT_EQ(init_reader_avg_ms, 30);
+    EXPECT_EQ(get_block_avg_ms, 300);
+    const auto* init_reader_avg =
+            
profile.get_info_string(RowIdStorageReader::InitReaderAvgTimeProfile);
+    ASSERT_NE(init_reader_avg, nullptr);
+    EXPECT_EQ(*init_reader_avg, "30ms");
+    const auto* get_block_avg = 
profile.get_info_string(RowIdStorageReader::GetBlockAvgTimeProfile);
+    ASSERT_NE(get_block_avg, nullptr);
+    EXPECT_EQ(*get_block_avg, "300ms");
+}
+
 TEST_F(RowIdStorageReaderTest, SameSourceColumnSharesKey) {
     // The bug case: one physical column projected twice must dedup onto one 
scan column.
     const SlotDescriptor first = make_slot({});


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

Reply via email to