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

deardeng 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 4de121c3b62 [fix](file cache) keep disk resource limit mode hysteresis 
across checks (#67313)
4de121c3b62 is described below

commit 4de121c3b6208d9b85cd6530876a7d86d2b5ffa7
Author: deardeng <[email protected]>
AuthorDate: Fri Sep 18 14:14:11 2026 +0800

    [fix](file cache) keep disk resource limit mode hysteresis across checks 
(#67313)
    
    Problem Summary:
    The disk-pressure protection mode is meant to enter at `enter` (85%) and
    stay on until usage drops below `exit` (80%), holding its previous state
    in between. That hold is implemented by `_disk_resource_limit_mode`
    remembering the previous round — and `check_disk_resource_limit()` wiped
    it before reading `statfs`:
    
    ```cpp
    if (_capacity > _cur_cache_size) {
        _disk_resource_limit_mode = false;    // runs before statfs is read
    }
    ...
    } else if (_disk_resource_limit_mode && space < exit && inode < exit) {
    ```
    
    With the memory gone, the exit branch is dead code: the mode dropped as
    soon as usage fell below `enter`, never holding down to `exit`. The
    `disk_resource_limit_mode` bvar was unusable for the same reason — a
    single round wrote 0 and then 1 whenever the mode was immediately
    re-entered, so it could not be used to tell whether a node was in the
    mode.
    
    ### What this PR changes
    
    1. Drop the pre-clear so the band holds, and publish
    `_disk_limit_mode_metrics` once, after the decision.
    2. Drop the `_disk_resource_limit_mode = true` that `reset_capacity()`
    forces on when shrinking — see below.
    3. Make `_disk_resource_limit_mode` and `_need_evict_cache_in_advance`
    `std::atomic<bool>`. `run_background_monitor()` writes them without the
    cache lock while `try_reserve()`, `is_overflow()` and
    `run_background_gc()` read them, so both plain bools were already racy
    on master.
    
    ### Why the `reset_capacity()` force goes too
    
    Both lines are halves of a mechanism that no longer exists. They arrived
    together in #37484, when shrinking was asynchronous:
    
    ```cpp
    cell->is_deleted = true;            // tagged only, nothing freed here
    ...
    _disk_resource_limit_mode = true;
    _async_clear_file_cache = true;     // a background thread does the removal
    ```
    
    `reset_capacity()` returned with `_cur_cache_size` still above the new
    capacity, so the force made `try_reserve()` drain aggressively while the
    background cleanup caught up, and `if (_capacity > _cur_cache_size)` was
    the matching "drained, stop" condition.
    
    Shrinking is synchronous now — `reset_capacity()` calls `remove()` under
    the cache lock and returns at the new capacity, and
    `_async_clear_file_cache` no longer exists in `be/src`. So the force has
    nothing left to accelerate, while the pre-clear kept firing on its own
    condition, which holds in ordinary operation whenever the cache is under
    its budget, not only while a shrink drains. That is the bug.
    
    Removing only the pre-clear would leave the force with nothing to turn
    it off: a shrink on a node whose disk sits in `[exit, enter)` for
    unrelated reasons — BE logs, another data dir on the same mount — would
    pin the mode on permanently, and every reservation would evict five
    times the requested size.
    
    The force was also not capacity enforcement during async metadata load:
    
    - The loader inserts restored cells through `add_cell()` and never calls
    `try_reserve()` (`fs_file_cache_storage.cpp:818`, `:1007`, `:1179`).
    - `try_reserve_during_async_load()` does not consult `_capacity`, and
    cannot: while loading, `_cur_cache_size` is a partial count growing from
    zero toward the restored total, so comparing it against the full budget
    is meaningless. `statfs` is the only signal valid at that point, which
    is why the mode is the only thing that function checks.
    
    ### Deliberately out of scope
    
    Real, pre-existing on master, independent of the state machine touched
    here, each getting its own PR:
    
    - **Capacity admission during async metadata load.** Making it work
    needs the loader to publish the on-disk total up front, or admission
    deferred until loading completes — a change to the loader's publication
    protocol, not an `if` in the reservation path.
    - **The eviction target reused as the admitted size.** `try_reserve()`
    does `size = 5 * size` under disk pressure and passes that to
    `QueryFileCacheContext::reserve()`, while `add_cell()` creates a cell of
    the original size, so `DCHECK(iter->size == cell_size)` at
    `block_file_cache.cpp:1483` can fire and release builds mis-account
    query usage. Already reachable on master whenever the disk is at or
    above `enter` with `enable_file_cache_query_limit` on.
    - **Republication over deleted files during load.**
    `load_cache_info_into_memory_from_db()` buffers 10k `BatchLoadArgs`
    outside `_mutex` (`fs_file_cache_storage.cpp:995`) and
    `handle_already_loaded_block()` checks only whether a cell exists, never
    whether the file is still on disk.
---
 be/src/io/cache/block_file_cache.cpp       |  28 ++++----
 be/src/io/cache/block_file_cache.h         |   4 +-
 be/test/io/cache/block_file_cache_test.cpp | 108 +++++++++++++++++++++++++----
 3 files changed, 110 insertions(+), 30 deletions(-)

diff --git a/be/src/io/cache/block_file_cache.cpp 
b/be/src/io/cache/block_file_cache.cpp
index e3eaa691b9f..a4c769ad9bb 100644
--- a/be/src/io/cache/block_file_cache.cpp
+++ b/be/src/io/cache/block_file_cache.cpp
@@ -2201,8 +2201,6 @@ std::string BlockFileCache::reset_capacity(size_t 
new_capacity) {
             queue_released = remove_blocks(_ttl_queue);
             ss << " ttl_queue released " << queue_released;
 
-            _disk_resource_limit_mode = true;
-            _disk_limit_mode_metrics->set_value(1);
             ss << " total_space_released=" << space_released;
         }
         old_capacity = _capacity;
@@ -2222,11 +2220,6 @@ void BlockFileCache::check_disk_resource_limit() {
         return;
     }
 
-    bool previous_mode = _disk_resource_limit_mode;
-    if (_capacity > _cur_cache_size) {
-        _disk_resource_limit_mode = false;
-        _disk_limit_mode_metrics->set_value(0);
-    }
     std::pair<int, int> percent;
     int ret = disk_used_percentage(_cache_base_path, &percent);
     if (ret != 0) {
@@ -2251,18 +2244,21 @@ void BlockFileCache::check_disk_resource_limit() {
         config::file_cache_enter_disk_resource_limit_mode_percent = 88;
         config::file_cache_exit_disk_resource_limit_mode_percent = 80;
     }
+    bool previous_mode = _disk_resource_limit_mode.load();
     bool is_space_insufficient = is_insufficient(space_percentage);
     bool is_inode_insufficient = is_insufficient(inode_percentage);
+    // Enter when either resource reaches the enter threshold, but exit only 
after both
+    // resources fall below the exit threshold. Values in [exit, enter) 
preserve the previous
+    // mode through _disk_resource_limit_mode.
     if (is_space_insufficient || is_inode_insufficient) {
         _disk_resource_limit_mode = true;
-        _disk_limit_mode_metrics->set_value(1);
     } else if (_disk_resource_limit_mode &&
                (space_percentage < 
config::file_cache_exit_disk_resource_limit_mode_percent) &&
                (inode_percentage < 
config::file_cache_exit_disk_resource_limit_mode_percent)) {
         _disk_resource_limit_mode = false;
-        _disk_limit_mode_metrics->set_value(0);
     }
-    if (previous_mode != _disk_resource_limit_mode) {
+    _disk_limit_mode_metrics->set_value(_disk_resource_limit_mode.load());
+    if (previous_mode != _disk_resource_limit_mode.load()) {
         // add log for disk resource limit mode switching
         if (_disk_resource_limit_mode) {
             LOG(WARNING) << "Entering disk resource limit mode: file_cache=" 
<< get_base_path()
@@ -2319,7 +2315,7 @@ void BlockFileCache::check_need_evict_cache_in_advance() {
         config::file_cache_enter_need_evict_cache_in_advance_percent = 78;
         config::file_cache_exit_need_evict_cache_in_advance_percent = 75;
     }
-    bool previous_mode = _need_evict_cache_in_advance;
+    bool previous_mode = _need_evict_cache_in_advance.load();
     bool is_space_insufficient = is_insufficient(space_percentage);
     bool is_inode_insufficient = is_insufficient(inode_percentage);
     bool is_size_insufficient = is_insufficient(size_percentage);
@@ -2333,7 +2329,7 @@ void BlockFileCache::check_need_evict_cache_in_advance() {
         _need_evict_cache_in_advance = false;
         _need_evict_cache_in_advance_metrics->set_value(0);
     }
-    if (previous_mode != _need_evict_cache_in_advance) {
+    if (previous_mode != _need_evict_cache_in_advance.load()) {
         // add log for evict cache in advance mode switching
         if (_need_evict_cache_in_advance) {
             LOG(WARNING) << "Entering evict cache in advance mode: "
@@ -2770,8 +2766,8 @@ std::map<std::string, double> BlockFileCache::get_stats() 
{
             
(double)_lru_recorder_shadow_queue_element_count_metrics[FileCacheType::DISPOSABLE]
                     ->get_value();
 
-    stats["need_evict_cache_in_advance"] = 
(double)_need_evict_cache_in_advance;
-    stats["disk_resource_limit_mode"] = (double)_disk_resource_limit_mode;
+    stats["need_evict_cache_in_advance"] = 
(double)_need_evict_cache_in_advance.load();
+    stats["disk_resource_limit_mode"] = 
(double)_disk_resource_limit_mode.load();
 
     stats["total_removed_counts"] = (double)_num_removed_blocks->get_value();
     stats["total_hit_counts"] = (double)_num_hit_blocks->get_value();
@@ -2825,8 +2821,8 @@ std::map<std::string, double> 
BlockFileCache::get_stats_unsafe() {
             
(double)_lru_recorder_shadow_queue_element_count_metrics[FileCacheType::DISPOSABLE]
                     ->get_value();
 
-    stats["need_evict_cache_in_advance"] = 
(double)_need_evict_cache_in_advance;
-    stats["disk_resource_limit_mode"] = (double)_disk_resource_limit_mode;
+    stats["need_evict_cache_in_advance"] = 
(double)_need_evict_cache_in_advance.load();
+    stats["disk_resource_limit_mode"] = 
(double)_disk_resource_limit_mode.load();
 
     stats["total_removed_counts"] = (double)_num_removed_blocks->get_value();
     stats["total_hit_counts"] = (double)_num_hit_blocks->get_value();
diff --git a/be/src/io/cache/block_file_cache.h 
b/be/src/io/cache/block_file_cache.h
index 2de0182ba7f..69c79c50e52 100644
--- a/be/src/io/cache/block_file_cache.h
+++ b/be/src/io/cache/block_file_cache.h
@@ -559,8 +559,8 @@ private:
     std::thread _cache_background_block_lru_update_thread;
     std::atomic_bool _async_open_done {false};
     // disk space or inode is less than the specified value
-    bool _disk_resource_limit_mode {false};
-    bool _need_evict_cache_in_advance {false};
+    std::atomic<bool> _disk_resource_limit_mode {false};
+    std::atomic<bool> _need_evict_cache_in_advance {false};
     bool _is_initialized {false};
 
     // strategy
diff --git a/be/test/io/cache/block_file_cache_test.cpp 
b/be/test/io/cache/block_file_cache_test.cpp
index 68d1d09db15..a84288f4521 100644
--- a/be/test/io/cache/block_file_cache_test.cpp
+++ b/be/test/io/cache/block_file_cache_test.cpp
@@ -6000,7 +6000,7 @@ TEST_F(BlockFileCacheTest, 
test_check_disk_reource_limit_2) {
     std::this_thread::sleep_for(std::chrono::milliseconds(10));
     EXPECT_EQ(config::file_cache_enter_disk_resource_limit_mode_percent, 2);
     EXPECT_EQ(config::file_cache_exit_disk_resource_limit_mode_percent, 1);
-    EXPECT_TRUE(cache._disk_resource_limit_mode);
+    EXPECT_TRUE(cache._disk_resource_limit_mode.load());
     config::file_cache_enter_disk_resource_limit_mode_percent = 99;
     if (fs::exists(cache_base_path)) {
         fs::remove_all(cache_base_path);
@@ -6029,13 +6029,93 @@ TEST_F(BlockFileCacheTest, 
test_check_disk_reource_limit_3) {
         std::this_thread::sleep_for(std::chrono::milliseconds(1));
     }
     std::this_thread::sleep_for(std::chrono::milliseconds(10));
-    EXPECT_FALSE(cache._disk_resource_limit_mode);
+    EXPECT_FALSE(cache._disk_resource_limit_mode.load());
     config::file_cache_exit_disk_resource_limit_mode_percent = 80;
     if (fs::exists(cache_base_path)) {
         fs::remove_all(cache_base_path);
     }
 }
 
+TEST_F(BlockFileCacheTest, test_check_disk_resource_limit_hysteresis) {
+    if (fs::exists(cache_base_path)) {
+        fs::remove_all(cache_base_path);
+    }
+    fs::create_directories(cache_base_path);
+
+    const auto origin_enter = 
config::file_cache_enter_disk_resource_limit_mode_percent;
+    const auto origin_exit = 
config::file_cache_exit_disk_resource_limit_mode_percent;
+    auto* sp = SyncPoint::get_instance();
+    Defer defer {[&] {
+        config::file_cache_enter_disk_resource_limit_mode_percent = 
origin_enter;
+        config::file_cache_exit_disk_resource_limit_mode_percent = origin_exit;
+        sp->disable_processing();
+        sp->clear_call_back("BlockFileCache::disk_used_percentage:1");
+        if (fs::exists(cache_base_path)) {
+            fs::remove_all(cache_base_path);
+        }
+    }};
+
+    config::file_cache_enter_disk_resource_limit_mode_percent = 85;
+    config::file_cache_exit_disk_resource_limit_mode_percent = 80;
+
+    io::FileCacheSettings settings;
+    settings.capacity = 100_mb;
+    settings.storage = "disk";
+    io::BlockFileCache cache(cache_base_path, settings);
+
+    std::pair<int, int> disk_usage {90, 70};
+    sp->set_call_back("BlockFileCache::disk_used_percentage:1", [&](auto&& 
values) {
+        *try_any_cast<std::pair<int, int>*>(values.back()) = disk_usage;
+    });
+    sp->enable_processing();
+
+    cache.check_disk_resource_limit();
+    EXPECT_TRUE(cache._disk_resource_limit_mode.load());
+    EXPECT_EQ(cache._disk_limit_mode_metrics->get_value(), 
cache._disk_resource_limit_mode.load());
+
+    cache._disk_resource_limit_mode = false;
+    disk_usage = {70, 90};
+    cache.check_disk_resource_limit();
+    EXPECT_TRUE(cache._disk_resource_limit_mode.load());
+    EXPECT_EQ(cache._disk_limit_mode_metrics->get_value(), 
cache._disk_resource_limit_mode.load());
+
+    ASSERT_GT(cache._capacity, cache._cur_cache_size);
+    disk_usage = {82, 70};
+    cache.check_disk_resource_limit();
+    EXPECT_TRUE(cache._disk_resource_limit_mode.load());
+    EXPECT_EQ(cache._disk_limit_mode_metrics->get_value(), 
cache._disk_resource_limit_mode.load());
+
+    disk_usage = {70, 70};
+    cache.check_disk_resource_limit();
+    EXPECT_FALSE(cache._disk_resource_limit_mode.load());
+    EXPECT_EQ(cache._disk_limit_mode_metrics->get_value(), 
cache._disk_resource_limit_mode.load());
+}
+
+TEST_F(BlockFileCacheTest, 
test_check_disk_resource_limit_statfs_failure_preserves_state) {
+    if (fs::exists(cache_base_path)) {
+        fs::remove_all(cache_base_path);
+    }
+    fs::create_directories(cache_base_path);
+    Defer cleanup {[&] {
+        if (fs::exists(cache_base_path)) {
+            fs::remove_all(cache_base_path);
+        }
+    }};
+
+    io::FileCacheSettings settings;
+    settings.capacity = 100_mb;
+    settings.storage = "disk";
+    io::BlockFileCache cache(cache_base_path, settings);
+    cache._disk_resource_limit_mode = true;
+    cache._disk_limit_mode_metrics->set_value(1);
+    cache._cache_base_path = "/non/existent/path/OOXXOO";
+
+    cache.check_disk_resource_limit();
+
+    EXPECT_TRUE(cache._disk_resource_limit_mode.load());
+    EXPECT_EQ(cache._disk_limit_mode_metrics->get_value(), 1);
+}
+
 TEST_F(BlockFileCacheTest, test_align_size) {
     const size_t total_size = 10_mb + 10086;
     {
@@ -6507,9 +6587,13 @@ TEST_F(BlockFileCacheTest, reset_capacity) {
         assert_range(1, segments[0], io::FileBlock::Range(offset, offset + 4),
                      io::FileBlock::State::DOWNLOADED);
     }
+    cache._disk_resource_limit_mode = false;
+    cache._disk_limit_mode_metrics->set_value(0);
     std::cout << cache.reset_capacity(30) << std::endl;
 
     EXPECT_EQ(cache._cur_cache_size, 30);
+    EXPECT_FALSE(cache._disk_resource_limit_mode.load());
+    EXPECT_EQ(cache._disk_limit_mode_metrics->get_value(), 0);
     if (fs::exists(cache_base_path)) {
         fs::remove_all(cache_base_path);
     }
@@ -8337,9 +8421,9 @@ TEST_F(BlockFileCacheTest, 
test_check_need_evict_cache_in_advance) {
     {
         settings.storage = "memory";
         io::BlockFileCache cache(cache_base_path, settings);
-        ASSERT_FALSE(cache._need_evict_cache_in_advance);
+        ASSERT_FALSE(cache._need_evict_cache_in_advance.load());
         cache.check_need_evict_cache_in_advance();
-        ASSERT_FALSE(cache._need_evict_cache_in_advance);
+        ASSERT_FALSE(cache._need_evict_cache_in_advance.load());
     }
 
     // the rest for disk
@@ -8348,17 +8432,17 @@ TEST_F(BlockFileCacheTest, 
test_check_need_evict_cache_in_advance) {
     // bad disk path
     {
         io::BlockFileCache cache(cache_base_path, settings);
-        ASSERT_FALSE(cache._need_evict_cache_in_advance);
+        ASSERT_FALSE(cache._need_evict_cache_in_advance.load());
 
         cache._cache_base_path = "/non/existent/path/OOXXOO";
         cache.check_need_evict_cache_in_advance();
-        ASSERT_FALSE(cache._need_evict_cache_in_advance);
+        ASSERT_FALSE(cache._need_evict_cache_in_advance.load());
     }
 
     // conditions for enter need evict cache in advance
     {
         io::BlockFileCache cache(cache_base_path, settings);
-        ASSERT_FALSE(cache._need_evict_cache_in_advance);
+        ASSERT_FALSE(cache._need_evict_cache_in_advance.load());
 
         // condition1 space usage rate exceed threshold
         config::file_cache_enter_need_evict_cache_in_advance_percent = 70;
@@ -8373,7 +8457,7 @@ TEST_F(BlockFileCacheTest, 
test_check_need_evict_cache_in_advance) {
 
         SyncPoint::get_instance()->enable_processing();
         cache.check_need_evict_cache_in_advance();
-        ASSERT_TRUE(cache._need_evict_cache_in_advance);
+        ASSERT_TRUE(cache._need_evict_cache_in_advance.load());
         SyncPoint::get_instance()->disable_processing();
         SyncPoint::get_instance()->clear_all_call_backs();
 
@@ -8389,7 +8473,7 @@ TEST_F(BlockFileCacheTest, 
test_check_need_evict_cache_in_advance) {
 
         SyncPoint::get_instance()->enable_processing();
         cache.check_need_evict_cache_in_advance();
-        ASSERT_TRUE(cache._need_evict_cache_in_advance);
+        ASSERT_TRUE(cache._need_evict_cache_in_advance.load());
         SyncPoint::get_instance()->disable_processing();
         SyncPoint::get_instance()->clear_all_call_backs();
 
@@ -8397,7 +8481,7 @@ TEST_F(BlockFileCacheTest, 
test_check_need_evict_cache_in_advance) {
         cache._need_evict_cache_in_advance = false;
         cache._cur_cache_size = 80_mb; // set high
         cache.check_need_evict_cache_in_advance();
-        ASSERT_TRUE(cache._need_evict_cache_in_advance);
+        ASSERT_TRUE(cache._need_evict_cache_in_advance.load());
     }
 
     // conditions for exit need evict cache in advance
@@ -8415,7 +8499,7 @@ TEST_F(BlockFileCacheTest, 
test_check_need_evict_cache_in_advance) {
 
         SyncPoint::get_instance()->enable_processing();
         cache.check_need_evict_cache_in_advance();
-        ASSERT_FALSE(cache._need_evict_cache_in_advance);
+        ASSERT_FALSE(cache._need_evict_cache_in_advance.load());
         SyncPoint::get_instance()->disable_processing();
         SyncPoint::get_instance()->clear_all_call_backs();
     }
@@ -8489,7 +8573,7 @@ TEST_F(BlockFileCacheTest, 
test_evict_cache_in_advance_skip) {
     ASSERT_TRUE(cache.get_async_open_success());
 
     cache.check_need_evict_cache_in_advance();
-    ASSERT_TRUE(cache._need_evict_cache_in_advance);
+    ASSERT_TRUE(cache._need_evict_cache_in_advance.load());
 
     // Set recycle keys threshold and fill with enough keys
     config::file_cache_evict_in_advance_recycle_keys_num_threshold = 10;


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

Reply via email to