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 cbfb86b3585 [improvement](spill) Monitor and log inode usage during
spill gc (#67498)
cbfb86b3585 is described below
commit cbfb86b3585728538f6139c7a2b235af69375eee
Author: Jerry Hu <[email protected]>
AuthorDate: Fri Oct 9 15:34:13 2026 +0800
[improvement](spill) Monitor and log inode usage during spill gc (#67498)
### What problem does this PR solve?
Issue Number: None
Problem Summary:
Spill creates one directory per query and per operator plus one file per
part, so under high concurrency a spill disk can run out of inodes long
before it runs out of bytes. The spill capacity checks and the periodic
`spill gc time` log only report bytes, so an inode leak or a growing
`spill_gc` backlog cannot be diagnosed from the BE log or metrics.
This PR adds inode monitoring to the spill file manager:
- `LocalFileSystem::get_inode_info()` returns the total and available
inode count of the file system holding a path via `statvfs`.
- `SpillDataDir::update_capacity()` refreshes the inode statistics and
exposes them as `spill_disk_inode_total` and
`spill_disk_inode_available` metrics. A throttled warning is logged
when the inode usage reaches `storage_flood_stage_usage_percent`.
- The spill gc summary log now prints, per spill store, the inode usage,
the number of query directories still pending under `spill_gc`, the
number of directories/files deleted in this round and the number of
failed deletions. Deletion failures are no longer silently discarded
but logged with a throttled warning.
### Release note
None
### Check List (For Author)
- Test:
- Unit Test: `LocalFileSystemTest.GetInodeInfo`,
`SpillFileTest.UpdateCapacityTracksInodeUsage`,
`SpillFileTest.GCCleansUpGcRootBacklog`
- Regression test: No
- Behavior changed: No
- Does this need documentation: No
### Check List (For Reviewer who merge this PR)
- Confirm the release note
- Confirm test cases
- Confirm document
- Add branch pick label
https://claude.ai/code/session_0149x6DMH4C9RUmHLjNKDFxS
---
be/src/exec/spill/spill_file_manager.cpp | 159 +++++++++++++++++++++++--------
be/src/exec/spill/spill_file_manager.h | 22 +++++
be/src/io/fs/local_file_system.cpp | 16 ++++
be/src/io/fs/local_file_system.h | 6 ++
be/test/io/fs/local_file_system_test.cpp | 21 ++++
be/test/vec/spill/spill_file_test.cpp | 70 ++++++++++++++
6 files changed, 253 insertions(+), 41 deletions(-)
diff --git a/be/src/exec/spill/spill_file_manager.cpp
b/be/src/exec/spill/spill_file_manager.cpp
index f5629d3b501..c04cb3c3410 100644
--- a/be/src/exec/spill/spill_file_manager.cpp
+++ b/be/src/exec/spill/spill_file_manager.cpp
@@ -26,6 +26,7 @@
#include <memory>
#include <string>
#include <utility>
+#include <vector>
#include "common/logging.h"
#include "common/metrics/doris_metrics.h"
@@ -410,19 +411,21 @@ void
SpillFileManager::_retry_pending_query_spill_directories() {
}
void SpillFileManager::gc(int32_t max_work_time_ms) {
- bool exists = true;
bool has_work = false;
int64_t max_work_time_ns = max_work_time_ms * 1000L * 1000L;
MonotonicStopWatch watch;
watch.start();
+ // One summary line per spill store, printed together with the inode usage
of each store so
+ // that an inode leak or a growing gc backlog can be diagnosed from the BE
log alone.
+ std::vector<std::string> store_summaries;
Defer defer {[&]() {
if (has_work) {
std::string msg(
fmt::format("spill gc time: {}",
PrettyPrinter::print(watch.elapsed_time(),
TUnit::TIME_NS)));
msg += ", spill storage:\n";
- for (const auto& [path, store_dir] : _spill_store_map) {
- msg += " " + store_dir->debug_string();
+ for (const auto& summary : store_summaries) {
+ msg += " " + summary;
msg += "\n";
}
LOG(INFO) << msg;
@@ -430,48 +433,74 @@ void SpillFileManager::gc(int32_t max_work_time_ms) {
}};
_retry_pending_query_spill_directories();
for (const auto& [path, store_dir] : _spill_store_map) {
- std::string gc_root_dir = store_dir->get_spill_data_gc_path();
+ SpillGcStats stats;
+ _gc_spill_store(store_dir.get(), watch, max_work_time_ns, &stats);
+ has_work |= stats.has_work;
+ store_summaries.emplace_back(fmt::format(
+ "{}, gc backlog: {} query dirs, deleted this round: {} dirs,
{} files, failed: {}",
+ store_dir->debug_string(), stats.backlog_dirs,
stats.deleted_dirs,
+ stats.deleted_files, stats.failed_deletes));
+ }
+}
+
+void SpillFileManager::_gc_spill_store(SpillDataDir* store_dir, const
MonotonicStopWatch& watch,
+ int64_t max_work_time_ns, SpillGcStats*
stats) {
+ std::string gc_root_dir = store_dir->get_spill_data_gc_path();
+
+ std::error_code ec;
+ bool exists = std::filesystem::exists(gc_root_dir, ec);
+ if (ec || !exists) {
+ return;
+ }
+ // dirs of queries
+ std::vector<io::FileInfo> dirs;
+ auto st = io::global_local_filesystem()->list(gc_root_dir, false, &dirs,
&exists);
+ if (!st.ok()) {
+ return;
+ }
+
+ auto delete_entry = [&](const std::string& abs_path, bool is_file) {
+ Status delete_st = is_file ?
io::global_local_filesystem()->delete_file(abs_path)
+ :
io::global_local_filesystem()->delete_directory(abs_path);
+ if (!delete_st.ok()) {
+ ++stats->failed_deletes;
+ LOG_EVERY_T(WARNING, 60) << fmt::format("failed to delete spill gc
entry {}: {}",
+ abs_path,
delete_st.to_string());
+ return false;
+ }
+ if (is_file) {
+ ++stats->deleted_files;
+ } else {
+ ++stats->deleted_dirs;
+ }
+ return true;
+ };
- std::error_code ec;
- exists = std::filesystem::exists(gc_root_dir, ec);
- if (ec || !exists) {
+ for (const auto& dir : dirs) {
+ stats->has_work = true;
+ if (dir.is_file) {
continue;
}
- // dirs of queries
- std::vector<io::FileInfo> dirs;
- auto st = io::global_local_filesystem()->list(gc_root_dir, false,
&dirs, &exists);
+ ++stats->backlog_dirs;
+ std::string abs_dir = fmt::format("{}/{}", gc_root_dir, dir.file_name);
+ // operator spill sub dirs of a query
+ std::vector<io::FileInfo> files;
+ st = io::global_local_filesystem()->list(abs_dir, false, &files,
&exists);
if (!st.ok()) {
continue;
}
-
- for (const auto& dir : dirs) {
- has_work = true;
- if (dir.is_file) {
- continue;
- }
- std::string abs_dir = fmt::format("{}/{}", gc_root_dir,
dir.file_name);
- // operator spill sub dirs of a query
- std::vector<io::FileInfo> files;
- st = io::global_local_filesystem()->list(abs_dir, false, &files,
&exists);
- if (!st.ok()) {
- continue;
- }
- if (files.empty()) {
-
static_cast<void>(io::global_local_filesystem()->delete_directory(abs_dir));
- continue;
+ if (files.empty()) {
+ if (delete_entry(abs_dir, false)) {
+ --stats->backlog_dirs;
}
+ continue;
+ }
- for (const auto& file : files) {
- auto abs_file_path = fmt::format("{}/{}", abs_dir,
file.file_name);
- if (file.is_file) {
-
static_cast<void>(io::global_local_filesystem()->delete_file(abs_file_path));
- } else {
- static_cast<void>(
-
io::global_local_filesystem()->delete_directory(abs_file_path));
- }
- if (watch.elapsed_time() > max_work_time_ns) {
- break;
- }
+ for (const auto& file : files) {
+ auto abs_file_path = fmt::format("{}/{}", abs_dir, file.file_name);
+ delete_entry(abs_file_path, file.is_file);
+ if (watch.elapsed_time() > max_work_time_ns) {
+ break;
}
}
}
@@ -481,6 +510,8 @@ DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(spill_disk_capacity,
MetricUnit::BYTES);
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(spill_disk_limit, MetricUnit::BYTES);
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(spill_disk_avail_capacity,
MetricUnit::BYTES);
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(spill_disk_data_size, MetricUnit::BYTES);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(spill_disk_inode_total, MetricUnit::NOUNIT);
+DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(spill_disk_inode_available,
MetricUnit::NOUNIT);
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(spill_disk_has_spill_data,
MetricUnit::BYTES);
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(spill_disk_has_spill_gc_data,
MetricUnit::BYTES);
@@ -495,10 +526,27 @@ SpillDataDir::SpillDataDir(std::string path, int64_t
capacity_bytes,
INT_GAUGE_METRIC_REGISTER(spill_data_dir_metric_entity, spill_disk_limit);
INT_GAUGE_METRIC_REGISTER(spill_data_dir_metric_entity,
spill_disk_avail_capacity);
INT_GAUGE_METRIC_REGISTER(spill_data_dir_metric_entity,
spill_disk_data_size);
+ INT_GAUGE_METRIC_REGISTER(spill_data_dir_metric_entity,
spill_disk_inode_total);
+ INT_GAUGE_METRIC_REGISTER(spill_data_dir_metric_entity,
spill_disk_inode_available);
INT_GAUGE_METRIC_REGISTER(spill_data_dir_metric_entity,
spill_disk_has_spill_data);
INT_GAUGE_METRIC_REGISTER(spill_data_dir_metric_entity,
spill_disk_has_spill_gc_data);
}
+namespace {
+// Fraction of inodes in use, 0 when the file system does not report a fixed
inode count.
+double inode_usage(size_t total, size_t available) {
+ return total == 0 ? 0 : (double)(total - available) / (double)total;
+}
+
+std::string inode_debug_string(size_t total, size_t available) {
+ if (total == 0) {
+ return "inodes: unknown";
+ }
+ return fmt::format("inodes: total: {}, used: {}, available: {}, used pct:
{:.2f}%", total,
+ total - available, available, inode_usage(total,
available) * 100);
+}
+} // namespace
+
bool is_directory_empty(const std::filesystem::path& dir) {
// Spill cleanup may delete the directory while the iterator is
constructed or advanced. Treat
// that race as empty for these presence metrics.
@@ -550,6 +598,7 @@ Status SpillDataDir::update_capacity() {
&_available_bytes));
spill_disk_capacity->set_value(_disk_capacity_bytes);
spill_disk_avail_capacity->set_value(_available_bytes);
+ _update_inode_usage();
auto disk_use_max_bytes =
(int64_t)(_disk_capacity_bytes *
config::storage_flood_stage_usage_percent / 100);
bool is_percent = true;
@@ -577,6 +626,32 @@ Status SpillDataDir::update_capacity() {
return Status::OK();
}
+// Inode statistics are for monitoring only, so a failure to read them never
fails
+// update_capacity(); the previous gauge values are kept and a throttled
warning is logged.
+void SpillDataDir::_update_inode_usage() {
+ size_t inode_total = 0;
+ size_t inode_available = 0;
+ auto st = io::global_local_filesystem()->get_inode_info(_path,
&inode_total, &inode_available);
+ if (!st.ok()) {
+ LOG_EVERY_T(WARNING, 60) << fmt::format("failed to get inode info of
spill path {}: {}",
+ _path, st.to_string());
+ return;
+ }
+ spill_disk_inode_total->set_value(inode_total);
+ spill_disk_inode_available->set_value(inode_available);
+
+ if (inode_total == 0) {
+ return;
+ }
+ if (inode_usage(inode_total, inode_available) >=
+ config::storage_flood_stage_usage_percent / 100.0) {
+ LOG_EVERY_T(WARNING, 60) << fmt::format(
+ "spill disk inode usage is high, path: {}, {}. Too many spill
files or a "
+ "large spill gc backlog may exhaust inodes before disk space
runs out",
+ _path, inode_debug_string(inode_total, inode_available));
+ }
+}
+
bool SpillDataDir::_reach_disk_capacity_limit(int64_t incoming_data_size) {
double used_pct = _get_disk_usage(incoming_data_size);
int64_t left_bytes = _available_bytes - incoming_data_size;
@@ -610,12 +685,14 @@ bool SpillDataDir::reach_capacity_limit(int64_t
incoming_data_size) {
return false;
}
std::string SpillDataDir::debug_string() {
+ std::lock_guard<std::mutex> l(_mutex);
return fmt::format(
- "path: {}, capacity: {}, limit: {}, used: {}, available: "
- "{}",
- _path, PrettyPrinter::print_bytes(_disk_capacity_bytes),
+ "path: {}, capacity: {}, limit: {}, used: {}, available: {}, {}",
_path,
+ PrettyPrinter::print_bytes(_disk_capacity_bytes),
PrettyPrinter::print_bytes(_spill_data_limit_bytes),
PrettyPrinter::print_bytes(_spill_data_bytes),
- PrettyPrinter::print_bytes(_available_bytes));
+ PrettyPrinter::print_bytes(_available_bytes),
+
inode_debug_string(static_cast<size_t>(spill_disk_inode_total->value()),
+
static_cast<size_t>(spill_disk_inode_available->value())));
}
} // namespace doris
diff --git a/be/src/exec/spill/spill_file_manager.h
b/be/src/exec/spill/spill_file_manager.h
index db6f2dced91..137c4c40917 100644
--- a/be/src/exec/spill/spill_file_manager.h
+++ b/be/src/exec/spill/spill_file_manager.h
@@ -28,6 +28,7 @@
#include "common/status.h"
#include "exec/spill/spill_file.h"
#include "storage/options.h"
+#include "util/stopwatch.hpp"
#include "util/threadpool.h"
namespace doris {
@@ -85,6 +86,7 @@ public:
private:
bool _reach_disk_capacity_limit(int64_t incoming_data_size);
+ void _update_inode_usage();
double _get_disk_usage(int64_t incoming_data_size) const {
return _disk_capacity_bytes == 0
? 0
@@ -110,6 +112,12 @@ private:
IntGauge* spill_disk_limit = nullptr;
IntGauge* spill_disk_avail_capacity = nullptr;
IntGauge* spill_disk_data_size = nullptr;
+ // inode statistics of the disk of this data dir, refreshed by
update_capacity(). Spill creates
+ // one directory per query and per operator plus one file per part, so
inodes may be exhausted
+ // long before bytes are. The gauges are the only storage of these values;
the total is 0 when
+ // the file system does not report a fixed inode count.
+ IntGauge* spill_disk_inode_total = nullptr;
+ IntGauge* spill_disk_inode_available = nullptr;
// for test
IntGauge* spill_disk_has_spill_data = nullptr;
IntGauge* spill_disk_has_spill_gc_data = nullptr;
@@ -191,6 +199,16 @@ private:
std::string query_dir;
};
+ // Per-store statistics of one gc round, logged in the gc summary.
+ struct SpillGcStats {
+ bool has_work = false;
+ // Query directories still under the gc root after this round, i.e.
the gc backlog.
+ size_t backlog_dirs = 0;
+ size_t deleted_dirs = 0;
+ size_t deleted_files = 0;
+ size_t failed_deletes = 0;
+ };
+
void _init_metrics();
Status _init_spill_store_map();
void _spill_gc_thread_callback();
@@ -198,6 +216,10 @@ private:
void _retry_pending_query_spill_directories();
Status _initialize_external_spill_session(ExternalSpillSession*
spill_session);
void _release_external_spill_session(ExternalSpillSession* spill_session);
+
+ // Delete the gc backlog of one spill store until `max_work_time_ns` of
`watch` has elapsed.
+ void _gc_spill_store(SpillDataDir* store_dir, const MonotonicStopWatch&
watch,
+ int64_t max_work_time_ns, SpillGcStats* stats);
std::vector<SpillDataDir*> _get_stores_for_spill(TStorageMedium::type
storage_medium);
SpillDataDir* _get_store_for_spill();
diff --git a/be/src/io/fs/local_file_system.cpp
b/be/src/io/fs/local_file_system.cpp
index 4f9b6baf2a7..421fb71bbbd 100644
--- a/be/src/io/fs/local_file_system.cpp
+++ b/be/src/io/fs/local_file_system.cpp
@@ -24,6 +24,7 @@
#include <openssl/md5.h>
#include <sys/mman.h>
#include <sys/stat.h>
+#include <sys/statvfs.h>
#include <unistd.h>
#include <filesystem>
@@ -387,6 +388,21 @@ Status LocalFileSystem::get_space_info_impl(const Path&
path, size_t* capacity,
return Status::OK();
}
+Status LocalFileSystem::get_inode_info(const Path& path, size_t* total,
size_t* available) {
+ FILESYSTEM_M(get_inode_info_impl(path, total, available));
+}
+
+Status LocalFileSystem::get_inode_info_impl(const Path& path, size_t* total,
size_t* available) {
+ struct statvfs vfs {};
+ if (::statvfs(path.c_str(), &vfs) != 0) {
+ return localfs_error(errno,
+ fmt::format("failed to get inode info for path
{}", path.native()));
+ }
+ *total = vfs.f_files;
+ *available = vfs.f_favail;
+ return Status::OK();
+}
+
Status LocalFileSystem::copy_path(const Path& src, const Path& dest) {
FILESYSTEM_M(copy_path_impl(src, dest));
}
diff --git a/be/src/io/fs/local_file_system.h b/be/src/io/fs/local_file_system.h
index b1546bc4734..b381e960a7b 100644
--- a/be/src/io/fs/local_file_system.h
+++ b/be/src/io/fs/local_file_system.h
@@ -55,6 +55,11 @@ public:
const std::function<bool(const FileInfo&)>& cb);
// return disk available space where the given path is.
Status get_space_info(const Path& path, size_t* capacity, size_t*
available);
+ // return inode statistics of the file system where the given path is.
+ // `total` is the number of inodes in the file system and `available` is
the number of free
+ // inodes usable by unprivileged processes. Some file systems (e.g. btrfs)
allocate inodes
+ // dynamically and report `total` as 0, callers must treat that as
"unknown".
+ Status get_inode_info(const Path& path, size_t* total, size_t* available);
// Copy src path to dest path. If `src` is a directory, this method will
call recursively for each directory entry.
Status copy_path(const Path& src, const Path& dest);
// return true if parent path contain sub path
@@ -101,6 +106,7 @@ protected:
Status iterate_directory_impl(const std::string& dir,
const std::function<bool(const FileInfo&)>&
cb);
Status get_space_info_impl(const Path& path, size_t* capacity, size_t*
available);
+ Status get_inode_info_impl(const Path& path, size_t* total, size_t*
available);
Status copy_path_impl(const Path& src, const Path& dest);
Status permission_impl(const Path& file, std::filesystem::perms prms);
diff --git a/be/test/io/fs/local_file_system_test.cpp
b/be/test/io/fs/local_file_system_test.cpp
index 4321a2ae562..a3b5549f98e 100644
--- a/be/test/io/fs/local_file_system_test.cpp
+++ b/be/test/io/fs/local_file_system_test.cpp
@@ -22,6 +22,7 @@
#include <gtest/gtest-test-part.h>
#include <gtest/gtest.h>
#include <sys/stat.h>
+#include <sys/statvfs.h>
#include <sys/uio.h>
#include <unistd.h>
@@ -283,6 +284,26 @@ TEST_F(LocalFileSystemTest, Exist) {
ASSERT_TRUE(check_exist(fname));
}
+TEST_F(LocalFileSystemTest, GetInodeInfo) {
+ size_t total = 0;
+ size_t available = 0;
+ auto st = io::global_local_filesystem()->get_inode_info(test_dir, &total,
&available);
+ ASSERT_TRUE(st.ok()) << st;
+ // Some file systems (e.g. btrfs) allocate inodes dynamically and report a
total of 0, so only
+ // the relation between the two values is portable.
+ EXPECT_GE(total, available);
+
+ struct statvfs vfs {};
+ ASSERT_EQ(::statvfs(std::string(test_dir).c_str(), &vfs), 0);
+ // The free inode count changes concurrently, so only cross-check the
total.
+ EXPECT_EQ(total, vfs.f_files);
+
+ st =
io::global_local_filesystem()->get_inode_info(fmt::format("{}/not_exist",
test_dir),
+ &total, &available);
+ ASSERT_FALSE(st.ok());
+ EXPECT_TRUE(st.is<ErrorCode::NOT_FOUND>()) << st;
+}
+
TEST_F(LocalFileSystemTest, List) {
io::FileWriterPtr file_writer;
auto fname = fmt::format("{}/abc", test_dir);
diff --git a/be/test/vec/spill/spill_file_test.cpp
b/be/test/vec/spill/spill_file_test.cpp
index d3d251fe147..d826649725b 100644
--- a/be/test/vec/spill/spill_file_test.cpp
+++ b/be/test/vec/spill/spill_file_test.cpp
@@ -18,6 +18,7 @@
#include "exec/spill/spill_file.h"
#include <gtest/gtest.h>
+#include <sys/statvfs.h>
#include <algorithm>
#include <filesystem>
@@ -29,6 +30,8 @@
#include <vector>
#include "common/config.h"
+#include "common/metrics/doris_metrics.h"
+#include "common/metrics/metrics.h"
#include "core/block/block.h"
#include "core/data_type/data_type_number.h"
#include "core/data_type/data_type_string.h"
@@ -42,6 +45,7 @@
#include "runtime/exec_env.h"
#include "runtime/fragment_mgr.h"
#include "runtime/runtime_profile.h"
+#include "storage/olap_define.h"
#include "testutil/column_helper.h"
#include "testutil/mock/mock_query_context.h"
#include "testutil/mock/mock_runtime_state.h"
@@ -962,6 +966,72 @@ TEST_F(SpillFileTest, GCCleansUpFiles) {
ASSERT_FALSE(exists);
}
+TEST_F(SpillFileTest, UpdateCapacityTracksInodeUsage) {
+ auto st = _data_dir_ptr->update_capacity();
+ ASSERT_TRUE(st.ok()) << st.to_string();
+
+ struct statvfs vfs {};
+ ASSERT_EQ(::statvfs(_data_dir_ptr->path().c_str(), &vfs), 0);
+
+ auto entity = DorisMetrics::instance()->metric_registry()->get_entity(
+ "spill_data_dir." + _spill_dir, {{"path", _spill_dir + "/" +
SPILL_DIR_PREFIX}});
+ ASSERT_NE(entity, nullptr);
+ auto* inode_total =
dynamic_cast<IntGauge*>(entity->get_metric("spill_disk_inode_total"));
+ auto* inode_available =
+
dynamic_cast<IntGauge*>(entity->get_metric("spill_disk_inode_available"));
+ ASSERT_NE(inode_total, nullptr);
+ ASSERT_NE(inode_available, nullptr);
+ // The free inode count changes concurrently, so only cross-check the
total.
+ EXPECT_EQ(inode_total->value(), static_cast<int64_t>(vfs.f_files));
+ EXPECT_LE(inode_available->value(), inode_total->value());
+
+ auto debug_string = _data_dir_ptr->debug_string();
+ if (vfs.f_files == 0) {
+ EXPECT_NE(debug_string.find("inodes: unknown"), std::string::npos) <<
debug_string;
+ } else {
+ EXPECT_NE(debug_string.find(fmt::format("inodes: total: {}, used: ",
vfs.f_files)),
+ std::string::npos)
+ << debug_string;
+ EXPECT_NE(debug_string.find("used pct: "), std::string::npos) <<
debug_string;
+ }
+}
+
+TEST_F(SpillFileTest, GCCleansUpGcRootBacklog) {
+ ExecEnv::GetInstance()->spill_file_mgr()->stop();
+
+ // A query directory holding both an operator directory with a part file
and a loose file,
+ // plus an already empty query directory, mirror what init() and
SpillFile::gc() leave behind.
+ const auto gc_root = _data_dir_ptr->get_spill_data_gc_path();
+ const auto pending_query_dir =
_data_dir_ptr->get_spill_data_gc_path("pending-query");
+ const auto empty_query_dir =
_data_dir_ptr->get_spill_data_gc_path("empty-query");
+ _create_residual_file(pending_query_dir + "/sort-1-2-3/0");
+ _create_residual_file(pending_query_dir + "/loose-file");
+ auto st = io::global_local_filesystem()->create_directory(empty_query_dir,
false);
+ ASSERT_TRUE(st.ok()) << st.to_string();
+
+ ExecEnv::GetInstance()->spill_file_mgr()->gc(10000);
+
+ bool exists = false;
+ st = io::global_local_filesystem()->exists(pending_query_dir +
"/sort-1-2-3", &exists);
+ ASSERT_TRUE(st.ok()) << st.to_string();
+ EXPECT_FALSE(exists);
+ st = io::global_local_filesystem()->exists(pending_query_dir +
"/loose-file", &exists);
+ ASSERT_TRUE(st.ok()) << st.to_string();
+ EXPECT_FALSE(exists);
+ st = io::global_local_filesystem()->exists(empty_query_dir, &exists);
+ ASSERT_TRUE(st.ok()) << st.to_string();
+ EXPECT_FALSE(exists);
+
+ // The emptied query directory is removed by the next round, the gc root
itself stays.
+ ExecEnv::GetInstance()->spill_file_mgr()->gc(10000);
+ st = io::global_local_filesystem()->exists(pending_query_dir, &exists);
+ ASSERT_TRUE(st.ok()) << st.to_string();
+ EXPECT_FALSE(exists);
+ st = io::global_local_filesystem()->exists(gc_root, &exists);
+ ASSERT_TRUE(st.ok()) << st.to_string();
+ EXPECT_TRUE(exists);
+}
+
TEST_F(SpillFileTest, QueryContextDeletesEmptySpillDirectory) {
ExecEnv::GetInstance()->spill_file_mgr()->stop();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]