diff --git a/dbms/src/Storages/S3/FileCache.cpp b/dbms/src/Storages/S3/FileCache.cpp index a1acd79bc87..c810cdc7fc0 100644 --- a/dbms/src/Storages/S3/FileCache.cpp +++ b/dbms/src/Storages/S3/FileCache.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include namespace ProfileEvents { @@ -210,7 +212,7 @@ FileSegmentPtr FileCache::get(const S3::S3FilenameView & s3_fname, const std::op // We don't know the exact size of a object/file, but we need reserve space to save the object/file. // A certain amount of space is reserved for each file type. auto estimzted_size = filesize ? *filesize : getEstimatedSizeOfFileType(file_type); - if (!reserveSpaceImpl(file_type, estimzted_size, /*try_evict*/ true)) + if (!reserveSpaceImpl(file_type, estimzted_size, EvictMode::TryEvict)) { // Space not enough. GET_METRIC(tiflash_storage_remote_cache, type_dtfile_full).Increment(); @@ -258,7 +260,7 @@ FileSegmentPtr FileCache::getOrWait(const S3::S3FilenameView & s3_fname, const s GET_METRIC(tiflash_storage_remote_cache, type_dtfile_miss).Increment(); auto estimated_size = filesize ? *filesize : getEstimatedSizeOfFileType(file_type); - if (!reserveSpaceImpl(file_type, estimated_size, /*try_evict*/ true)) + if (!reserveSpaceImpl(file_type, estimated_size, EvictMode::ForceEvict)) { // Space not enough. GET_METRIC(tiflash_storage_remote_cache, type_dtfile_full).Increment(); @@ -361,7 +363,7 @@ std::pair::iterator> FileCache::removeImpl( return {release_size, table.remove(s3_key)}; } -bool FileCache::reserveSpaceImpl(FileType reserve_for, UInt64 size, bool try_evict) +bool FileCache::reserveSpaceImpl(FileType reserve_for, UInt64 size, EvictMode evict) { if (cache_used + size <= cache_capacity) { @@ -369,12 +371,17 @@ bool FileCache::reserveSpaceImpl(FileType reserve_for, UInt64 size, bool try_evi CurrentMetrics::set(CurrentMetrics::DTFileCacheUsed, cache_used); return true; } - if (try_evict) + if (evict == EvictMode::TryEvict || evict == EvictMode::ForceEvict) { UInt64 min_evict_size = size - (cache_capacity - cache_used); - LOG_DEBUG(log, "tryEvictFile for {} min_evict_size={}", magic_enum::enum_name(reserve_for), min_evict_size); - tryEvictFile(reserve_for, min_evict_size); - return reserveSpaceImpl(reserve_for, size, /*try_evict*/ false); + LOG_DEBUG( + log, + "tryEvictFile for {} min_evict_size={} evict_mode={}", + magic_enum::enum_name(reserve_for), + min_evict_size, + magic_enum::enum_name(evict)); + tryEvictFile(reserve_for, min_evict_size, evict); + return reserveSpaceImpl(reserve_for, size, EvictMode::NoEvict); } return false; } @@ -383,21 +390,27 @@ bool FileCache::reserveSpaceImpl(FileType reserve_for, UInt64 size, bool try_evi // Distinguish cache priority according to file type. The larger the file type, the lower the priority. // First, try to evict files which not be used recently with the same type. => Try to evict old files. // Second, try to evict files with lower priority. => Try to evict lower priority files. +// Finally, evict files with higher priority, if space is still not sufficient. Higher priority files +// are usually smaller. If we don't evict them, it is very possible that cache is full of these higher +// priority small files and we can't effectively cache any lower-priority large files. std::vector FileCache::getEvictFileTypes(FileType evict_for) { std::vector evict_types; evict_types.push_back(evict_for); // First, try evict with the same file type. constexpr auto all_file_types = magic_enum::enum_values(); // all_file_types are sorted by enum value. // Second, try evict from the lower proirity file type. - for (auto itr = std::rbegin(all_file_types); itr != std::rend(all_file_types) && *itr > evict_for; ++itr) + for (auto itr = std::rbegin(all_file_types); itr != std::rend(all_file_types); ++itr) { - evict_types.push_back(*itr); + if (*itr != evict_for) + evict_types.push_back(*itr); } return evict_types; } -void FileCache::tryEvictFile(FileType evict_for, UInt64 size) +void FileCache::tryEvictFile(FileType evict_for, UInt64 size, EvictMode evict) { + RUNTIME_CHECK(evict != EvictMode::NoEvict); + auto file_types = getEvictFileTypes(evict_for); for (auto evict_from : file_types) { @@ -414,9 +427,18 @@ void FileCache::tryEvictFile(FileType evict_for, UInt64 size) } else { + size = 0; break; } } + + if (size > 0 && evict == EvictMode::ForceEvict) + { + // After a series of tryEvict, the space is still not sufficient, + // so we do a force eviction. + auto evicted_size = forceEvict(size); + LOG_DEBUG(log, "forceEvict required_size={} evicted_size={}", size, evicted_size); + } } UInt64 FileCache::tryEvictFrom(FileType evict_for, UInt64 size, FileType evict_from) @@ -460,10 +482,93 @@ UInt64 FileCache::tryEvictFrom(FileType evict_for, UInt64 size, FileType evict_f return total_released_size; } -bool FileCache::reserveSpace(FileType reserve_for, UInt64 size, bool try_evict) +struct ForceEvictCandidate +{ + UInt64 file_type_slot; + String s3_key; + FileSegmentPtr file_segment; + std::chrono::time_point last_access_time; // Order by this field +}; + +struct ForceEvictCandidateComparer +{ + bool operator()(ForceEvictCandidate a, ForceEvictCandidate b) { return a.last_access_time > b.last_access_time; } +}; + +UInt64 FileCache::forceEvict(UInt64 size_to_evict) +{ + if (size_to_evict == 0) + return 0; + + // For a force evict, we simply evict from the oldest to the newest, until + // space is sufficient. + + std::priority_queue, ForceEvictCandidateComparer> + evict_candidates; + + // First, pick an item from all levels. + + size_t total_released_size = 0; + + constexpr auto all_file_types = magic_enum::enum_values(); + std::vector::iterator> each_type_lru_iters; // Stores the iterator of next candicate to add + each_type_lru_iters.reserve(all_file_types.size()); + for (const auto file_type : all_file_types) + { + auto file_type_slot = static_cast(file_type); + auto iter = tables[file_type_slot].begin(); + if (iter != tables[file_type_slot].end()) + { + const auto & s3_key = *iter; + const auto & f = tables[file_type_slot].get(s3_key, /*update_lru*/ false); + evict_candidates.emplace(ForceEvictCandidate{ + .file_type_slot = file_type_slot, + .s3_key = s3_key, + .file_segment = f, + .last_access_time = f->getLastAccessTime(), + }); + iter++; + } + each_type_lru_iters.emplace_back(iter); + } + + // Then we iterate the heap to remove the file with oldest access time. + + while (!evict_candidates.empty()) + { + auto to_evict = evict_candidates.top(); // intentionally copy + evict_candidates.pop(); + + const auto file_type_slot = to_evict.file_type_slot; + if (each_type_lru_iters[file_type_slot] != tables[file_type_slot].end()) + { + const auto s3_key = *each_type_lru_iters[file_type_slot]; + const auto & f = tables[file_type_slot].get(s3_key, /*update_lru*/ false); + evict_candidates.emplace(ForceEvictCandidate{ + .file_type_slot = file_type_slot, + .s3_key = s3_key, + .file_segment = f, + .last_access_time = f->getLastAccessTime(), + }); + each_type_lru_iters[file_type_slot]++; + } + + auto [released_size, next_itr] = removeImpl(tables[file_type_slot], to_evict.s3_key, to_evict.file_segment); + LOG_DEBUG(log, "ForceEvict {} size={}", to_evict.s3_key, released_size); + if (released_size >= 0) // removed + { + total_released_size += released_size; + if (total_released_size >= size_to_evict) + break; + } + } + return total_released_size; +} + +bool FileCache::reserveSpace(FileType reserve_for, UInt64 size, EvictMode evict) { std::lock_guard lock(mtx); - return reserveSpaceImpl(reserve_for, size, try_evict); + return reserveSpaceImpl(reserve_for, size, evict); } void FileCache::releaseSpaceImpl(UInt64 size) @@ -551,7 +656,7 @@ bool FileCache::finalizeReservedSize(FileType reserve_for, UInt64 reserved_size, if (content_length > reserved_size) { // Need more space. - return reserveSpace(reserve_for, content_length - reserved_size, /*try_evict*/ true); + return reserveSpace(reserve_for, content_length - reserved_size, EvictMode::TryEvict); } else if (content_length < reserved_size) { diff --git a/dbms/src/Storages/S3/FileCache.h b/dbms/src/Storages/S3/FileCache.h index dba2eacd66f..efb5ae2aff7 100644 --- a/dbms/src/Storages/S3/FileCache.h +++ b/dbms/src/Storages/S3/FileCache.h @@ -124,6 +124,12 @@ class FileSegment return status; } + auto getLastAccessTime() const + { + std::unique_lock lock(mtx); + return last_access_time; + } + private: mutable std::mutex mtx; const String local_fname; @@ -303,14 +309,23 @@ class FileCache static FileSegment::FileType getFileType(const String & fname); static FileSegment::FileType getFileTypeOfColData(const std::filesystem::path & p); bool canCache(FileSegment::FileType file_type) const; - bool reserveSpaceImpl(FileSegment::FileType reserve_for, UInt64 size, bool try_evict); + + enum class EvictMode + { + NoEvict, + TryEvict, + ForceEvict, + }; + + bool reserveSpaceImpl(FileSegment::FileType reserve_for, UInt64 size, EvictMode evict); void releaseSpaceImpl(UInt64 size); void releaseSpace(UInt64 size); - bool reserveSpace(FileSegment::FileType reserve_for, UInt64 size, bool try_evict); + bool reserveSpace(FileSegment::FileType reserve_for, UInt64 size, EvictMode evict); bool finalizeReservedSize(FileSegment::FileType reserve_for, UInt64 reserved_size, UInt64 content_length); static std::vector getEvictFileTypes(FileSegment::FileType evict_for); - void tryEvictFile(FileSegment::FileType evict_for, UInt64 size); + void tryEvictFile(FileSegment::FileType evict_for, UInt64 size, EvictMode evict); UInt64 tryEvictFrom(FileSegment::FileType evict_for, UInt64 size, FileSegment::FileType evict_from); + UInt64 forceEvict(UInt64 size); // This function is used for test. std::vector getAll(); diff --git a/dbms/src/Storages/S3/tests/gtest_filecache.cpp b/dbms/src/Storages/S3/tests/gtest_filecache.cpp index 187e76e43c3..567a1eb4725 100644 --- a/dbms/src/Storages/S3/tests/gtest_filecache.cpp +++ b/dbms/src/Storages/S3/tests/gtest_filecache.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include @@ -113,6 +112,14 @@ class FileCacheTest : public ::testing::Test ASSERT_EQ(r, 0); LOG_DEBUG(log, "write fname={} size={} done, cost={}s", key, size, sw.elapsedSeconds()); } + + void writeS3FileWithSize(const S3Filename & s3_dir, std::string_view file_name, size_t size) + { + std::vector data; + data.resize(size); + writeFile(fmt::format("{}/{}", s3_dir.toFullKey(), file_name), '0', size, WriteSettings{}); + } + struct ObjectInfo { String key; @@ -221,7 +228,7 @@ class FileCacheTest : public ::testing::Test String tmp_dir; UInt64 cache_capacity = 100 * 1024 * 1024; - UInt64 cache_level = 5; + const UInt64 cache_level = 5; UInt64 cache_min_age_seconds = 30 * 60; LoggerPtr log; PathCapacityMetricsPtr capacity_metrics; @@ -437,12 +444,12 @@ try ASSERT_EQ(FileCache::getFileType(unknow_fname1), FileType::Unknow); { - UInt64 cache_level_ = 0; - auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level_); + const UInt64 cache_level = 0; + auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level); StorageRemoteCacheConfig cache_config{ .dir = cache_dir, .capacity = cache_capacity, - .dtfile_level = cache_level_}; + .dtfile_level = cache_level}; FileCache file_cache(capacity_metrics, cache_config); ASSERT_FALSE(file_cache.canCache(FileType::Unknow)); ASSERT_FALSE(file_cache.canCache(FileType::Meta)); @@ -456,12 +463,12 @@ try ASSERT_FALSE(file_cache.canCache(FileType::ColData)); } { - UInt64 cache_level_ = 1; - auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level_); + const UInt64 cache_level = 1; + auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level); StorageRemoteCacheConfig cache_config{ .dir = cache_dir, .capacity = cache_capacity, - .dtfile_level = cache_level_}; + .dtfile_level = cache_level}; FileCache file_cache(capacity_metrics, cache_config); ASSERT_FALSE(file_cache.canCache(FileType::Unknow)); ASSERT_TRUE(file_cache.canCache(FileType::Meta)); @@ -475,12 +482,12 @@ try ASSERT_FALSE(file_cache.canCache(FileType::ColData)); } { - UInt64 cache_level_ = 2; - auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level_); + const UInt64 cache_level = 2; + auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level); StorageRemoteCacheConfig cache_config{ .dir = cache_dir, .capacity = cache_capacity, - .dtfile_level = cache_level_}; + .dtfile_level = cache_level}; FileCache file_cache(capacity_metrics, cache_config); ASSERT_FALSE(file_cache.canCache(FileType::Unknow)); ASSERT_TRUE(file_cache.canCache(FileType::Meta)); @@ -494,12 +501,12 @@ try ASSERT_FALSE(file_cache.canCache(FileType::ColData)); } { - UInt64 cache_level_ = 3; - auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level_); + const UInt64 cache_level = 3; + auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level); StorageRemoteCacheConfig cache_config{ .dir = cache_dir, .capacity = cache_capacity, - .dtfile_level = cache_level_}; + .dtfile_level = cache_level}; FileCache file_cache(capacity_metrics, cache_config); ASSERT_FALSE(file_cache.canCache(FileType::Unknow)); ASSERT_TRUE(file_cache.canCache(FileType::Meta)); @@ -513,12 +520,12 @@ try ASSERT_FALSE(file_cache.canCache(FileType::ColData)); } { - UInt64 cache_level_ = 4; - auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level_); + const UInt64 cache_level = 4; + auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level); StorageRemoteCacheConfig cache_config{ .dir = cache_dir, .capacity = cache_capacity, - .dtfile_level = cache_level_}; + .dtfile_level = cache_level}; FileCache file_cache(capacity_metrics, cache_config); ASSERT_FALSE(file_cache.canCache(FileType::Unknow)); ASSERT_TRUE(file_cache.canCache(FileType::Meta)); @@ -532,12 +539,12 @@ try ASSERT_FALSE(file_cache.canCache(FileType::ColData)); } { - UInt64 cache_level_ = 5; - auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level_); + const UInt64 cache_level = 5; + auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level); StorageRemoteCacheConfig cache_config{ .dir = cache_dir, .capacity = cache_capacity, - .dtfile_level = cache_level_}; + .dtfile_level = cache_level}; FileCache file_cache(capacity_metrics, cache_config); ASSERT_FALSE(file_cache.canCache(FileType::Unknow)); ASSERT_TRUE(file_cache.canCache(FileType::Meta)); @@ -551,12 +558,12 @@ try ASSERT_FALSE(file_cache.canCache(FileType::ColData)); } { - UInt64 cache_level_ = 6; - auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level_); + const UInt64 cache_level = 6; + auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level); StorageRemoteCacheConfig cache_config{ .dir = cache_dir, .capacity = cache_capacity, - .dtfile_level = cache_level_}; + .dtfile_level = cache_level}; FileCache file_cache(capacity_metrics, cache_config); ASSERT_FALSE(file_cache.canCache(FileType::Unknow)); ASSERT_TRUE(file_cache.canCache(FileType::Meta)); @@ -570,12 +577,12 @@ try ASSERT_FALSE(file_cache.canCache(FileType::ColData)); } { - UInt64 cache_level_ = 7; - auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level_); + const UInt64 cache_level = 7; + auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level); StorageRemoteCacheConfig cache_config{ .dir = cache_dir, .capacity = cache_capacity, - .dtfile_level = cache_level_}; + .dtfile_level = cache_level}; FileCache file_cache(capacity_metrics, cache_config); ASSERT_FALSE(file_cache.canCache(FileType::Unknow)); ASSERT_TRUE(file_cache.canCache(FileType::Meta)); @@ -589,12 +596,12 @@ try ASSERT_FALSE(file_cache.canCache(FileType::ColData)); } { - UInt64 cache_level_ = 8; - auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level_); + const UInt64 cache_level = 8; + auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level); StorageRemoteCacheConfig cache_config{ .dir = cache_dir, .capacity = cache_capacity, - .dtfile_level = cache_level_}; + .dtfile_level = cache_level}; FileCache file_cache(capacity_metrics, cache_config); ASSERT_FALSE(file_cache.canCache(FileType::Unknow)); ASSERT_TRUE(file_cache.canCache(FileType::Meta)); @@ -608,12 +615,12 @@ try ASSERT_FALSE(file_cache.canCache(FileType::ColData)); } { - UInt64 cache_level_ = 9; - auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level_); + const UInt64 cache_level = 9; + auto cache_dir = fmt::format("{}/filetype{}", tmp_dir, cache_level); StorageRemoteCacheConfig cache_config{ .dir = cache_dir, .capacity = cache_capacity, - .dtfile_level = cache_level_}; + .dtfile_level = cache_level}; FileCache file_cache(capacity_metrics, cache_config); ASSERT_FALSE(file_cache.canCache(FileType::Unknow)); ASSERT_TRUE(file_cache.canCache(FileType::Meta)); @@ -635,18 +642,18 @@ TEST_F(FileCacheTest, Space) StorageRemoteCacheConfig cache_config{.dir = cache_dir, .capacity = cache_capacity, .dtfile_level = cache_level}; FileCache file_cache(capacity_metrics, cache_config); auto dt_cache_capacity = cache_config.getDTFileCapacity(); - ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, dt_cache_capacity - 1024, /*try_evict*/ false)); - ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, 512, /*try_evict*/ false)); - ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, 256, /*try_evict*/ false)); - ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, 256, /*try_evict*/ false)); - ASSERT_FALSE(file_cache.reserveSpace(FileType::Meta, 1, /*try_evict*/ false)); + ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, dt_cache_capacity - 1024, FileCache::EvictMode::NoEvict)); + ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, 512, FileCache::EvictMode::NoEvict)); + ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, 256, FileCache::EvictMode::NoEvict)); + ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, 256, FileCache::EvictMode::NoEvict)); + ASSERT_FALSE(file_cache.reserveSpace(FileType::Meta, 1, FileCache::EvictMode::NoEvict)); ASSERT_FALSE(file_cache.finalizeReservedSize(FileType::Meta, /*reserved_size*/ 512, /*content_length*/ 513)); ASSERT_TRUE(file_cache.finalizeReservedSize(FileType::Meta, /*reserved_size*/ 512, /*content_length*/ 511)); - ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, 1, /*try_evict*/ false)); - ASSERT_FALSE(file_cache.reserveSpace(FileType::Meta, 1, /*try_evict*/ false)); + ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, 1, FileCache::EvictMode::NoEvict)); + ASSERT_FALSE(file_cache.reserveSpace(FileType::Meta, 1, FileCache::EvictMode::NoEvict)); file_cache.releaseSpace(dt_cache_capacity); - ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, dt_cache_capacity, /*try_evict*/ false)); - ASSERT_FALSE(file_cache.reserveSpace(FileType::Meta, 1, /*try_evict*/ false)); + ASSERT_TRUE(file_cache.reserveSpace(FileType::Meta, dt_cache_capacity, FileCache::EvictMode::NoEvict)); + ASSERT_FALSE(file_cache.reserveSpace(FileType::Meta, 1, FileCache::EvictMode::NoEvict)); } TEST_F(FileCacheTest, LRUFileTable) @@ -871,4 +878,135 @@ try } CATCH +TEST_F(FileCacheTest, ForceEvict) +try +{ + // Generate multiple files for each different file-types. + struct ObjDesc + { + String name; + size_t size; + }; + const std::vector objects = { + {.name = "1.meta", .size = 10}, + {.name = "1.idx", .size = 1}, + {.name = "2.idx", .size = 2}, + {.name = "1.mrk", .size = 3}, + {.name = "2.meta", .size = 5}, + {.name = "3.meta", .size = 20}, + {.name = "2.mrk", .size = 10}, + {.name = "4.meta", .size = 3}, + {.name = "4.idx", .size = 10}, + {.name = "4.mrk", .size = 7}, + {.name = "3.mrk", .size = 1}, + {.name = "3.idx", .size = 5}, + }; + + const auto s3_dir = S3Filename::fromTableID(0, 0, 1); + for (const auto & obj : objects) + writeS3FileWithSize(s3_dir, obj.name, obj.size); + + // Create a large enough cache + auto cache_dir = fmt::format("{}/force_evict_1", tmp_dir); + auto cache_config = StorageRemoteCacheConfig{ + .dir = cache_dir, + .capacity = 100, + .dtfile_level = 100, + .delta_rate = 0, + .reserved_rate = 0, + }; + FileCache file_cache(capacity_metrics, cache_config); + + ASSERT_EQ(file_cache.getAll().size(), 0); + + // Put everything in cache + for (const auto & obj : objects) + { + auto full_path = fmt::format("{}/{}", s3_dir.toFullKey(), obj.name); + auto s3_fname = S3FilenameView::fromKey(full_path); + auto guard = file_cache.downloadFileForLocalRead(s3_fname, obj.size); + ASSERT_NE(guard, nullptr); + } + + ASSERT_EQ(file_cache.getAll().size(), 12); + + // Ensure the LRU order is correct. + for (const auto & obj : objects) + { + auto full_path = fmt::format("{}/{}", s3_dir.toFullKey(), obj.name); + auto s3_fname = S3FilenameView::fromKey(full_path); + ASSERT_TRUE(file_cache.getOrWait(s3_fname, obj.size)); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Avoid possible same lastAccessTime. + } + + ASSERT_EQ(file_cache.getAll().size(), 12); + + auto cache_not_contains = [&](const String & file) { + const auto all = file_cache.getAll(); + for (const auto & file_seg : all) + if (file_seg->getLocalFileName().contains(file)) + return false; + return true; + }; + ASSERT_FALSE(cache_not_contains("1.meta")); + + // Now, we want space=5, should evict: + // {.name = "1.meta", .size = 10}, + auto evicted = file_cache.forceEvict(5); + ASSERT_EQ(evicted, 10); + + ASSERT_EQ(file_cache.getAll().size(), 11); + ASSERT_TRUE(cache_not_contains("1.meta")); + + // Evict 5 space again, should evict: + // {.name = "1.idx", .size = 1}, + // {.name = "2.idx", .size = 2}, + // {.name = "1.mrk", .size = 3}, + evicted = file_cache.forceEvict(5); + ASSERT_EQ(evicted, 6); + + ASSERT_EQ(file_cache.getAll().size(), 8); + ASSERT_TRUE(cache_not_contains("1.idx")); + ASSERT_TRUE(cache_not_contains("2.idx")); + ASSERT_TRUE(cache_not_contains("1.mrk")); + + // Evict 0 + evicted = file_cache.forceEvict(0); + ASSERT_EQ(evicted, 0); + + ASSERT_EQ(file_cache.getAll().size(), 8); + + // Evict 1, should evict: + // {.name = "2.meta", .size = 5}, + evicted = file_cache.forceEvict(1); + ASSERT_EQ(evicted, 5); + + ASSERT_EQ(file_cache.getAll().size(), 7); + ASSERT_TRUE(cache_not_contains("2.meta")); + + // Use get(), it should not evict anything. + { + auto full_path = fmt::format("{}/not_exist", s3_dir.toFullKey()); + ASSERT_FALSE(file_cache.get(S3FilenameView::fromKey(full_path), 999)); + ASSERT_EQ(file_cache.getAll().size(), 7); + } + + // Use getOrWait(), it should force evict everything and then fail. + { + auto full_path = fmt::format("{}/not_exist", s3_dir.toFullKey()); + try + { + file_cache.getOrWait(S3FilenameView::fromKey(full_path), 999); + FAIL(); + } + catch (Exception & e) + { + ASSERT_TRUE(e.message().contains("Cannot reserve 999 space for object")); + } + ASSERT_EQ(file_cache.getAll().size(), 0); + } +} +CATCH + + } // namespace DB::tests::S3