You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by mr...@apache.org on 2026/04/03 02:42:41 UTC

(doris) branch master updated: [fix](spill) Fix revocable_mem_size to return 0 when partition reaches max repartition depth (#61973)

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

mrhhsg 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 e4ed81fdb1b [fix](spill) Fix revocable_mem_size to return 0 when partition reaches max repartition depth (#61973)
e4ed81fdb1b is described below

commit e4ed81fdb1b78dbf4b283c8fd3096dcf379655b8
Author: Jerry Hu <hu...@selectdb.com>
AuthorDate: Fri Apr 3 10:42:33 2026 +0800

    [fix](spill) Fix revocable_mem_size to return 0 when partition reaches max repartition depth (#61973)
    
    ### What problem does this PR solve?
    
    When a partition's level reaches or exceeds the max repartition depth,
    it cannot be repartitioned further. In this case, `revocable_mem_size()`
    should return 0 because the memory is not actually revocable —
    attempting to spill it would lead to futile repartition attempts.
    
    This fix adds the depth check to both `PartitionedAggSourceOperatorX`
    and `PartitionedHashJoinProbeOperatorX`, along with corresponding unit
    tests that verify:
    - revocable_mem_size returns 0 at (max depth - 1)
    - revocable_mem_size returns >0 at (max depth - 2)
    
    
    
    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 -->
---
 .../partitioned_aggregation_source_operator.cpp    |  5 +++
 .../partitioned_hash_join_probe_operator.cpp       |  5 +++
 ...artitioned_aggregation_source_operator_test.cpp | 38 ++++++++++++++++++
 .../partitioned_hash_join_probe_operator_test.cpp  | 45 ++++++++++++++++++++++
 4 files changed, 93 insertions(+)

diff --git a/be/src/exec/operator/partitioned_aggregation_source_operator.cpp b/be/src/exec/operator/partitioned_aggregation_source_operator.cpp
index bfefcfb9af2..ea57cc0091d 100644
--- a/be/src/exec/operator/partitioned_aggregation_source_operator.cpp
+++ b/be/src/exec/operator/partitioned_aggregation_source_operator.cpp
@@ -191,6 +191,11 @@ size_t PartitionedAggSourceOperatorX::revocable_mem_size(RuntimeState* state) co
     if (!local_state._shared_state->_is_spilled || !local_state._current_partition.spill_file) {
         return 0;
     }
+    // If the current partition has reached the max repartition depth, it cannot be
+    // repartitioned further, so its data is not revocable.
+    if ((local_state._current_partition.level + 1) >= _repartition_max_depth) {
+        return 0;
+    }
 
     size_t bytes = 0;
     for (const auto& block : local_state._blocks) {
diff --git a/be/src/exec/operator/partitioned_hash_join_probe_operator.cpp b/be/src/exec/operator/partitioned_hash_join_probe_operator.cpp
index d5267969cd4..1bbc1972bec 100644
--- a/be/src/exec/operator/partitioned_hash_join_probe_operator.cpp
+++ b/be/src/exec/operator/partitioned_hash_join_probe_operator.cpp
@@ -875,6 +875,11 @@ size_t PartitionedHashJoinProbeOperatorX::revocable_mem_size(RuntimeState* state
         // Or if current partition has finished build hash table.
         return 0;
     }
+    // If the current partition has reached the max repartition depth, it cannot be
+    // repartitioned further, so its data is not revocable.
+    if ((local_state._current_partition.level + 1) >= _repartition_max_depth) {
+        return 0;
+    }
 
     // Include build-side memory that has been recovered but not yet consumed by the hash table.
     // This data is revocable because we can repartition instead of building the hash table.
diff --git a/be/test/exec/operator/partitioned_aggregation_source_operator_test.cpp b/be/test/exec/operator/partitioned_aggregation_source_operator_test.cpp
index f331b529ce5..2b13ad97f4c 100644
--- a/be/test/exec/operator/partitioned_aggregation_source_operator_test.cpp
+++ b/be/test/exec/operator/partitioned_aggregation_source_operator_test.cpp
@@ -737,6 +737,44 @@ TEST_F(PartitionedAggregationSourceOperatorTest, RevocableMemSizeWithAggContaine
     EXPECT_EQ(source_operator->revocable_mem_size(_helper.runtime_state.get()), container_bytes);
 }
 
+// Condition: partition (level + 1) >= _repartition_max_depth → 0 (not revocable).
+TEST_F(PartitionedAggregationSourceOperatorTest, RevocableMemSizeAtMaxDepthReturnsZero) {
+    auto [source_operator, sink_operator] = _helper.create_operators();
+    const auto tnode = _helper.create_test_plan_node();
+    ASSERT_TRUE(source_operator->init(tnode, _helper.runtime_state.get()).ok());
+    ASSERT_TRUE(source_operator->prepare(_helper.runtime_state.get()).ok());
+
+    std::shared_ptr<MockPartitionedAggSharedState> shared_state;
+    auto* local_state = _helper.create_source_local_state(_helper.runtime_state.get(),
+                                                          source_operator.get(), shared_state);
+
+    // Add a large block so that without the depth check, revocable_mem_size would be > 0.
+    std::vector<int32_t> large_data(300000);
+    std::iota(large_data.begin(), large_data.end(), 0);
+    auto large_block = ColumnHelper::create_block<DataTypeInt32>(large_data);
+    ASSERT_GT(large_block.allocated_bytes(), 1UL << 20);
+    local_state->_blocks.push_back(std::move(large_block));
+
+    SpillFileSPtr spill_file;
+    ASSERT_TRUE(ExecEnv::GetInstance()
+                        ->spill_file_mgr()
+                        ->create_spill_file("ut/revocable_max_depth", spill_file)
+                        .ok());
+    local_state->_current_partition.spill_file = spill_file;
+
+    ASSERT_GE(source_operator->_repartition_max_depth, 2);
+
+    // Set partition level to max depth → not revocable.
+    local_state->_current_partition.level =
+            static_cast<int>(source_operator->_repartition_max_depth) - 1;
+    EXPECT_EQ(source_operator->revocable_mem_size(_helper.runtime_state.get()), 0UL);
+
+    // Also verify level == max_depth - 2 IS still revocable.
+    local_state->_current_partition.level =
+            static_cast<int>(source_operator->_repartition_max_depth) - 2;
+    EXPECT_GT(source_operator->revocable_mem_size(_helper.runtime_state.get()), 0UL);
+}
+
 // --- Tests for PartitionedAggSourceOperatorX::revoke_memory ---
 // revoke_memory:
 //   if (!_is_spilled) return OK  (no-op)
diff --git a/be/test/exec/operator/partitioned_hash_join_probe_operator_test.cpp b/be/test/exec/operator/partitioned_hash_join_probe_operator_test.cpp
index 7a1fb8c839a..e50ccb1ec35 100644
--- a/be/test/exec/operator/partitioned_hash_join_probe_operator_test.cpp
+++ b/be/test/exec/operator/partitioned_hash_join_probe_operator_test.cpp
@@ -1027,6 +1027,51 @@ TEST_F(PartitionedHashJoinProbeOperatorTest, revocable_mem_size) {
     ASSERT_EQ(probe_operator->revocable_mem_size(_helper.runtime_state.get()), 0);
 }
 
+// Partition (level + 1) >= _repartition_max_depth → revocable_mem_size returns 0.
+TEST_F(PartitionedHashJoinProbeOperatorTest, revocable_mem_size_at_max_depth) {
+    auto [probe_operator, sink_operator] = _helper.create_operators();
+
+    std::shared_ptr<MockPartitionedHashJoinSharedState> shared_state;
+    auto local_state = _helper.create_probe_local_state(_helper.runtime_state.get(),
+                                                        probe_operator.get(), shared_state);
+
+    local_state->_shared_state->_is_spilled = true;
+    local_state->_child_eos = true;
+
+    // Set up a valid current partition with build not finished (the path that
+    // checks level vs max depth).
+    SpillFileSPtr build_file;
+    ASSERT_TRUE(ExecEnv::GetInstance()
+                        ->spill_file_mgr()
+                        ->create_spill_file("ut/revocable_join_max_depth_build", build_file)
+                        .ok());
+    SpillFileSPtr probe_file;
+    ASSERT_TRUE(ExecEnv::GetInstance()
+                        ->spill_file_mgr()
+                        ->create_spill_file("ut/revocable_join_max_depth_probe", probe_file)
+                        .ok());
+    local_state->_current_partition = JoinSpillPartitionInfo(
+            build_file, probe_file, static_cast<int>(probe_operator->_repartition_max_depth - 1));
+    local_state->_current_partition.build_finished = false;
+
+    // Add a recovered build block large enough to exceed the threshold.
+    std::vector<int32_t> large_data(256 * 1024); // 1MB of int32
+    std::iota(large_data.begin(), large_data.end(), 0);
+    Block large_block = ColumnHelper::create_block<DataTypeInt32>(large_data);
+    ASSERT_GE(large_block.allocated_bytes(), SpillFile::MIN_SPILL_WRITE_BATCH_MEM);
+    local_state->_recovered_build_block = MutableBlock::create_unique(std::move(large_block));
+
+    ASSERT_GE(probe_operator->_repartition_max_depth, 2);
+
+    // At max depth → not revocable.
+    ASSERT_EQ(probe_operator->revocable_mem_size(_helper.runtime_state.get()), 0);
+
+    // One level below max depth → revocable.
+    local_state->_current_partition.level =
+            static_cast<int>(probe_operator->_repartition_max_depth) - 2;
+    ASSERT_GT(probe_operator->revocable_mem_size(_helper.runtime_state.get()), 0);
+}
+
 TEST_F(PartitionedHashJoinProbeOperatorTest, get_reserve_mem_size) {
     // Setup test environment
     auto [probe_operator, sink_operator] = _helper.create_operators();


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org