This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.0 by this push:
new 4aeec0a391a branch-4.0:[fix](file-cache): speed up LRU restore startup
path(pick#65174) (#66129)
4aeec0a391a is described below
commit 4aeec0a391a9aec60db6091be15c2fecfcdac0b7
Author: zhengyu <[email protected]>
AuthorDate: Tue Jul 28 23:08:51 2026 +0800
branch-4.0:[fix](file-cache): speed up LRU restore startup path(pick#65174)
(#66129)
### What problem does this PR solve?
Issue Number: N/A
Related PR: #65174
---
be/src/io/cache/block_file_cache_factory.cpp | 137 ++++++++++++++++++++++-----
be/src/io/cache/block_file_cache_factory.h | 7 ++
be/src/io/cache/cache_lru_dumper.cpp | 18 ++--
be/src/io/cache/cache_lru_dumper.h | 4 +-
be/src/runtime/exec_env_init.cpp | 28 ++----
be/test/io/cache/block_file_cache_test.cpp | 67 +++++++++++++
be/test/io/cache/cache_lru_dumper_test.cpp | 42 ++++++++
7 files changed, 251 insertions(+), 52 deletions(-)
diff --git a/be/src/io/cache/block_file_cache_factory.cpp
b/be/src/io/cache/block_file_cache_factory.cpp
index 93ec201a784..3f60612a790 100644
--- a/be/src/io/cache/block_file_cache_factory.cpp
+++ b/be/src/io/cache/block_file_cache_factory.cpp
@@ -33,7 +33,10 @@
#include <algorithm>
#include <atomic>
#include <execution>
+#include <functional>
#include <ostream>
+#include <thread>
+#include <unordered_set>
#include <utility>
#include "common/config.h"
@@ -41,6 +44,7 @@
#include "io/cache/file_cache_common.h"
#include "io/fs/local_file_system.h"
#include "runtime/exec_env.h"
+#include "runtime/thread_context.h"
#include "service/backend_options.h"
#include "util/slice.h"
#include "vec/core/block.h"
@@ -50,28 +54,16 @@ class TUniqueId;
namespace io {
-FileCacheFactory* FileCacheFactory::instance() {
- return ExecEnv::GetInstance()->file_cache_factory();
-}
+namespace {
-size_t FileCacheFactory::try_release() {
- int elements = 0;
- for (auto& cache : _caches) {
- elements += cache->try_release();
- }
- return elements;
-}
-
-size_t FileCacheFactory::try_release(const std::string& base_path) {
- auto iter = _path_to_cache.find(base_path);
- if (iter != _path_to_cache.end()) {
- return iter->second->try_release();
- }
- return 0;
-}
+struct BuiltFileCache {
+ std::string cache_base_path;
+ FileCacheSettings settings;
+ std::unique_ptr<BlockFileCache> cache;
+};
-Status FileCacheFactory::create_file_cache(const std::string& cache_base_path,
- FileCacheSettings
file_cache_settings) {
+Status build_file_cache(const std::string& cache_base_path, FileCacheSettings
file_cache_settings,
+ BuiltFileCache* built_cache) {
if (file_cache_settings.storage == "memory") {
if (cache_base_path != "memory") {
LOG(WARNING) << "memory storage must use memory path";
@@ -110,13 +102,112 @@ Status FileCacheFactory::create_file_cache(const
std::string& cache_base_path,
<< " total_size: " << file_cache_settings.capacity
<< " disk_total_size: " << disk_capacity;
}
+
auto cache = std::make_unique<BlockFileCache>(cache_base_path,
file_cache_settings);
RETURN_IF_ERROR(cache->initialize());
+ built_cache->cache_base_path = cache_base_path;
+ built_cache->settings = file_cache_settings;
+ built_cache->cache = std::move(cache);
+ return Status::OK();
+}
+
+} // namespace
+
+FileCacheFactory* FileCacheFactory::instance() {
+ return ExecEnv::GetInstance()->file_cache_factory();
+}
+
+size_t FileCacheFactory::try_release() {
+ int elements = 0;
+ for (auto& cache : _caches) {
+ elements += cache->try_release();
+ }
+ return elements;
+}
+
+size_t FileCacheFactory::try_release(const std::string& base_path) {
+ auto iter = _path_to_cache.find(base_path);
+ if (iter != _path_to_cache.end()) {
+ return iter->second->try_release();
+ }
+ return 0;
+}
+
+Status FileCacheFactory::create_file_cache(const std::string& cache_base_path,
+ FileCacheSettings
file_cache_settings) {
+ BuiltFileCache built_cache;
+ RETURN_IF_ERROR(build_file_cache(cache_base_path, file_cache_settings,
&built_cache));
+ {
+ std::lock_guard lock(_mtx);
+ _path_to_cache[built_cache.cache_base_path] = built_cache.cache.get();
+ _capacity += built_cache.settings.capacity;
+ _caches.push_back(std::move(built_cache.cache));
+ }
+
+ return Status::OK();
+}
+
+Status FileCacheFactory::create_file_caches(
+ const std::vector<CachePath>& cache_paths,
+ const std::function<bool(const std::string&, const Status&)>&
should_ignore_error) {
+ struct BuildResult {
+ std::string cache_base_path;
+ FileCacheSettings settings;
+ BuiltFileCache built_cache;
+ Status status;
+ bool skip = false;
+ };
+
+ std::vector<BuildResult> results;
+ results.reserve(cache_paths.size());
+ std::unordered_set<std::string> cache_path_set;
+ for (const auto& cache_path : cache_paths) {
+ if (cache_path_set.find(cache_path.path) != cache_path_set.end()) {
+ LOG(WARNING) << fmt::format("cache path {} is duplicate",
cache_path.path);
+ continue;
+ }
+
+ cache_path_set.emplace(cache_path.path);
+ auto& result = results.emplace_back();
+ result.cache_base_path = cache_path.path;
+ result.settings = cache_path.init_settings();
+ }
+
+ std::vector<std::thread> workers;
+ workers.reserve(results.size());
+ for (auto& result : results) {
+ auto* result_ptr = &result;
+ workers.emplace_back([result_ptr]() {
+ SCOPED_INIT_THREAD_CONTEXT();
+ result_ptr->status = build_file_cache(result_ptr->cache_base_path,
result_ptr->settings,
+ &result_ptr->built_cache);
+ });
+ }
+
+ for (auto& worker : workers) {
+ worker.join();
+ }
+
+ for (auto& result : results) {
+ if (!result.status.ok()) {
+ if (should_ignore_error &&
should_ignore_error(result.cache_base_path, result.status)) {
+ result.skip = true;
+ continue;
+ }
+ return result.status;
+ }
+ }
+
{
std::lock_guard lock(_mtx);
- _path_to_cache[cache_base_path] = cache.get();
- _caches.push_back(std::move(cache));
- _capacity += file_cache_settings.capacity;
+ for (auto& result : results) {
+ if (result.skip) {
+ continue;
+ }
+ _path_to_cache[result.built_cache.cache_base_path] =
result.built_cache.cache.get();
+ _capacity += result.built_cache.settings.capacity;
+ _caches.push_back(std::move(result.built_cache.cache));
+ }
}
return Status::OK();
diff --git a/be/src/io/cache/block_file_cache_factory.h
b/be/src/io/cache/block_file_cache_factory.h
index f2882f0a29a..6daa314b80b 100644
--- a/be/src/io/cache/block_file_cache_factory.h
+++ b/be/src/io/cache/block_file_cache_factory.h
@@ -20,6 +20,7 @@
#pragma once
+#include <functional>
#include <memory>
#include <mutex>
#include <optional>
@@ -51,6 +52,10 @@ public:
Status create_file_cache(const std::string& cache_base_path,
FileCacheSettings file_cache_settings);
+ Status create_file_caches(
+ const std::vector<CachePath>& cache_paths,
+ const std::function<bool(const std::string&, const Status&)>&
should_ignore_error);
+
Status reload_file_cache(const std::vector<CachePath>& cache_base_paths);
size_t try_release();
@@ -107,6 +112,8 @@ public:
void get_cache_stats_block(vectorized::Block* block);
+ const std::vector<std::unique_ptr<BlockFileCache>>& get_caches() const {
return _caches; }
+
FileCacheFactory() = default;
FileCacheFactory& operator=(const FileCacheFactory&) = delete;
FileCacheFactory(const FileCacheFactory&) = delete;
diff --git a/be/src/io/cache/cache_lru_dumper.cpp
b/be/src/io/cache/cache_lru_dumper.cpp
index 1e4a3c2e0ce..69ceb388027 100644
--- a/be/src/io/cache/cache_lru_dumper.cpp
+++ b/be/src/io/cache/cache_lru_dumper.cpp
@@ -393,6 +393,8 @@ Status CacheLRUDumper::parse_dump_footer(std::ifstream& in,
std::string& filenam
RETURN_IF_ERROR(check_ifstream_status(in, filename));
_parse_meta.Clear();
_current_parse_group.Clear();
+ _parse_group_index = 0;
+ _parse_entry_index = 0;
if (!_parse_meta.ParseFromString(meta_serialized)) {
std::string warn_msg = std::string(
fmt::format("LRU dump file meta parse failed, file={}, skip
restore", filename));
@@ -407,13 +409,13 @@ Status CacheLRUDumper::parse_dump_footer(std::ifstream&
in, std::string& filenam
Status CacheLRUDumper::parse_one_lru_entry(std::ifstream& in, std::string&
filename,
UInt128Wrapper& hash, size_t&
offset, size_t& size) {
- // Read next group if current is empty
- if (_current_parse_group.entries_size() == 0) {
- if (_parse_meta.group_offset_size_size() == 0) {
+ // Read next group if current group has been fully consumed.
+ if (_parse_entry_index >= _current_parse_group.entries_size()) {
+ if (_parse_group_index >= _parse_meta.group_offset_size_size()) {
return Status::EndOfFile("No more entries");
}
- auto group_info = _parse_meta.group_offset_size(0);
+ const auto& group_info =
_parse_meta.group_offset_size(_parse_group_index++);
in.seekg(group_info.offset(), std::ios::beg);
std::string group_serialized(group_info.size(), '\0');
in.read(&group_serialized[0], group_serialized.size());
@@ -431,20 +433,16 @@ Status CacheLRUDumper::parse_one_lru_entry(std::ifstream&
in, std::string& filen
LOG(WARNING) << warn_msg;
return Status::InternalError(warn_msg);
}
-
- // Remove processed group info
-
_parse_meta.mutable_group_offset_size()->erase(_parse_meta.group_offset_size().begin());
+ _parse_entry_index = 0;
}
// Get next entry from current group
VLOG_DEBUG << "After deserialization: " <<
_current_parse_group.DebugString();
- auto entry = _current_parse_group.entries(0);
+ const auto& entry = _current_parse_group.entries(_parse_entry_index++);
hash = UInt128Wrapper((static_cast<uint128_t>(entry.hash().high()) << 64)
| entry.hash().low());
offset = entry.offset();
size = entry.size();
- // Remove processed entry
-
_current_parse_group.mutable_entries()->erase(_current_parse_group.entries().begin());
return Status::OK();
}
diff --git a/be/src/io/cache/cache_lru_dumper.h
b/be/src/io/cache/cache_lru_dumper.h
index d9addff614c..d4589c09955 100644
--- a/be/src/io/cache/cache_lru_dumper.h
+++ b/be/src/io/cache/cache_lru_dumper.h
@@ -86,6 +86,8 @@ private:
// For parsing
doris::io::cache::LRUDumpEntryGroupPb _current_parse_group;
doris::io::cache::LRUDumpMetaPb _parse_meta;
+ int _parse_group_index = 0;
+ int _parse_entry_index = 0;
BlockFileCache* _mgr;
LRUQueueRecorder* _recorder;
@@ -93,4 +95,4 @@ private:
std::string _start_time;
bool _is_first_dump = true;
};
-} // namespace doris::io
\ No newline at end of file
+} // namespace doris::io
diff --git a/be/src/runtime/exec_env_init.cpp b/be/src/runtime/exec_env_init.cpp
index 1630cc50d8a..994dcbab071 100644
--- a/be/src/runtime/exec_env_init.cpp
+++ b/be/src/runtime/exec_env_init.cpp
@@ -498,7 +498,6 @@ void
ExecEnv::init_file_cache_factory(std::vector<doris::CachePath>& cache_paths
config::file_cache_each_block_size,
config::s3_write_buffer_size);
exit(-1);
}
- std::unordered_set<std::string> cache_path_set;
Status rest =
doris::parse_conf_cache_paths(doris::config::file_cache_path, cache_paths);
if (!rest) {
throw Exception(
@@ -506,23 +505,16 @@ void
ExecEnv::init_file_cache_factory(std::vector<doris::CachePath>& cache_paths
doris::config::file_cache_path,
rest.msg()));
}
- doris::Status cache_status;
- for (auto& cache_path : cache_paths) {
- if (cache_path_set.find(cache_path.path) != cache_path_set.end()) {
- LOG(WARNING) << fmt::format("cache path {} is duplicate",
cache_path.path);
- continue;
- }
-
- cache_status =
doris::io::FileCacheFactory::instance()->create_file_cache(
- cache_path.path, cache_path.init_settings());
- if (!cache_status.ok()) {
- if (!doris::config::ignore_broken_disk) {
- throw Exception(
- Status::FatalError("failed to init file cache, err:
{}", cache_status));
- }
- LOG(WARNING) << "failed to init file cache, err: " << cache_status;
- }
- cache_path_set.emplace(cache_path.path);
+ auto cache_status =
doris::io::FileCacheFactory::instance()->create_file_caches(
+ cache_paths, [](const std::string&, const Status& status) {
+ if (!doris::config::ignore_broken_disk) {
+ return false;
+ }
+ LOG(WARNING) << "failed to init file cache, err: " << status;
+ return true;
+ });
+ if (!cache_status.ok()) {
+ throw Exception(Status::FatalError("failed to init file cache, err:
{}", cache_status));
}
}
diff --git a/be/test/io/cache/block_file_cache_test.cpp
b/be/test/io/cache/block_file_cache_test.cpp
index 3f51263736e..81c58de85ec 100644
--- a/be/test/io/cache/block_file_cache_test.cpp
+++ b/be/test/io/cache/block_file_cache_test.cpp
@@ -3665,6 +3665,73 @@ TEST_F(BlockFileCacheTest, test_factory_1) {
FileCacheFactory::instance()->_capacity = 0;
}
+TEST_F(BlockFileCacheTest, create_file_caches_preserves_config_order) {
+ reset_file_cache_factory_for_test();
+ std::string cache_path2 = caches_dir / "cache2" / "";
+ std::string cache_path3 = caches_dir / "cache3" / "";
+ if (fs::exists(cache_base_path)) {
+ fs::remove_all(cache_base_path);
+ }
+ if (fs::exists(cache_path2)) {
+ fs::remove_all(cache_path2);
+ }
+ if (fs::exists(cache_path3)) {
+ fs::remove_all(cache_path3);
+ }
+ Defer cleanup {[&] {
+ reset_file_cache_factory_for_test();
+ if (fs::exists(cache_base_path)) {
+ fs::remove_all(cache_base_path);
+ }
+ if (fs::exists(cache_path2)) {
+ fs::remove_all(cache_path2);
+ }
+ if (fs::exists(cache_path3)) {
+ fs::remove_all(cache_path3);
+ }
+ }};
+
+ std::vector<CachePath> cache_paths;
+ cache_paths.emplace_back(cache_base_path, 90, 30, DEFAULT_NORMAL_PERCENT,
+ DEFAULT_DISPOSABLE_PERCENT,
DEFAULT_INDEX_PERCENT, DEFAULT_TTL_PERCENT,
+ "disk");
+ cache_paths.emplace_back(cache_path2, 120, 30, DEFAULT_NORMAL_PERCENT,
+ DEFAULT_DISPOSABLE_PERCENT,
DEFAULT_INDEX_PERCENT, DEFAULT_TTL_PERCENT,
+ "disk");
+ cache_paths.emplace_back(cache_base_path, 90, 30, DEFAULT_NORMAL_PERCENT,
+ DEFAULT_DISPOSABLE_PERCENT,
DEFAULT_INDEX_PERCENT, DEFAULT_TTL_PERCENT,
+ "disk");
+ cache_paths.emplace_back(cache_path3, 150, 30, DEFAULT_NORMAL_PERCENT,
+ DEFAULT_DISPOSABLE_PERCENT,
DEFAULT_INDEX_PERCENT, DEFAULT_TTL_PERCENT,
+ "disk");
+
+ ASSERT_TRUE(FileCacheFactory::instance()
+ ->create_file_caches(cache_paths, [](const
std::string&,
+ const Status&) {
return false; })
+ .ok());
+
+ const auto& caches = FileCacheFactory::instance()->get_caches();
+ ASSERT_EQ(caches.size(), 3);
+ EXPECT_EQ(caches[0]->get_base_path(), cache_base_path);
+ EXPECT_EQ(caches[1]->get_base_path(), cache_path2);
+ EXPECT_EQ(caches[2]->get_base_path(), cache_path3);
+
EXPECT_EQ(FileCacheFactory::instance()->get_by_path(cache_base_path)->get_base_path(),
+ cache_base_path);
+
EXPECT_EQ(FileCacheFactory::instance()->get_by_path(cache_path2)->get_base_path(),
cache_path2);
+
EXPECT_EQ(FileCacheFactory::instance()->get_by_path(cache_path3)->get_base_path(),
cache_path3);
+ EXPECT_EQ(FileCacheFactory::instance()->get_capacity(), 360);
+
+ for (const auto& cache : caches) {
+ wait_until_cache_ready(*cache);
+ }
+
+ for (int i = 0; i < 64; ++i) {
+ auto key = io::BlockFileCache::hash("factory_order_key_" +
std::to_string(i));
+ const auto expected_path = caches[KeyHash()(key) %
caches.size()]->get_base_path();
+
EXPECT_EQ(FileCacheFactory::instance()->get_by_path(key)->get_base_path(),
expected_path);
+ }
+}
+
TEST_F(BlockFileCacheTest, test_factory_2) {
if (fs::exists(cache_base_path)) {
fs::remove_all(cache_base_path);
diff --git a/be/test/io/cache/cache_lru_dumper_test.cpp
b/be/test/io/cache/cache_lru_dumper_test.cpp
index e2fdfb6a7e7..e8a6bb4e674 100644
--- a/be/test/io/cache/cache_lru_dumper_test.cpp
+++ b/be/test/io/cache/cache_lru_dumper_test.cpp
@@ -160,6 +160,48 @@ TEST_F(CacheLRUDumperTest, test_dump_and_restore_queue) {
}
}
+TEST_F(CacheLRUDumperTest,
test_parse_multiple_groups_without_mutating_repeated_fields) {
+ const auto old_tail_record_num =
config::file_cache_background_lru_dump_tail_record_num;
+ Defer defer {[old_tail_record_num] {
+ config::file_cache_background_lru_dump_tail_record_num =
old_tail_record_num;
+ }};
+ config::file_cache_background_lru_dump_tail_record_num = 10001;
+
+ LRUQueue src_queue;
+ std::string queue_name = "normal";
+ std::lock_guard<std::mutex> lock(_mutex);
+ UInt128Wrapper hash(987654321ULL);
+
+ for (size_t i = 0; i < 10001; ++i) {
+ src_queue.add(hash, i * 4096, 4096 + i, lock);
+ }
+
+ dumper->do_dump_queue(src_queue, queue_name);
+
+ std::string filename = fmt::format("{}lru_dump_{}.tail", test_dir,
queue_name);
+ std::ifstream in(filename, std::ios::binary);
+ ASSERT_TRUE(in);
+
+ size_t entry_num = 0;
+ ASSERT_TRUE(dumper->parse_dump_footer(in, filename, entry_num).ok());
+ ASSERT_EQ(entry_num, 10001);
+ ASSERT_EQ(dumper->_parse_meta.group_offset_size_size(), 2);
+
+ UInt128Wrapper parsed_hash;
+ size_t offset = 0;
+ size_t size = 0;
+ for (size_t i = 0; i < entry_num; ++i) {
+ ASSERT_TRUE(dumper->parse_one_lru_entry(in, filename, parsed_hash,
offset, size).ok());
+ EXPECT_EQ(parsed_hash, hash);
+ EXPECT_EQ(offset, i * 4096);
+ EXPECT_EQ(size, 4096 + i);
+ }
+
+ EXPECT_EQ(dumper->_parse_meta.group_offset_size_size(), 2);
+ EXPECT_EQ(dumper->_parse_group_index, 2);
+ EXPECT_EQ(dumper->_parse_entry_index, 1);
+}
+
TEST_F(CacheLRUDumperTest,
test_lru_log_record_disabled_keeps_existing_backlog) {
const auto old_tail_record_num =
config::file_cache_background_lru_dump_tail_record_num;
const auto old_queue_limit =
config::file_cache_background_lru_log_queue_max_size;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]