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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new da6ff4d07e1 branch-4.1: [fix](filecache) clean empty v3 cache dirs 
#63344 (#63616)
da6ff4d07e1 is described below

commit da6ff4d07e191f94aaa6cf4534522e132ca992b6
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue May 26 12:04:26 2026 +0800

    branch-4.1: [fix](filecache) clean empty v3 cache dirs #63344 (#63616)
    
    Cherry-picked from #63344
    
    Co-authored-by: Xin Liao <[email protected]>
---
 be/src/io/cache/fs_file_cache_storage.cpp          | 32 ++++++++----
 be/src/io/fs/local_file_system.cpp                 | 20 ++++++++
 be/src/io/fs/local_file_system.h                   |  3 ++
 .../fs_file_cache_storage_leak_cleaner_test.cpp    | 57 ++++++++++++++++++++++
 be/test/io/fs/local_file_system_test.cpp           | 24 +++++++++
 5 files changed, 127 insertions(+), 9 deletions(-)

diff --git a/be/src/io/cache/fs_file_cache_storage.cpp 
b/be/src/io/cache/fs_file_cache_storage.cpp
index 029d37425e5..5ed82dcaf43 100644
--- a/be/src/io/cache/fs_file_cache_storage.cpp
+++ b/be/src/io/cache/fs_file_cache_storage.cpp
@@ -279,25 +279,39 @@ Status FSFileCacheStorage::read(const FileCacheKey& key, 
size_t value_offset, Sl
 }
 
 Status FSFileCacheStorage::remove(const FileCacheKey& key) {
-    std::string dir = get_path_in_local_cache_v3(key.hash);
-    std::string file = get_path_in_local_cache_v3(dir, key.offset);
+    const std::string v3_dir = get_path_in_local_cache_v3(key.hash);
+    const std::string v3_file = get_path_in_local_cache_v3(v3_dir, key.offset);
     FDCache::instance()->remove_file_reader(std::make_pair(key.hash, 
key.offset));
-    RETURN_IF_ERROR(fs->delete_file(file));
+    RETURN_IF_ERROR(fs->delete_file(v3_file));
     // return OK not means the file is deleted, it may be not exist
 
+    std::string v2_dir;
     { // try to detect the file with old v2 format
-        dir = get_path_in_local_cache_v2(key.hash, key.meta.expiration_time);
-        file = get_path_in_local_cache_v2(dir, key.offset, key.meta.type);
-        RETURN_IF_ERROR(fs->delete_file(file));
+        v2_dir = get_path_in_local_cache_v2(key.hash, 
key.meta.expiration_time);
+        const std::string v2_file = get_path_in_local_cache_v2(v2_dir, 
key.offset, key.meta.type);
+        RETURN_IF_ERROR(fs->delete_file(v2_file));
     }
 
     BlockMetaKey mkey(key.meta.tablet_id, UInt128Wrapper(key.hash), 
key.offset);
     _meta_store->delete_key(mkey);
     std::vector<FileInfo> files;
     bool exists {false};
-    RETURN_IF_ERROR(fs->list(dir, true, &files, &exists));
-    if (files.empty()) {
-        RETURN_IF_ERROR(fs->delete_directory(dir));
+    RETURN_IF_ERROR(fs->list(v2_dir, true, &files, &exists));
+    if (exists && files.empty()) {
+        auto st = fs->delete_empty_directory(v2_dir);
+        if (!st.ok()) {
+            LOG_WARNING("failed to remove cache directory {}", 
v2_dir).error(st);
+        }
+    }
+
+    files.clear();
+    exists = false;
+    RETURN_IF_ERROR(fs->list(v3_dir, true, &files, &exists));
+    if (exists && files.empty()) {
+        auto st = fs->delete_empty_directory(v3_dir);
+        if (!st.ok()) {
+            LOG_WARNING("failed to remove cache directory {}", 
v3_dir).error(st);
+        }
     }
     return Status::OK();
 }
diff --git a/be/src/io/fs/local_file_system.cpp 
b/be/src/io/fs/local_file_system.cpp
index 7edabd836c1..4f9b6baf2a7 100644
--- a/be/src/io/fs/local_file_system.cpp
+++ b/be/src/io/fs/local_file_system.cpp
@@ -167,6 +167,26 @@ Status LocalFileSystem::delete_directory_or_file(const 
Path& path) {
     FILESYSTEM_M(delete_directory_or_file_impl(path));
 }
 
+Status LocalFileSystem::delete_empty_directory(const Path& dir) {
+    FILESYSTEM_M(delete_empty_directory_impl(dir));
+}
+
+Status LocalFileSystem::delete_empty_directory_impl(const Path& dir) {
+    Path path;
+    RETURN_IF_ERROR(absolute_path(dir, path));
+    VLOG_DEBUG << "delete empty directory: " << path.native();
+    int ret = 0;
+    RETRY_ON_EINTR(ret, rmdir(path.c_str()));
+    if (ret != 0) {
+        std::error_code ec(errno, std::generic_category());
+        if (ec == std::errc::no_such_file_or_directory) {
+            return Status::OK();
+        }
+        return localfs_error(ec, fmt::format("failed to delete empty directory 
{}", path.native()));
+    }
+    return Status::OK();
+}
+
 Status LocalFileSystem::delete_directory_or_file_impl(const Path& path) {
     bool is_dir;
     RETURN_IF_ERROR(is_directory(path, &is_dir));
diff --git a/be/src/io/fs/local_file_system.h b/be/src/io/fs/local_file_system.h
index c1ffbd22949..b1546bc4734 100644
--- a/be/src/io/fs/local_file_system.h
+++ b/be/src/io/fs/local_file_system.h
@@ -63,6 +63,8 @@ public:
     static bool equal_or_sub_path(const Path& parent, const Path& child);
     // delete dir or file
     Status delete_directory_or_file(const Path& path);
+    // delete directory only if it is empty
+    Status delete_empty_directory(const Path& dir);
     // change the file permission of the given path
     Status permission(const Path& file, std::filesystem::perms prms);
 
@@ -87,6 +89,7 @@ protected:
     Status delete_file_impl(const Path& file) override;
     Status delete_directory_impl(const Path& dir) override;
     Status delete_directory_or_file_impl(const Path& path);
+    Status delete_empty_directory_impl(const Path& dir);
     Status batch_delete_impl(const std::vector<Path>& files) override;
     Status exists_impl(const Path& path, bool* res) const override;
     Status file_size_impl(const Path& file, int64_t* file_size) const override;
diff --git a/be/test/io/cache/fs_file_cache_storage_leak_cleaner_test.cpp 
b/be/test/io/cache/fs_file_cache_storage_leak_cleaner_test.cpp
index 49ef8587ced..e4e066ad458 100644
--- a/be/test/io/cache/fs_file_cache_storage_leak_cleaner_test.cpp
+++ b/be/test/io/cache/fs_file_cache_storage_leak_cleaner_test.cpp
@@ -288,6 +288,63 @@ TEST_F(FSFileCacheLeakCleanerTest, 
remove_orphan_and_tmp_files) {
     fs::remove_all(dir, ec);
 }
 
+TEST_F(FSFileCacheLeakCleanerTest, 
remove_cleans_empty_v2_and_v3_key_directories) {
+    auto dir = prepare_test_dir();
+    FileCacheSettings settings = default_settings();
+    BlockFileCache mgr(dir.string(), settings);
+    FSFileCacheStorage storage;
+    setup_storage(storage, mgr, dir);
+
+    FileCacheKey key;
+    key.hash = BlockFileCache::hash("remove_empty_v3_dir");
+    key.offset = 0;
+    key.meta.expiration_time = 123456789;
+    key.meta.type = FileCacheType::NORMAL;
+    key.meta.tablet_id = 0;
+
+    auto key_dir = storage.get_path_in_local_cache_v3(key.hash);
+    auto block_path = FSFileCacheStorage::get_path_in_local_cache_v3(key_dir, 
key.offset);
+    auto old_key_dir = storage.get_path_in_local_cache_v2(key.hash, 
key.meta.expiration_time);
+    auto old_block_path =
+            FSFileCacheStorage::get_path_in_local_cache_v2(old_key_dir, 
key.offset, key.meta.type);
+    create_regular_file(block_path, 'r');
+    create_regular_file(old_block_path, 'o');
+
+    ASSERT_TRUE(storage.remove(key).ok());
+
+    EXPECT_FALSE(fs::exists(block_path));
+    EXPECT_FALSE(fs::exists(key_dir));
+    EXPECT_FALSE(fs::exists(old_block_path));
+    EXPECT_FALSE(fs::exists(old_key_dir));
+}
+
+TEST_F(FSFileCacheLeakCleanerTest, remove_v3_block_when_v2_directory_missing) {
+    auto dir = prepare_test_dir();
+    FileCacheSettings settings = default_settings();
+    BlockFileCache mgr(dir.string(), settings);
+    FSFileCacheStorage storage;
+    setup_storage(storage, mgr, dir);
+
+    FileCacheKey key;
+    key.hash = BlockFileCache::hash("remove_v3_without_v2_dir");
+    key.offset = 0;
+    key.meta.expiration_time = 123456789;
+    key.meta.type = FileCacheType::NORMAL;
+    key.meta.tablet_id = 0;
+
+    auto key_dir = storage.get_path_in_local_cache_v3(key.hash);
+    auto block_path = FSFileCacheStorage::get_path_in_local_cache_v3(key_dir, 
key.offset);
+    auto old_key_dir = storage.get_path_in_local_cache_v2(key.hash, 
key.meta.expiration_time);
+    create_regular_file(block_path, 'r');
+    ASSERT_FALSE(fs::exists(old_key_dir));
+
+    ASSERT_TRUE(storage.remove(key).ok());
+
+    EXPECT_FALSE(fs::exists(block_path));
+    EXPECT_FALSE(fs::exists(key_dir));
+    EXPECT_FALSE(fs::exists(old_key_dir));
+}
+
 TEST_F(FSFileCacheLeakCleanerTest, 
snapshot_metadata_for_hash_offsets_handles_missing_hash) {
     auto dir = prepare_test_dir();
     FileCacheSettings settings = default_settings();
diff --git a/be/test/io/fs/local_file_system_test.cpp 
b/be/test/io/fs/local_file_system_test.cpp
index 450c3daea8f..5947dffef9c 100644
--- a/be/test/io/fs/local_file_system_test.cpp
+++ b/be/test/io/fs/local_file_system_test.cpp
@@ -212,6 +212,30 @@ TEST_F(LocalFileSystemTest, Delete) {
     ASSERT_TRUE(check_exist(fmt::format("{}/dir/dir1", test_dir))); // Parent 
should exist
 }
 
+TEST_F(LocalFileSystemTest, DeleteEmptyDirectory) {
+    auto empty_dir = fmt::format("{}/empty_dir", test_dir);
+    auto st = io::global_local_filesystem()->create_directory(empty_dir);
+    ASSERT_TRUE(st.ok()) << st;
+
+    st = io::global_local_filesystem()->delete_empty_directory(empty_dir);
+    ASSERT_TRUE(st.ok()) << st;
+    ASSERT_FALSE(check_exist(empty_dir));
+    st = io::global_local_filesystem()->delete_empty_directory(empty_dir);
+    ASSERT_TRUE(st.ok()) << st;
+
+    auto non_empty_dir = fmt::format("{}/non_empty_dir", test_dir);
+    auto child_file = fmt::format("{}/child", non_empty_dir);
+    st = io::global_local_filesystem()->create_directory(non_empty_dir);
+    ASSERT_TRUE(st.ok()) << st;
+    st = save_string_file(child_file, "abc");
+    ASSERT_TRUE(st.ok()) << st;
+
+    st = io::global_local_filesystem()->delete_empty_directory(non_empty_dir);
+    ASSERT_FALSE(st.ok()) << st;
+    ASSERT_TRUE(check_exist(non_empty_dir));
+    ASSERT_TRUE(check_exist(child_file));
+}
+
 TEST_F(LocalFileSystemTest, AbnormalFileWriter) {
     auto fname = fmt::format("{}/abc", test_dir);
     {


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

Reply via email to