From 7f54fa726b8d6f2207bad8bdde4effed980e8eaa Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 14 Jun 2022 19:08:27 +0800 Subject: [PATCH 01/18] Decoupling cache functions and algorithms --- src/Common/FileSegment.h | 2 +- src/Common/IFileCachePriority.h | 92 ++++++++++++ src/Common/LRUFileCache.cpp | 169 ++++++++++++---------- src/Common/tests/gtest_lru_file_cache.cpp | 8 +- 4 files changed, 186 insertions(+), 85 deletions(-) create mode 100644 src/Common/IFileCachePriority.h diff --git a/src/Common/FileSegment.h b/src/Common/FileSegment.h index 93cbf269a8e..4404d0e14be 100644 --- a/src/Common/FileSegment.h +++ b/src/Common/FileSegment.h @@ -27,7 +27,7 @@ using FileSegments = std::list; class FileSegment : boost::noncopyable { -friend class LRUFileCache; +friend class FileCache; friend struct FileSegmentsHolder; friend class FileSegmentRangeWriter; diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h new file mode 100644 index 00000000000..35b82d61228 --- /dev/null +++ b/src/Common/IFileCachePriority.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include + +namespace DB +{ + +class IFileCachePriority; +using FileCachePriorityPtr = std::shared_ptr; + +/// IFileCachePriority is used to maintain the priority of cached data. +class IFileCachePriority +{ +public: + using Key = UInt128; + + class IIterator; + friend class IIterator; + using Iterator = std::shared_ptr; + + struct FileCacheRecord + { + Key key; + size_t offset; + size_t size; + size_t hits = 0; + + FileCacheRecord(const Key & key_, size_t offset_, size_t size_) : key(key_), offset(offset_), size(size_) { } + }; + + /// It provides an iterator to traverse the cache priority. Under normal circumstances, + /// the iterator can only return the records that have been directly swapped out. + /// For example, in the LRU algorithm, it can traverse all records, but in the LRU-K, it + /// can only traverse the records in the low priority queue. + class IIterator + { + public: + virtual ~IIterator() = default; + + virtual void next() = 0; + + virtual bool vaild() const = 0; + + /// Mark a cache record as recently used, it will update the priority + /// of the cache record according to different cache algorithms. + virtual void use(std::lock_guard & cache_lock) = 0; + + /// Deletes an existing cached record. + virtual void remove(std::lock_guard & cache_lock) = 0; + + virtual Key & key() const = 0; + + virtual size_t offset() const = 0; + + virtual size_t size() const = 0; + + virtual size_t hits() const = 0; + + virtual Iterator getSnapshot() = 0; + + virtual void incrementSize(size_t size_increment, std::lock_guard & cache_lock) = 0; + }; + +public: + virtual ~IFileCachePriority() = default; + + /// Add a cache record that did not exist before, and throw a + /// logical exception if the cache block already exists. + virtual Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) = 0; + + /// Query whether a cache record exists. If it exists, return true. If not, return false. + virtual bool contains(const Key & key, size_t offset, std::lock_guard & cache_lock) = 0; + + virtual void removeAll(std::lock_guard & cache_lock) = 0; + + /// Returns an iterator pointing to the lowest priority cached record. + /// We can traverse all cached records through the iterator's next(). + virtual Iterator getNewIterator(std::lock_guard & cache_lock) = 0; + + virtual size_t getElementsNum(std::lock_guard & cache_lock) const = 0; + + size_t getCacheSize(std::lock_guard &) const { return cache_size; } + + virtual std::string toString(std::lock_guard & cache_lock) const = 0; + +protected: + size_t max_cache_size = 0; + size_t cache_size = 0; +}; +}; diff --git a/src/Common/LRUFileCache.cpp b/src/Common/LRUFileCache.cpp index 6306b6de059..817208c6c30 100644 --- a/src/Common/LRUFileCache.cpp +++ b/src/Common/LRUFileCache.cpp @@ -24,6 +24,8 @@ namespace ErrorCodes LRUFileCache::LRUFileCache(const String & cache_base_path_, const FileCacheSettings & cache_settings_) : IFileCache(cache_base_path_, cache_settings_) + , main_priority(std::make_shared()) + , stash_priority(std::make_shared()) , max_stash_element_size(cache_settings_.max_elements) , enable_cache_hits_threshold(cache_settings_.enable_cache_hits_threshold) , log(&Poco::Logger::get("LRUFileCache")) @@ -31,7 +33,7 @@ LRUFileCache::LRUFileCache(const String & cache_base_path_, const FileCacheSetti { } -void LRUFileCache::initialize() +void FileCache::initialize() { std::lock_guard cache_lock(mutex); if (!is_initialized) @@ -55,7 +57,7 @@ void LRUFileCache::initialize() } } -void LRUFileCache::useCell( +void FileCache::useCell( const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock) { auto file_segment = cell.file_segment; @@ -75,11 +77,11 @@ void LRUFileCache::useCell( if (cell.queue_iterator) { /// Move to the end of the queue. The iterator remains valid. - queue.moveToEnd(*cell.queue_iterator, cache_lock); + cell.queue_iterator->use(cache_lock); } } -LRUFileCache::FileSegmentCell * LRUFileCache::getCell( +FileCache::FileSegmentCell * FileCache::getCell( const Key & key, size_t offset, std::lock_guard & /* cache_lock */) { auto it = files.find(key); @@ -94,7 +96,7 @@ LRUFileCache::FileSegmentCell * LRUFileCache::getCell( return &cell_it->second; } -FileSegments LRUFileCache::getImpl( +FileSegments FileCache::getImpl( const Key & key, const FileSegment::Range & range, std::lock_guard & cache_lock) { /// Given range = [left, right] and non-overlapping ordered set of file segments, @@ -145,12 +147,8 @@ FileSegments LRUFileCache::getImpl( if (range.left <= prev_cell_range.right) { - /// segment{k-1} segment{k} /// [________] [_____ /// [___________ - /// ^ - /// range.left - useCell(prev_cell, result, cache_lock); } } @@ -204,7 +202,7 @@ FileSegments LRUFileCache::splitRangeIntoCells( return file_segments; } -void LRUFileCache::fillHolesWithEmptyFileSegments( +void FileCache::fillHolesWithEmptyFileSegments( FileSegments & file_segments, const Key & key, const FileSegment::Range & range, @@ -326,7 +324,7 @@ FileSegmentsHolder LRUFileCache::getOrSet(const Key & key, size_t offset, size_t return FileSegmentsHolder(std::move(file_segments)); } -FileSegmentsHolder LRUFileCache::get(const Key & key, size_t offset, size_t size) +FileSegmentsHolder FileCache::get(const Key & key, size_t offset, size_t size) { assertInitialized(); @@ -379,20 +377,19 @@ LRUFileCache::FileSegmentCell * LRUFileCache::addCell( FileSegment::State result_state = state; if (state == FileSegment::State::EMPTY && enable_cache_hits_threshold) { - auto record = records.find({key, offset}); + auto record = stash_records.find({key, offset}); - if (record == records.end()) + if (record == stash_records.end()) { - auto queue_iter = stash_queue.add(key, offset, 0, cache_lock); - records.insert({{key, offset}, queue_iter}); + auto priority_iter = stash_priority->add(key, offset, 0, cache_lock); + stash_records.insert({{key, offset}, priority_iter}); - if (stash_queue.getElementsNum(cache_lock) > max_stash_element_size) + if (stash_priority->getElementsNum(cache_lock) > max_stash_element_size) { - auto remove_queue_iter = stash_queue.begin(); - records.erase({remove_queue_iter->key, remove_queue_iter->offset}); - stash_queue.remove(remove_queue_iter, cache_lock); + auto remove_priority_iter = stash_priority->getNewIterator(cache_lock); + stash_records.erase({remove_priority_iter->key(), remove_priority_iter->offset()}); + remove_priority_iter->remove(cache_lock); } - /// For segments that do not reach the download threshold, we do not download them, but directly read them result_state = FileSegment::State::SKIP_CACHE; } @@ -452,7 +449,7 @@ FileSegmentsHolder LRUFileCache::setDownloading( return FileSegmentsHolder(std::move(file_segments)); } -bool LRUFileCache::tryReserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) +bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) { auto query_context = enable_filesystem_query_cache_limit ? getCurrentQueryContext(cache_lock) : nullptr; if (!query_context) @@ -473,40 +470,40 @@ bool LRUFileCache::tryReserve(const Key & key, size_t offset, size_t size, std:: else { size_t removed_size = 0; - size_t queue_size = queue.getElementsNum(cache_lock); + size_t queue_size = main_priority->getElementsNum(cache_lock); auto * cell_for_reserve = getCell(key, offset, cache_lock); - std::vector ghost; + std::vector ghost; std::vector trash; std::vector to_evict; auto is_overflow = [&] { - return (max_size != 0 && queue.getTotalCacheSize(cache_lock) + size - removed_size > max_size) + return (max_size != 0 && main_priority->getCacheSize(cache_lock) + size - removed_size > max_size) || (max_element_size != 0 && queue_size > max_element_size) || (query_context->getCacheSize() + size - removed_size > query_context->getMaxCacheSize()); }; /// Select the cache from the LRU queue held by query for expulsion. - for (auto iter = query_context->queue().begin(); iter != query_context->queue().end(); iter++) + for (auto iter = query_context->getPriority()->getNewIterator(cache_lock); iter->vaild(); iter->next()) { if (!is_overflow()) break; - auto * cell = getCell(iter->key, iter->offset, cache_lock); + auto * cell = getCell(iter->key(), iter->offset(), cache_lock); if (!cell) { /// The cache corresponding to this record may be swapped out by /// other queries, so it has become invalid. - ghost.push_back(iter); - removed_size += iter->size; + ghost.push_back(iter->getSnapshot()); + removed_size += iter->size(); } else { size_t cell_size = cell->size(); - assert(iter->size == cell_size); + assert(iter->size() == cell_size); if (cell->releasable()) { @@ -548,7 +545,7 @@ bool LRUFileCache::tryReserve(const Key & key, size_t offset, size_t size, std:: } for (auto & iter : ghost) - query_context->remove(iter->key, iter->offset, iter->size, cache_lock); + query_context->remove(iter->key(), iter->offset(), iter->size(), cache_lock); if (is_overflow()) return false; @@ -557,9 +554,9 @@ bool LRUFileCache::tryReserve(const Key & key, size_t offset, size_t size, std:: { auto queue_iterator = cell_for_reserve->queue_iterator; if (queue_iterator) - queue.incrementSize(*queue_iterator, size, cache_lock); + queue_iterator->incrementSize(size, cache_lock); else - cell_for_reserve->queue_iterator = queue.add(key, offset, size, cache_lock); + cell_for_reserve->queue_iterator = main_priority->add(key, offset, size, cache_lock); } for (auto & cell : to_evict) @@ -573,11 +570,11 @@ bool LRUFileCache::tryReserve(const Key & key, size_t offset, size_t size, std:: } } -bool LRUFileCache::tryReserveForMainList( +bool FileCache::tryReserveForMainList( const Key & key, size_t offset, size_t size, QueryContextPtr query_context, std::lock_guard & cache_lock) { auto removed_size = 0; - size_t queue_size = queue.getElementsNum(cache_lock); + size_t queue_size = main_priority->getElementsNum(cache_lock); assert(queue_size <= max_element_size); /// Since space reservation is incremental, cache cell already exists if it's state is EMPTY. @@ -592,15 +589,18 @@ bool LRUFileCache::tryReserveForMainList( auto is_overflow = [&] { /// max_size == 0 means unlimited cache size, max_element_size means unlimited number of cache elements. - return (max_size != 0 && queue.getTotalCacheSize(cache_lock) + size - removed_size > max_size) + return (max_size != 0 && main_priority->getCacheSize(cache_lock) + size - removed_size > max_size) || (max_element_size != 0 && queue_size > max_element_size); }; std::vector to_evict; std::vector trash; - for (const auto & [entry_key, entry_offset, entry_size, _] : queue) + for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) { + auto entry_key = it->key(); + auto entry_offset = it->offset(); + if (!is_overflow()) break; @@ -612,7 +612,7 @@ bool LRUFileCache::tryReserveForMainList( key.toString(), offset); size_t cell_size = cell->size(); - assert(entry_size == cell_size); + assert(it->size() == cell_size); /// It is guaranteed that cell is not removed from cache as long as /// pointer to corresponding file segment is hold by any other thread. @@ -671,9 +671,9 @@ bool LRUFileCache::tryReserveForMainList( /// If queue iterator already exists, we need to update the size after each space reservation. auto queue_iterator = cell_for_reserve->queue_iterator; if (queue_iterator) - queue.incrementSize(*queue_iterator, size, cache_lock); + queue_iterator->incrementSize(size, cache_lock); else - cell_for_reserve->queue_iterator = queue.add(key, offset, size, cache_lock); + cell_for_reserve->queue_iterator = main_priority->add(key, offset, size, cache_lock); } for (auto & cell : to_evict) @@ -682,7 +682,7 @@ bool LRUFileCache::tryReserveForMainList( remove_file_segment(file_segment); } - if (queue.getTotalCacheSize(cache_lock) > (1ull << 63)) + if (main_priority->getCacheSize(cache_lock) > (1ull << 63)) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cache became inconsistent. There must be a bug"); if (query_context) @@ -751,10 +751,12 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) std::lock_guard cache_lock(mutex); - std::vector to_remove; - for (auto it = queue.begin(); it != queue.end();) + std::vector to_remove; + for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) { - const auto & [key, offset, size, _] = *it++; + auto key = it->key(); + auto offset = it->offset(); + auto * cell = getCell(key, offset, cache_lock); if (!cell) throw Exception( @@ -776,6 +778,13 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) } } + for (auto & file_segment : to_remove) + { + std::lock_guard segment_lock(file_segment->mutex); + file_segment->detach(cache_lock, segment_lock); + remove(file_segment->key(), file_segment->offset(), cache_lock, segment_lock); + } + /// Remove all access information. records.clear(); stash_queue.removeAll(cache_lock); @@ -785,7 +794,7 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) #endif } -void LRUFileCache::remove( +void FileCache::remove( Key key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) { @@ -799,7 +808,7 @@ void LRUFileCache::remove( if (cell->queue_iterator) { - queue.remove(*cell->queue_iterator, cache_lock); + cell->queue_iterator->remove(cache_lock); } auto & offsets = files[key]; @@ -831,12 +840,12 @@ void LRUFileCache::remove( } } -void LRUFileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_lock) +void FileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_lock) { Key key; UInt64 offset = 0; size_t size = 0; - std::vector>> queue_entries; + std::vector>> queue_entries; /// cache_base_path / key_prefix / key / offset @@ -888,7 +897,7 @@ void LRUFileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_l { auto * cell = addCell(key, offset, size, FileSegment::State::DOWNLOADED, is_persistent, cache_lock); if (cell) - queue_entries.emplace_back(*cell->queue_iterator, cell->file_segment); + queue_entries.emplace_back(cell->queue_iterator, cell->file_segment); } else { @@ -912,14 +921,14 @@ void LRUFileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_l if (file_segment.expired()) continue; - queue.moveToEnd(it, cache_lock); + it->use(cache_lock); } #ifndef NDEBUG assertCacheCorrectness(cache_lock); #endif } -void LRUFileCache::reduceSizeToDownloaded( +void FileCache::reduceSizeToDownloaded( const Key & key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) { @@ -952,7 +961,7 @@ void LRUFileCache::reduceSizeToDownloaded( cell->file_segment = std::make_shared(offset, downloaded_size, key, this, FileSegment::State::DOWNLOADED); } -bool LRUFileCache::isLastFileSegmentHolder( +bool FileCache::isLastFileSegmentHolder( const Key & key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) { @@ -965,7 +974,7 @@ bool LRUFileCache::isLastFileSegmentHolder( return cell->file_segment.use_count() == 2; } -FileSegments LRUFileCache::getSnapshot() const +FileSegments FileCache::getSnapshot() const { std::lock_guard cache_lock(mutex); @@ -979,7 +988,7 @@ FileSegments LRUFileCache::getSnapshot() const return file_segments; } -std::vector LRUFileCache::tryGetCachePaths(const Key & key) +std::vector FileCache::tryGetCachePaths(const Key & key) { std::lock_guard cache_lock(mutex); @@ -996,42 +1005,42 @@ std::vector LRUFileCache::tryGetCachePaths(const Key & key) return cache_paths; } -size_t LRUFileCache::getUsedCacheSize() const +size_t FileCache::getUsedCacheSize() const { std::lock_guard cache_lock(mutex); return getUsedCacheSizeUnlocked(cache_lock); } -size_t LRUFileCache::getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const +size_t FileCache::getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const { - return queue.getTotalCacheSize(cache_lock); + return main_priority->getCacheSize(cache_lock); } -size_t LRUFileCache::getAvailableCacheSize() const +size_t FileCache::getAvailableCacheSize() const { std::lock_guard cache_lock(mutex); return getAvailableCacheSizeUnlocked(cache_lock); } -size_t LRUFileCache::getAvailableCacheSizeUnlocked(std::lock_guard & cache_lock) const +size_t FileCache::getAvailableCacheSizeUnlocked(std::lock_guard & cache_lock) const { return max_size - getUsedCacheSizeUnlocked(cache_lock); } -size_t LRUFileCache::getFileSegmentsNum() const +size_t FileCache::getFileSegmentsNum() const { std::lock_guard cache_lock(mutex); return getFileSegmentsNumUnlocked(cache_lock); } -size_t LRUFileCache::getFileSegmentsNumUnlocked(std::lock_guard & cache_lock) const +size_t FileCache::getFileSegmentsNumUnlocked(std::lock_guard & cache_lock) const { - return queue.getElementsNum(cache_lock); + return main_priority->getElementsNum(cache_lock); } -LRUFileCache::FileSegmentCell::FileSegmentCell( +FileCache::FileSegmentCell::FileSegmentCell( FileSegmentPtr file_segment_, - LRUFileCache * cache, + FileCache * cache, std::lock_guard & cache_lock) : file_segment(file_segment_) { @@ -1045,7 +1054,7 @@ LRUFileCache::FileSegmentCell::FileSegmentCell( { case FileSegment::State::DOWNLOADED: { - queue_iterator = cache->queue.add(file_segment->key(), file_segment->offset(), file_segment->range().size(), cache_lock); + queue_iterator = cache->main_priority->add(file_segment->key(), file_segment->offset(), file_segment->range().size(), cache_lock); break; } case FileSegment::State::SKIP_CACHE: @@ -1133,7 +1142,7 @@ String LRUFileCache::dumpStructure(const Key & key) return dumpStructureUnlocked(key, cache_lock); } -String LRUFileCache::dumpStructureUnlocked(const Key & key, std::lock_guard & cache_lock) +String FileCache::dumpStructureUnlocked(const Key & key, std::lock_guard & cache_lock) { WriteBufferFromOwnString result; const auto & cells_by_offset = files[key]; @@ -1141,11 +1150,11 @@ String LRUFileCache::dumpStructureUnlocked(const Key & key, std::lock_guardgetInfoForLog() << "\n"; - result << "\n\nQueue: " << queue.toString(cache_lock); + result << "\n\nPriority: " << main_priority->toString(cache_lock); return result.str(); } -void LRUFileCache::assertCacheCellsCorrectness( +void FileCache::assertCacheCellsCorrectness( const FileSegmentsByOffset & cells_by_offset, [[maybe_unused]] std::lock_guard & cache_lock) { for (const auto & [_, cell] : cells_by_offset) @@ -1156,30 +1165,32 @@ void LRUFileCache::assertCacheCellsCorrectness( if (file_segment->reserved_size != 0) { assert(cell.queue_iterator); - assert(queue.contains(file_segment->key(), file_segment->offset(), cache_lock)); + assert(priority.contains(file_segment->key(), file_segment->offset(), cache_lock)); } } } -void LRUFileCache::assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock) +void FileCache::assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock) { assertCacheCellsCorrectness(files[key], cache_lock); - assertQueueCorrectness(cache_lock); + assertPriorityCorrectness(cache_lock); } -void LRUFileCache::assertCacheCorrectness(std::lock_guard & cache_lock) +void FileCache::assertCacheCorrectness(std::lock_guard & cache_lock) { for (const auto & [key, cells_by_offset] : files) assertCacheCellsCorrectness(files[key], cache_lock); - assertQueueCorrectness(cache_lock); + assertPriorityCorrectness(cache_lock); } -void LRUFileCache::assertQueueCorrectness(std::lock_guard & cache_lock) +void FileCache::assertPriorityCorrectness(std::lock_guard & cache_lock) { [[maybe_unused]] size_t total_size = 0; - for (auto it = queue.begin(); it != queue.end();) + for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) { - auto & [key, offset, size, _] = *it++; + auto key = it->key(); + auto offset = it->offset(); + auto size = it->size(); auto * cell = getCell(key, offset, cache_lock); if (!cell) @@ -1188,14 +1199,12 @@ void LRUFileCache::assertQueueCorrectness(std::lock_guard & cache_lo ErrorCodes::LOGICAL_ERROR, "Cache is in inconsistent state: LRU queue contains entries with no cache cell (assertCorrectness())"); } - assert(cell->size() == size); total_size += size; } - - assert(total_size == queue.getTotalCacheSize(cache_lock)); - assert(queue.getTotalCacheSize(cache_lock) <= max_size); - assert(queue.getElementsNum(cache_lock) <= max_element_size); + assert(total_size == main_priority->getCacheSize(cache_lock)); + assert(main_priority->getCacheSize(cache_lock) <= max_size); + assert(main_priority->getElementsNum(cache_lock) <= max_element_size); } } diff --git a/src/Common/tests/gtest_lru_file_cache.cpp b/src/Common/tests/gtest_lru_file_cache.cpp index 2f268e217df..8e7554f0418 100644 --- a/src/Common/tests/gtest_lru_file_cache.cpp +++ b/src/Common/tests/gtest_lru_file_cache.cpp @@ -85,7 +85,7 @@ void complete(const DB::FileSegmentsHolder & holder) } -TEST(LRUFileCache, get) +TEST(FileCache, get) { if (fs::exists(cache_base_path)) fs::remove_all(cache_base_path); @@ -103,7 +103,7 @@ TEST(LRUFileCache, get) DB::FileCacheSettings settings; settings.max_size = 30; settings.max_elements = 5; - auto cache = DB::LRUFileCache(cache_base_path, settings); + auto cache = DB::FileCache(cache_base_path, settings); cache.initialize(); auto key = cache.hash("key1"); @@ -479,7 +479,7 @@ TEST(LRUFileCache, get) { /// Test LRUCache::restore(). - auto cache2 = DB::LRUFileCache(cache_base_path, settings); + auto cache2 = DB::FileCache(cache_base_path, settings); cache2.initialize(); auto holder1 = cache2.getOrSet(key, 2, 28, false); /// Get [2, 29] @@ -499,7 +499,7 @@ TEST(LRUFileCache, get) auto settings2 = settings; settings2.max_file_segment_size = 10; - auto cache2 = DB::LRUFileCache(caches_dir / "cache2", settings2); + auto cache2 = DB::FileCache(caches_dir / "cache2", settings2); cache2.initialize(); auto holder1 = cache2.getOrSet(key, 0, 25, false); /// Get [0, 24] From ffaf44c1c1838fda438c4da374d2427e6576b0ff Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 14 Jun 2022 20:32:30 +0800 Subject: [PATCH 02/18] fix style --- src/Common/FileCache.h | 390 ++++++++++++++++++++++++++++++++ src/Common/IFileCachePriority.h | 2 +- src/Common/LRUFileCache.cpp | 8 +- 3 files changed, 395 insertions(+), 5 deletions(-) create mode 100644 src/Common/FileCache.h diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h new file mode 100644 index 00000000000..13bca0e2dae --- /dev/null +++ b/src/Common/FileCache.h @@ -0,0 +1,390 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FileCache_fwd.h" +#include +#include +#include +#include +#include +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +class IFileCache; +using FileCachePtr = std::shared_ptr; + +/** + * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. + */ +class IFileCache : private boost::noncopyable +{ +friend class FileSegment; +friend struct FileSegmentsHolder; +friend class FileSegmentRangeWriter; + +public: + using Key = UInt128; + using Downloader = std::unique_ptr; + + IFileCache( + const String & cache_base_path_, + const FileCacheSettings & cache_settings_); + + virtual ~IFileCache() = default; + + /// Restore cache from local filesystem. + virtual void initialize() = 0; + + virtual void remove(const Key & key) = 0; + + virtual void remove() = 0; + + static bool isReadOnly(); + + /// Cache capacity in bytes. + size_t capacity() const { return max_size; } + + static Key hash(const String & path); + + String getPathInLocalCache(const Key & key, size_t offset); + + String getPathInLocalCache(const Key & key); + + const String & getBasePath() const { return cache_base_path; } + + virtual std::vector tryGetCachePaths(const Key & key) = 0; + + /** + * Given an `offset` and `size` representing [offset, offset + size) bytes interval, + * return list of cached non-overlapping non-empty + * file segments `[segment1, ..., segmentN]` which intersect with given interval. + * + * Segments in returned list are ordered in ascending order and represent a full contiguous + * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. + * + * As long as pointers to returned file segments are hold + * it is guaranteed that these file segments are not removed from cache. + */ + virtual FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size) = 0; + + /** + * Segments in returned list are ordered in ascending order and represent a full contiguous + * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. + * + * If file segment has state EMPTY, then it is also marked as "detached". E.g. it is "detached" + * from cache (not owned by cache), and as a result will never change it's state and will be destructed + * with the destruction of the holder, while in getOrSet() EMPTY file segments can eventually change + * it's state (and become DOWNLOADED). + */ + virtual FileSegmentsHolder get(const Key & key, size_t offset, size_t size) = 0; + + virtual FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size) = 0; + + virtual FileSegments getSnapshot() const = 0; + + /// For debug. + virtual String dumpStructure(const Key & key) = 0; + + virtual size_t getUsedCacheSize() const = 0; + + virtual size_t getFileSegmentsNum() const = 0; + +protected: + String cache_base_path; + size_t max_size; + size_t max_element_size; + size_t max_file_segment_size; + + bool is_initialized = false; + + mutable std::mutex mutex; + + virtual bool tryReserve( + const Key & key, size_t offset, size_t size, + std::lock_guard & cache_lock) = 0; + + virtual void remove( + Key key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) = 0; + + virtual bool isLastFileSegmentHolder( + const Key & key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) = 0; + + /// If file segment was partially downloaded and then space reservation fails (because of no + /// space left), then update corresponding cache cell metadata (file segment size). + virtual void reduceSizeToDownloaded( + const Key & key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) = 0; + + void assertInitialized() const; + +protected: + using KeyAndOffset = std::pair; + + struct KeyAndOffsetHash + { + std::size_t operator()(const KeyAndOffset & key) const + { + return std::hash()(key.first) ^ std::hash()(key.second); + } + }; + + using FileCacheRecords = std::unordered_map; + + /// Used to track and control the cache access of each query. + /// Through it, we can realize the processing of different queries by the cache layer. + struct QueryContext + { + FileCacheRecords records; + FileCachePriorityPtr priority; + + size_t cache_size = 0; + size_t max_cache_size; + + bool skip_download_if_exceeds_query_cache; + + QueryContext(size_t max_cache_size_, bool skip_download_if_exceeds_query_cache_) + : priority(std::make_shared()) + , max_cache_size(max_cache_size_) + , skip_download_if_exceeds_query_cache(skip_download_if_exceeds_query_cache_) {} + + void remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) + { + if (cache_size < size) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Deleted cache size exceeds existing cache size"); + + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record != records.end()) + { + record->second->remove(cache_lock); + records.erase({key, offset}); + } + } + cache_size -= size; + } + + void reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) + { + if (cache_size + size > max_cache_size) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Reserved cache size exceeds the remaining cache size"); + + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record == records.end()) + { + auto queue_iter = priority->add(key, offset, 0, cache_lock); + record = records.insert({{key, offset}, queue_iter}).first; + } + record->second->incrementSize(size, cache_lock); + } + cache_size += size; + } + + void use(const Key & key, size_t offset, std::lock_guard & cache_lock) + { + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record != records.end()) + record->second->use(cache_lock); + } + } + + size_t getMaxCacheSize() { return max_cache_size; } + + size_t getCacheSize() { return cache_size; } + + FileCachePriorityPtr getPriority() { return priority; } + + bool isSkipDownloadIfExceed() { return skip_download_if_exceeds_query_cache; } + }; + + using QueryContextPtr = std::shared_ptr; + using QueryContextMap = std::unordered_map; + + QueryContextMap query_map; + + bool enable_filesystem_query_cache_limit; + + QueryContextPtr getCurrentQueryContext(std::lock_guard & cache_lock); + + QueryContextPtr getQueryContext(const String & query_id, std::lock_guard & cache_lock); + + void removeQueryContext(const String & query_id); + + QueryContextPtr getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard &); + +public: + /// Save a query context information, and adopt different cache policies + /// for different queries through the context cache layer. + struct QueryContextHolder : private boost::noncopyable + { + explicit QueryContextHolder(const String & query_id_, IFileCache * cache_, QueryContextPtr context_); + + QueryContextHolder() = default; + + ~QueryContextHolder(); + + String query_id {}; + IFileCache * cache = nullptr; + QueryContextPtr context = nullptr; + }; + + QueryContextHolder getQueryContextHolder(const String & query_id, const ReadSettings & settings); +}; + +class FileCache final : public IFileCache +{ +public: + FileCache( + const String & cache_base_path_, + const FileCacheSettings & cache_settings_); + + FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size) override; + + FileSegmentsHolder get(const Key & key, size_t offset, size_t size) override; + + FileSegments getSnapshot() const override; + + void initialize() override; + + void remove(const Key & key) override; + + void remove() override; + + std::vector tryGetCachePaths(const Key & key) override; + + size_t getUsedCacheSize() const override; + + size_t getFileSegmentsNum() const override; + +private: + struct FileSegmentCell : private boost::noncopyable + { + FileSegmentPtr file_segment; + + /// Iterator is put here on first reservation attempt, if successful. + IFileCachePriority::Iterator queue_iterator; + + /// Pointer to file segment is always hold by the cache itself. + /// Apart from pointer in cache, it can be hold by cache users, when they call + /// getorSet(), but cache users always hold it via FileSegmentsHolder. + bool releasable() const { return file_segment.unique(); } + + size_t size() const { return file_segment->reserved_size; } + + FileSegmentCell(FileSegmentPtr file_segment_, FileCache * cache, std::lock_guard & cache_lock); + + FileSegmentCell(FileSegmentCell && other) noexcept + : file_segment(std::move(other.file_segment)) + , queue_iterator(other.queue_iterator) {} + }; + + using FileSegmentsByOffset = std::map; + using CachedFiles = std::unordered_map; + + CachedFiles files; + FileCachePriorityPtr main_priority; + + FileCacheRecords stash_records; + FileCachePriorityPtr stash_priority; + + size_t max_stash_element_size; + size_t enable_cache_hits_threshold; + + Poco::Logger * log; + + FileSegments getImpl( + const Key & key, const FileSegment::Range & range, + std::lock_guard & cache_lock); + + FileSegmentCell * getCell( + const Key & key, size_t offset, std::lock_guard & cache_lock); + + FileSegmentCell * addCell( + const Key & key, size_t offset, size_t size, + FileSegment::State state, std::lock_guard & cache_lock); + + void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); + + bool tryReserve( + const Key & key, size_t offset, size_t size, + std::lock_guard & cache_lock) override; + + bool tryReserveForMainList( + const Key & key, size_t offset, size_t size, + QueryContextPtr query_context, + std::lock_guard & cache_lock); + + void remove( + Key key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) override; + + bool isLastFileSegmentHolder( + const Key & key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) override; + + void reduceSizeToDownloaded( + const Key & key, size_t offset, + std::lock_guard & cache_lock, + std::lock_guard & segment_lock) override; + + size_t getAvailableCacheSize() const; + + void loadCacheInfoIntoMemory(std::lock_guard & cache_lock); + + FileSegments splitRangeIntoCells( + const Key & key, size_t offset, size_t size, FileSegment::State state, std::lock_guard & cache_lock); + + String dumpStructureUnlocked(const Key & key_, std::lock_guard & cache_lock); + + void fillHolesWithEmptyFileSegments( + FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, std::lock_guard & cache_lock); + + FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size) override; + + size_t getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const; + + size_t getAvailableCacheSizeUnlocked(std::lock_guard & cache_lock) const; + + size_t getFileSegmentsNumUnlocked(std::lock_guard & cache_lock) const; + + void assertCacheCellsCorrectness(const FileSegmentsByOffset & cells_by_offset, std::lock_guard & cache_lock); + +public: + String dumpStructure(const Key & key_) override; + + void assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock); + + void assertCacheCorrectness(std::lock_guard & cache_lock); + + void assertPriorityCorrectness(std::lock_guard & cache_lock); +}; + +} diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 35b82d61228..a5186bbeea8 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -41,7 +41,7 @@ public: virtual void next() = 0; - virtual bool vaild() const = 0; + virtual bool valid() const = 0; /// Mark a cache record as recently used, it will update the priority /// of the cache record according to different cache algorithms. diff --git a/src/Common/LRUFileCache.cpp b/src/Common/LRUFileCache.cpp index 817208c6c30..54b07a81afe 100644 --- a/src/Common/LRUFileCache.cpp +++ b/src/Common/LRUFileCache.cpp @@ -486,7 +486,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc }; /// Select the cache from the LRU queue held by query for expulsion. - for (auto iter = query_context->getPriority()->getNewIterator(cache_lock); iter->vaild(); iter->next()) + for (auto iter = query_context->getPriority()->getNewIterator(cache_lock); iter->valid(); iter->next()) { if (!is_overflow()) break; @@ -596,7 +596,7 @@ bool FileCache::tryReserveForMainList( std::vector to_evict; std::vector trash; - for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) + for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) { auto entry_key = it->key(); auto entry_offset = it->offset(); @@ -752,7 +752,7 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) std::lock_guard cache_lock(mutex); std::vector to_remove; - for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) + for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) { auto key = it->key(); auto offset = it->offset(); @@ -1186,7 +1186,7 @@ void FileCache::assertCacheCorrectness(std::lock_guard & cache_lock) void FileCache::assertPriorityCorrectness(std::lock_guard & cache_lock) { [[maybe_unused]] size_t total_size = 0; - for (auto it = main_priority->getNewIterator(cache_lock); it->vaild(); it->next()) + for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) { auto key = it->key(); auto offset = it->offset(); From 43cf7716574c5d4c99a16433d18143928a480d25 Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 14 Jun 2022 21:17:52 +0800 Subject: [PATCH 03/18] better --- src/Common/IFileCachePriority.h | 30 ++++++++++++++++++++++-------- src/Common/LRUFileCache.cpp | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index a5186bbeea8..677ccd76934 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -7,6 +7,11 @@ namespace DB { +namespace ErrorCodes +{ + extern const int NOT_IMPLEMENTED; +} + class IFileCachePriority; using FileCachePriorityPtr = std::shared_ptr; @@ -39,16 +44,29 @@ public: public: virtual ~IIterator() = default; - virtual void next() = 0; + virtual void next() { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support next() for IIterator."); } - virtual bool valid() const = 0; + virtual bool valid() const { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support valid() for IIterator."); } /// Mark a cache record as recently used, it will update the priority /// of the cache record according to different cache algorithms. - virtual void use(std::lock_guard & cache_lock) = 0; + virtual void use(std::lock_guard &) + { + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support use() for IIterator."); + } /// Deletes an existing cached record. - virtual void remove(std::lock_guard & cache_lock) = 0; + virtual void remove(std::lock_guard &) + { + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support remove() for IIterator."); + } + + virtual Iterator getSnapshot() { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support getSnapshot() for IIterator."); } + + virtual void incrementSize(size_t, std::lock_guard &) + { + throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support incrementSize() for IIterator."); + } virtual Key & key() const = 0; @@ -57,10 +75,6 @@ public: virtual size_t size() const = 0; virtual size_t hits() const = 0; - - virtual Iterator getSnapshot() = 0; - - virtual void incrementSize(size_t size_increment, std::lock_guard & cache_lock) = 0; }; public: diff --git a/src/Common/LRUFileCache.cpp b/src/Common/LRUFileCache.cpp index 54b07a81afe..1a9924ba332 100644 --- a/src/Common/LRUFileCache.cpp +++ b/src/Common/LRUFileCache.cpp @@ -1165,7 +1165,7 @@ void FileCache::assertCacheCellsCorrectness( if (file_segment->reserved_size != 0) { assert(cell.queue_iterator); - assert(priority.contains(file_segment->key(), file_segment->offset(), cache_lock)); + assert(main_priority->contains(file_segment->key(), file_segment->offset(), cache_lock)); } } } From c5f90225103b521b8c2dafb68c50813657d6c65f Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Sun, 26 Jun 2022 03:05:54 +0800 Subject: [PATCH 04/18] fix --- .../{LRUFileCache.cpp => FileCache.cpp} | 99 ++----- src/Common/FileCache.h | 267 ++---------------- src/Common/FileCacheFactory.cpp | 4 +- src/Common/IFileCache.cpp | 8 +- src/Common/IFileCache.h | 63 +---- src/Common/IFileCachePriority.h | 14 +- src/Common/LRUFileCache.h | 202 +++++-------- src/Common/tests/gtest_lru_file_cache.cpp | 2 +- 8 files changed, 131 insertions(+), 528 deletions(-) rename src/Common/{LRUFileCache.cpp => FileCache.cpp} (92%) diff --git a/src/Common/LRUFileCache.cpp b/src/Common/FileCache.cpp similarity index 92% rename from src/Common/LRUFileCache.cpp rename to src/Common/FileCache.cpp index 1a9924ba332..56eb4c9e081 100644 --- a/src/Common/LRUFileCache.cpp +++ b/src/Common/FileCache.cpp @@ -1,4 +1,4 @@ -#include "LRUFileCache.h" +#include "FileCache.h" #include #include @@ -11,6 +11,7 @@ #include #include #include +#include namespace fs = std::filesystem; @@ -22,13 +23,13 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -LRUFileCache::LRUFileCache(const String & cache_base_path_, const FileCacheSettings & cache_settings_) +FileCache::FileCache(const String & cache_base_path_, const FileCacheSettings & cache_settings_) : IFileCache(cache_base_path_, cache_settings_) , main_priority(std::make_shared()) , stash_priority(std::make_shared()) , max_stash_element_size(cache_settings_.max_elements) , enable_cache_hits_threshold(cache_settings_.enable_cache_hits_threshold) - , log(&Poco::Logger::get("LRUFileCache")) + , log(&Poco::Logger::get("FileCache")) , allow_to_remove_persistent_segments_from_cache_by_default(cache_settings_.allow_to_remove_persistent_segments_from_cache_by_default) { } @@ -173,7 +174,7 @@ FileSegments FileCache::getImpl( return result; } -FileSegments LRUFileCache::splitRangeIntoCells( +FileSegments FileCache::splitRangeIntoCells( const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock) { assert(size > 0); @@ -296,7 +297,7 @@ void FileCache::fillHolesWithEmptyFileSegments( } } -FileSegmentsHolder LRUFileCache::getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) +FileSegmentsHolder FileCache::getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) { assertInitialized(); @@ -356,7 +357,7 @@ FileSegmentsHolder FileCache::get(const Key & key, size_t offset, size_t size) return FileSegmentsHolder(std::move(file_segments)); } -LRUFileCache::FileSegmentCell * LRUFileCache::addCell( +FileCache::FileSegmentCell * FileCache::addCell( const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock) @@ -395,11 +396,9 @@ LRUFileCache::FileSegmentCell * LRUFileCache::addCell( } else { - auto queue_iter = record->second; - queue_iter->hits++; - stash_queue.moveToEnd(queue_iter, cache_lock); - - result_state = queue_iter->hits >= enable_cache_hits_threshold ? FileSegment::State::EMPTY : FileSegment::State::SKIP_CACHE; + auto priority_iter = record->second; + priority_iter->use(cache_lock); + result_state = priority_iter->hits() >= enable_cache_hits_threshold ? FileSegment::State::EMPTY : FileSegment::State::SKIP_CACHE; } } @@ -426,7 +425,7 @@ LRUFileCache::FileSegmentCell * LRUFileCache::addCell( return &(it->second); } -FileSegmentsHolder LRUFileCache::setDownloading( +FileSegmentsHolder FileCache::setDownloading( const Key & key, size_t offset, size_t size, @@ -691,7 +690,7 @@ bool FileCache::tryReserveForMainList( return true; } -void LRUFileCache::removeIfExists(const Key & key) +void FileCache::removeIfExists(const Key & key) { assertInitialized(); @@ -742,7 +741,7 @@ void LRUFileCache::removeIfExists(const Key & key) } } -void LRUFileCache::removeIfReleasable(bool remove_persistent_files) +void FileCache::removeIfReleasable(bool remove_persistent_files) { /// Try remove all cached files by cache_base_path. /// Only releasable file segments are evicted. @@ -786,8 +785,8 @@ void LRUFileCache::removeIfReleasable(bool remove_persistent_files) } /// Remove all access information. - records.clear(); - stash_queue.removeAll(cache_lock); + stash_records.clear(); + stash_priority->removeAll(cache_lock); #ifndef NDEBUG assertCacheCorrectness(cache_lock); @@ -1070,73 +1069,7 @@ FileCache::FileSegmentCell::FileSegmentCell( } } -IFileCache::LRUQueue::Iterator IFileCache::LRUQueue::add( - const IFileCache::Key & key, size_t offset, size_t size, std::lock_guard & /* cache_lock */) -{ -#ifndef NDEBUG - for (const auto & [entry_key, entry_offset, entry_size, entry_hits] : queue) - { - if (entry_key == key && entry_offset == offset) - throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Attempt to add duplicate queue entry to queue. (Key: {}, offset: {}, size: {})", - key.toString(), offset, size); - } -#endif - - cache_size += size; - return queue.insert(queue.end(), FileKeyAndOffset(key, offset, size)); -} - -void IFileCache::LRUQueue::remove(Iterator queue_it, std::lock_guard & /* cache_lock */) -{ - cache_size -= queue_it->size; - queue.erase(queue_it); -} - -void IFileCache::LRUQueue::removeAll(std::lock_guard & /* cache_lock */) -{ - queue.clear(); - cache_size = 0; -} - -void IFileCache::LRUQueue::moveToEnd(Iterator queue_it, std::lock_guard & /* cache_lock */) -{ - queue.splice(queue.end(), queue, queue_it); -} - -void IFileCache::LRUQueue::incrementSize(Iterator queue_it, size_t size_increment, std::lock_guard & /* cache_lock */) -{ - cache_size += size_increment; - queue_it->size += size_increment; -} - -bool IFileCache::LRUQueue::contains( - const IFileCache::Key & key, size_t offset, std::lock_guard & /* cache_lock */) const -{ - /// This method is used for assertions in debug mode. - /// So we do not care about complexity here. - for (const auto & [entry_key, entry_offset, size, _] : queue) - { - if (key == entry_key && offset == entry_offset) - return true; - } - return false; -} - -String IFileCache::LRUQueue::toString(std::lock_guard & /* cache_lock */) const -{ - String result; - for (const auto & [key, offset, size, _] : queue) - { - if (!result.empty()) - result += ", "; - result += fmt::format("{}: [{}, {}]", key.toString(), offset, offset + size - 1); - } - return result; -} - -String LRUFileCache::dumpStructure(const Key & key) +String FileCache::dumpStructure(const Key & key) { std::lock_guard cache_lock(mutex); return dumpStructureUnlocked(key, cache_lock); diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index 13bca0e2dae..5aa32ac94ca 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -11,252 +11,18 @@ #include #include -#include "FileCache_fwd.h" -#include #include #include -#include -#include -#include +#include + namespace DB { -namespace ErrorCodes -{ - extern const int LOGICAL_ERROR; -} - -class IFileCache; -using FileCachePtr = std::shared_ptr; - /** * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. + * Implements LRU eviction policy. */ -class IFileCache : private boost::noncopyable -{ -friend class FileSegment; -friend struct FileSegmentsHolder; -friend class FileSegmentRangeWriter; - -public: - using Key = UInt128; - using Downloader = std::unique_ptr; - - IFileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_); - - virtual ~IFileCache() = default; - - /// Restore cache from local filesystem. - virtual void initialize() = 0; - - virtual void remove(const Key & key) = 0; - - virtual void remove() = 0; - - static bool isReadOnly(); - - /// Cache capacity in bytes. - size_t capacity() const { return max_size; } - - static Key hash(const String & path); - - String getPathInLocalCache(const Key & key, size_t offset); - - String getPathInLocalCache(const Key & key); - - const String & getBasePath() const { return cache_base_path; } - - virtual std::vector tryGetCachePaths(const Key & key) = 0; - - /** - * Given an `offset` and `size` representing [offset, offset + size) bytes interval, - * return list of cached non-overlapping non-empty - * file segments `[segment1, ..., segmentN]` which intersect with given interval. - * - * Segments in returned list are ordered in ascending order and represent a full contiguous - * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. - * - * As long as pointers to returned file segments are hold - * it is guaranteed that these file segments are not removed from cache. - */ - virtual FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size) = 0; - - /** - * Segments in returned list are ordered in ascending order and represent a full contiguous - * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. - * - * If file segment has state EMPTY, then it is also marked as "detached". E.g. it is "detached" - * from cache (not owned by cache), and as a result will never change it's state and will be destructed - * with the destruction of the holder, while in getOrSet() EMPTY file segments can eventually change - * it's state (and become DOWNLOADED). - */ - virtual FileSegmentsHolder get(const Key & key, size_t offset, size_t size) = 0; - - virtual FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size) = 0; - - virtual FileSegments getSnapshot() const = 0; - - /// For debug. - virtual String dumpStructure(const Key & key) = 0; - - virtual size_t getUsedCacheSize() const = 0; - - virtual size_t getFileSegmentsNum() const = 0; - -protected: - String cache_base_path; - size_t max_size; - size_t max_element_size; - size_t max_file_segment_size; - - bool is_initialized = false; - - mutable std::mutex mutex; - - virtual bool tryReserve( - const Key & key, size_t offset, size_t size, - std::lock_guard & cache_lock) = 0; - - virtual void remove( - Key key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - virtual bool isLastFileSegmentHolder( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - /// If file segment was partially downloaded and then space reservation fails (because of no - /// space left), then update corresponding cache cell metadata (file segment size). - virtual void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - void assertInitialized() const; - -protected: - using KeyAndOffset = std::pair; - - struct KeyAndOffsetHash - { - std::size_t operator()(const KeyAndOffset & key) const - { - return std::hash()(key.first) ^ std::hash()(key.second); - } - }; - - using FileCacheRecords = std::unordered_map; - - /// Used to track and control the cache access of each query. - /// Through it, we can realize the processing of different queries by the cache layer. - struct QueryContext - { - FileCacheRecords records; - FileCachePriorityPtr priority; - - size_t cache_size = 0; - size_t max_cache_size; - - bool skip_download_if_exceeds_query_cache; - - QueryContext(size_t max_cache_size_, bool skip_download_if_exceeds_query_cache_) - : priority(std::make_shared()) - , max_cache_size(max_cache_size_) - , skip_download_if_exceeds_query_cache(skip_download_if_exceeds_query_cache_) {} - - void remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) - { - if (cache_size < size) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Deleted cache size exceeds existing cache size"); - - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record != records.end()) - { - record->second->remove(cache_lock); - records.erase({key, offset}); - } - } - cache_size -= size; - } - - void reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) - { - if (cache_size + size > max_cache_size) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Reserved cache size exceeds the remaining cache size"); - - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record == records.end()) - { - auto queue_iter = priority->add(key, offset, 0, cache_lock); - record = records.insert({{key, offset}, queue_iter}).first; - } - record->second->incrementSize(size, cache_lock); - } - cache_size += size; - } - - void use(const Key & key, size_t offset, std::lock_guard & cache_lock) - { - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record != records.end()) - record->second->use(cache_lock); - } - } - - size_t getMaxCacheSize() { return max_cache_size; } - - size_t getCacheSize() { return cache_size; } - - FileCachePriorityPtr getPriority() { return priority; } - - bool isSkipDownloadIfExceed() { return skip_download_if_exceeds_query_cache; } - }; - - using QueryContextPtr = std::shared_ptr; - using QueryContextMap = std::unordered_map; - - QueryContextMap query_map; - - bool enable_filesystem_query_cache_limit; - - QueryContextPtr getCurrentQueryContext(std::lock_guard & cache_lock); - - QueryContextPtr getQueryContext(const String & query_id, std::lock_guard & cache_lock); - - void removeQueryContext(const String & query_id); - - QueryContextPtr getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard &); - -public: - /// Save a query context information, and adopt different cache policies - /// for different queries through the context cache layer. - struct QueryContextHolder : private boost::noncopyable - { - explicit QueryContextHolder(const String & query_id_, IFileCache * cache_, QueryContextPtr context_); - - QueryContextHolder() = default; - - ~QueryContextHolder(); - - String query_id {}; - IFileCache * cache = nullptr; - QueryContextPtr context = nullptr; - }; - - QueryContextHolder getQueryContextHolder(const String & query_id, const ReadSettings & settings); -}; - class FileCache final : public IFileCache { public: @@ -264,7 +30,7 @@ public: const String & cache_base_path_, const FileCacheSettings & cache_settings_); - FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size) override; + FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) override; FileSegmentsHolder get(const Key & key, size_t offset, size_t size) override; @@ -272,9 +38,9 @@ public: void initialize() override; - void remove(const Key & key) override; + void removeIfExists(const Key & key) override; - void remove() override; + void removeIfReleasable(bool remove_persistent_files) override; std::vector tryGetCachePaths(const Key & key) override; @@ -293,7 +59,7 @@ private: /// Pointer to file segment is always hold by the cache itself. /// Apart from pointer in cache, it can be hold by cache users, when they call /// getorSet(), but cache users always hold it via FileSegmentsHolder. - bool releasable() const { return file_segment.unique(); } + bool releasable() const {return file_segment.unique(); } size_t size() const { return file_segment->reserved_size; } @@ -317,6 +83,7 @@ private: size_t enable_cache_hits_threshold; Poco::Logger * log; + bool allow_to_remove_persistent_segments_from_cache_by_default; FileSegments getImpl( const Key & key, const FileSegment::Range & range, @@ -327,7 +94,8 @@ private: FileSegmentCell * addCell( const Key & key, size_t offset, size_t size, - FileSegment::State state, std::lock_guard & cache_lock); + FileSegment::State state, bool is_persistent, + std::lock_guard & cache_lock); void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); @@ -350,24 +118,19 @@ private: std::lock_guard & cache_lock, std::lock_guard & segment_lock) override; - void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; - size_t getAvailableCacheSize() const; void loadCacheInfoIntoMemory(std::lock_guard & cache_lock); FileSegments splitRangeIntoCells( - const Key & key, size_t offset, size_t size, FileSegment::State state, std::lock_guard & cache_lock); + const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock); String dumpStructureUnlocked(const Key & key_, std::lock_guard & cache_lock); void fillHolesWithEmptyFileSegments( - FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, std::lock_guard & cache_lock); + FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, bool is_persistent, std::lock_guard & cache_lock); - FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size) override; + FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent) override; size_t getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const; @@ -377,6 +140,10 @@ private: void assertCacheCellsCorrectness(const FileSegmentsByOffset & cells_by_offset, std::lock_guard & cache_lock); + void reduceSizeToDownloaded( + const Key & key, size_t offset, + std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) override; + public: String dumpStructure(const Key & key_) override; diff --git a/src/Common/FileCacheFactory.cpp b/src/Common/FileCacheFactory.cpp index 259c1d3f48e..b276760c0dd 100644 --- a/src/Common/FileCacheFactory.cpp +++ b/src/Common/FileCacheFactory.cpp @@ -1,5 +1,5 @@ #include "FileCacheFactory.h" -#include "LRUFileCache.h" +#include "FileCache.h" namespace DB { @@ -53,7 +53,7 @@ FileCachePtr FileCacheFactory::getOrCreate( return it->second->cache; } - auto cache = std::make_shared(cache_base_path, file_cache_settings); + auto cache = std::make_shared(cache_base_path, file_cache_settings); FileCacheData result{cache, file_cache_settings}; auto cache_it = caches.insert(caches.end(), std::move(result)); diff --git a/src/Common/IFileCache.cpp b/src/Common/IFileCache.cpp index 8fe434dd740..e3ed82d7b62 100644 --- a/src/Common/IFileCache.cpp +++ b/src/Common/IFileCache.cpp @@ -140,7 +140,7 @@ void IFileCache::QueryContext::remove(const Key & key, size_t offset, size_t siz auto record = records.find({key, offset}); if (record != records.end()) { - lru_queue.remove(record->second, cache_lock); + record->second->remove(cache_lock); records.erase({key, offset}); } } @@ -162,10 +162,10 @@ void IFileCache::QueryContext::reserve(const Key & key, size_t offset, size_t si auto record = records.find({key, offset}); if (record == records.end()) { - auto queue_iter = lru_queue.add(key, offset, 0, cache_lock); + auto queue_iter = priority->add(key, offset, 0, cache_lock); record = records.insert({{key, offset}, queue_iter}).first; } - record->second->size += size; + record->second->incrementSize(size, cache_lock); } cache_size += size; } @@ -177,7 +177,7 @@ void IFileCache::QueryContext::use(const Key & key, size_t offset, std::lock_gua auto record = records.find({key, offset}); if (record != records.end()) - lru_queue.moveToEnd(record->second, cache_lock); + record->second->use(cache_lock); } IFileCache::QueryContextHolder::QueryContextHolder( diff --git a/src/Common/IFileCache.h b/src/Common/IFileCache.h index c820d18cb95..f46a83d52cf 100644 --- a/src/Common/IFileCache.h +++ b/src/Common/IFileCache.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -28,16 +29,7 @@ friend struct FileSegmentsHolder; friend class FileSegmentRangeWriter; public: - struct Key - { - UInt128 key; - String toString() const; - - Key() = default; - explicit Key(const UInt128 & key_) : key(key_) {} - - bool operator==(const Key & other) const { return key == other.key; } - }; + using Key = IFileCachePriority::Key; IFileCache( const String & cache_base_path_, @@ -133,49 +125,6 @@ protected: void assertInitialized() const; - class LRUQueue - { - public: - struct FileKeyAndOffset - { - Key key; - size_t offset; - size_t size; - size_t hits = 0; - - FileKeyAndOffset(const Key & key_, size_t offset_, size_t size_) : key(key_), offset(offset_), size(size_) {} - }; - - using Iterator = typename std::list::iterator; - - size_t getTotalCacheSize(std::lock_guard & /* cache_lock */) const { return cache_size; } - - size_t getElementsNum(std::lock_guard & /* cache_lock */) const { return queue.size(); } - - Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); - - void remove(Iterator queue_it, std::lock_guard & cache_lock); - - void moveToEnd(Iterator queue_it, std::lock_guard & cache_lock); - - /// Space reservation for a file segment is incremental, so we need to be able to increment size of the queue entry. - void incrementSize(Iterator queue_it, size_t size_increment, std::lock_guard & cache_lock); - - String toString(std::lock_guard & cache_lock) const; - - bool contains(const Key & key, size_t offset, std::lock_guard & cache_lock) const; - - Iterator begin() { return queue.begin(); } - - Iterator end() { return queue.end(); } - - void removeAll(std::lock_guard & cache_lock); - - private: - std::list queue; - size_t cache_size = 0; - }; - using AccessKeyAndOffset = std::pair; struct KeyAndOffsetHash { @@ -185,14 +134,14 @@ protected: } }; - using AccessRecord = std::unordered_map; + using FileCacheRecords = std::unordered_map; /// Used to track and control the cache access of each query. /// Through it, we can realize the processing of different queries by the cache layer. struct QueryContext { - LRUQueue lru_queue; - AccessRecord records; + FileCacheRecords records; + FileCachePriorityPtr priority; size_t cache_size = 0; size_t max_cache_size; @@ -213,7 +162,7 @@ protected: size_t getCacheSize() const { return cache_size; } - LRUQueue & queue() { return lru_queue; } + FileCachePriorityPtr getPriority() { return priority; } bool isSkipDownloadIfExceed() const { return skip_download_if_exceeds_query_cache; } }; diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 677ccd76934..a29d66c70be 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include namespace DB { @@ -19,7 +22,16 @@ using FileCachePriorityPtr = std::shared_ptr; class IFileCachePriority { public: - using Key = UInt128; + struct Key + { + UInt128 key; + String toString() const; + + Key() = default; + explicit Key(const UInt128 & key_) : key(key_) {} + + bool operator==(const Key & other) const { return key == other.key; } + }; class IIterator; friend class IIterator; diff --git a/src/Common/LRUFileCache.h b/src/Common/LRUFileCache.h index 059fc0c22c9..0bd87b2b38c 100644 --- a/src/Common/LRUFileCache.h +++ b/src/Common/LRUFileCache.h @@ -1,157 +1,99 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - +#include namespace DB { -/** - * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. - * Implements LRU eviction policy. - */ -class LRUFileCache final : public IFileCache +class LRUFileCache : public IFileCachePriority { public: - LRUFileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_); + using LRUQueue = std::list; + using LRUQueueIterator = typename LRUQueue::iterator; + class WriteableIterator; - FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) override; - - FileSegmentsHolder get(const Key & key, size_t offset, size_t size) override; - - FileSegments getSnapshot() const override; - - void initialize() override; - - void removeIfExists(const Key & key) override; - - void removeIfReleasable(bool remove_persistent_files) override; - - std::vector tryGetCachePaths(const Key & key) override; - - size_t getUsedCacheSize() const override; - - size_t getFileSegmentsNum() const override; - -private: - struct FileSegmentCell : private boost::noncopyable + class ReadableIterator : public IIterator { - FileSegmentPtr file_segment; + public: + ReadableIterator(LRUFileCache * file_cache_, LRUQueueIterator queue_iter_) : file_cache(file_cache_), queue_iter(queue_iter_) { } - /// Iterator is put here on first reservation attempt, if successful. - std::optional queue_iterator; + void next() override { queue_iter++; } - /// Pointer to file segment is always hold by the cache itself. - /// Apart from pointer in cache, it can be hold by cache users, when they call - /// getorSet(), but cache users always hold it via FileSegmentsHolder. - bool releasable() const {return file_segment.unique(); } + bool valid() const override { return queue_iter != file_cache->queue.end(); } - size_t size() const { return file_segment->reserved_size; } + Key & key() const override { return queue_iter->key; } - FileSegmentCell(FileSegmentPtr file_segment_, LRUFileCache * cache, std::lock_guard & cache_lock); + size_t offset() const override { return queue_iter->offset; } - FileSegmentCell(FileSegmentCell && other) noexcept - : file_segment(std::move(other.file_segment)) - , queue_iterator(other.queue_iterator) {} + size_t size() const override { return queue_iter->size; } + + size_t hits() const override { return queue_iter->hits; } + + Iterator getSnapshot() override { return std::make_shared(file_cache, queue_iter); } + + protected: + LRUFileCache * file_cache; + LRUQueueIterator queue_iter; }; - using FileSegmentsByOffset = std::map; - using CachedFiles = std::unordered_map; + class WriteableIterator : public ReadableIterator + { + public: + WriteableIterator(LRUFileCache * file_cache_, LRUQueueIterator queue_iter_) : ReadableIterator(file_cache_, queue_iter_) { } - CachedFiles files; - LRUQueue queue; + void remove(std::lock_guard &) override + { + file_cache->cache_size -= queue_iter->size; + file_cache->queue.erase(queue_iter); + } - LRUQueue stash_queue; - AccessRecord records; + void incrementSize(size_t size_increment, std::lock_guard &) override + { + file_cache->cache_size += size_increment; + queue_iter->size += size_increment; + } - size_t max_stash_element_size; - size_t enable_cache_hits_threshold; - - Poco::Logger * log; - bool allow_to_remove_persistent_segments_from_cache_by_default; - - FileSegments getImpl( - const Key & key, const FileSegment::Range & range, - std::lock_guard & cache_lock); - - FileSegmentCell * getCell( - const Key & key, size_t offset, std::lock_guard & cache_lock); - - FileSegmentCell * addCell( - const Key & key, size_t offset, size_t size, - FileSegment::State state, bool is_persistent, - std::lock_guard & cache_lock); - - void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); - - bool tryReserve( - const Key & key, size_t offset, size_t size, - std::lock_guard & cache_lock) override; - - bool tryReserveForMainList( - const Key & key, size_t offset, size_t size, - QueryContextPtr query_context, - std::lock_guard & cache_lock); - - void remove( - Key key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; - - bool isLastFileSegmentHolder( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; - - size_t getAvailableCacheSize() const; - - void loadCacheInfoIntoMemory(std::lock_guard & cache_lock); - - FileSegments splitRangeIntoCells( - const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock); - - String dumpStructureUnlocked(const Key & key_, std::lock_guard & cache_lock); - - void fillHolesWithEmptyFileSegments( - FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, bool is_persistent, std::lock_guard & cache_lock); - - FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent) override; - - size_t getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const; - - size_t getAvailableCacheSizeUnlocked(std::lock_guard & cache_lock) const; - - size_t getFileSegmentsNumUnlocked(std::lock_guard & cache_lock) const; - - void assertCacheCellsCorrectness(const FileSegmentsByOffset & cells_by_offset, std::lock_guard & cache_lock); - - void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) override; + void use(std::lock_guard &) override + { + queue_iter->hits++; + file_cache->queue.splice(file_cache->queue.end(), file_cache->queue, queue_iter); + } + }; public: - String dumpStructure(const Key & key_) override; + LRUFileCache() = default; - void assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock); + Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override + { + auto iter = queue.insert(queue.end(), FileCacheRecord(key, offset, size)); + cache_size += size; + return std::make_shared(this, iter); + } - void assertCacheCorrectness(std::lock_guard & cache_lock); + bool contains(const Key & key, size_t offset, std::lock_guard &) override + { + for (const auto & record : queue) + { + if (key == record.key && offset == record.offset) + return true; + } + return false; + } - void assertQueueCorrectness(std::lock_guard & cache_lock); + void removeAll(std::lock_guard &) override + { + queue.clear(); + cache_size = 0; + } + + Iterator getNewIterator(std::lock_guard &) override { return std::make_shared(this, queue.begin()); } + + size_t getElementsNum(std::lock_guard &) const override { return queue.size(); } + + std::string toString(std::lock_guard &) const override { return {}; } + +private: + LRUQueue queue; }; -} +}; diff --git a/src/Common/tests/gtest_lru_file_cache.cpp b/src/Common/tests/gtest_lru_file_cache.cpp index 8e7554f0418..ac942d97a32 100644 --- a/src/Common/tests/gtest_lru_file_cache.cpp +++ b/src/Common/tests/gtest_lru_file_cache.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include From 50fd740ec34825962e1f4e17c7bba2e84742692f Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Sun, 26 Jun 2022 20:35:02 +0800 Subject: [PATCH 05/18] fix --- src/Common/FileCache.cpp | 2 +- src/Common/IFileCachePriority.h | 6 +++--- src/Common/LRUFileCache.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 56eb4c9e081..bf981cc466e 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -772,7 +772,7 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) { std::lock_guard segment_lock(file_segment->mutex); file_segment->detach(cache_lock, segment_lock); - remove(file_segment->key(), file_segment->offset(), cache_lock, segment_lock); + to_remove.emplace_back(file_segment); } } } diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index a29d66c70be..2e73bb92841 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -3,9 +3,9 @@ #include #include #include +#include #include #include -#include namespace DB { @@ -28,7 +28,7 @@ public: String toString() const; Key() = default; - explicit Key(const UInt128 & key_) : key(key_) {} + explicit Key(const UInt128 & key_) : key(key_) { } bool operator==(const Key & other) const { return key == other.key; } }; @@ -80,7 +80,7 @@ public: throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support incrementSize() for IIterator."); } - virtual Key & key() const = 0; + virtual Key key() const = 0; virtual size_t offset() const = 0; diff --git a/src/Common/LRUFileCache.h b/src/Common/LRUFileCache.h index 0bd87b2b38c..6e42a7732d4 100644 --- a/src/Common/LRUFileCache.h +++ b/src/Common/LRUFileCache.h @@ -19,9 +19,9 @@ public: void next() override { queue_iter++; } - bool valid() const override { return queue_iter != file_cache->queue.end(); } + bool valid() const override { return (file_cache->queue.size() && (queue_iter != file_cache->queue.end())); } - Key & key() const override { return queue_iter->key; } + Key key() const override { return queue_iter->key; } size_t offset() const override { return queue_iter->offset; } From 9d83b93e88025f4052e5a32562377b26ab87a0cd Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Wed, 10 Aug 2022 13:50:30 +0800 Subject: [PATCH 06/18] fix rebase --- src/Common/FileCache.cpp | 196 +++++++++++++- src/Common/FileCache.h | 247 ++++++++++++++---- src/Common/FileCacheType.h | 28 ++ src/Common/FileCache_fwd.h | 4 +- src/Common/FileSegment.cpp | 6 +- src/Common/FileSegment.h | 10 +- src/Common/IFileCache.cpp | 201 -------------- src/Common/IFileCache.h | 216 --------------- src/Common/IFileCachePriority.h | 68 ++--- ...{LRUFileCache.h => LRUFileCachePriority.h} | 41 +-- src/Common/tests/gtest_lru_file_cache.cpp | 2 +- src/Disks/IO/CachedReadBufferFromRemoteFS.cpp | 2 +- src/Disks/IO/CachedReadBufferFromRemoteFS.h | 6 +- .../ObjectStorages/DiskObjectStorage.cpp | 3 +- .../DiskObjectStorageCommon.cpp | 2 +- src/Disks/ObjectStorages/IObjectStorage.h | 1 + .../ObjectStorages/S3/S3ObjectStorage.cpp | 2 +- src/IO/WriteBufferFromS3.cpp | 2 +- src/Interpreters/AsynchronousMetrics.cpp | 2 +- .../InterpreterDescribeCacheQuery.cpp | 2 +- src/Interpreters/InterpreterSystemQuery.cpp | 2 +- .../System/StorageSystemFilesystemCache.cpp | 2 +- .../System/StorageSystemRemoteDataPaths.cpp | 2 +- 23 files changed, 480 insertions(+), 567 deletions(-) create mode 100644 src/Common/FileCacheType.h delete mode 100644 src/Common/IFileCache.cpp delete mode 100644 src/Common/IFileCache.h rename src/Common/{LRUFileCache.h => LRUFileCachePriority.h} (61%) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index bf981cc466e..2a2fc68e768 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include namespace fs = std::filesystem; @@ -23,10 +23,16 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -FileCache::FileCache(const String & cache_base_path_, const FileCacheSettings & cache_settings_) - : IFileCache(cache_base_path_, cache_settings_) - , main_priority(std::make_shared()) - , stash_priority(std::make_shared()) +FileCache::FileCache( + const String & cache_base_path_, + const FileCacheSettings & cache_settings_) + : cache_base_path(cache_base_path_) + , max_size(cache_settings_.max_size) + , max_element_size(cache_settings_.max_elements) + , max_file_segment_size(cache_settings_.max_file_segment_size) + , enable_filesystem_query_cache_limit(cache_settings_.enable_filesystem_query_cache_limit) + , main_priority(std::make_shared()) + , stash_priority(std::make_shared()) , max_stash_element_size(cache_settings_.max_elements) , enable_cache_hits_threshold(cache_settings_.enable_cache_hits_threshold) , log(&Poco::Logger::get("FileCache")) @@ -34,6 +40,175 @@ FileCache::FileCache(const String & cache_base_path_, const FileCacheSettings & { } +String FileCache::Key::toString() const +{ + return getHexUIntLowercase(key); +} + +FileCache::Key FileCache::hash(const String & path) +{ + return Key(sipHash128(path.data(), path.size())); +} + +String FileCache::getPathInLocalCache(const Key & key, size_t offset, bool is_persistent) const +{ + auto key_str = key.toString(); + return fs::path(cache_base_path) + / key_str.substr(0, 3) + / key_str + / (std::to_string(offset) + (is_persistent ? "_persistent" : "")); +} + +String FileCache::getPathInLocalCache(const Key & key) const +{ + auto key_str = key.toString(); + return fs::path(cache_base_path) / key_str.substr(0, 3) / key_str; +} + +static bool isQueryInitialized() +{ + return CurrentThread::isInitialized() + && CurrentThread::get().getQueryContext() + && CurrentThread::getQueryId().size != 0; +} + +bool FileCache::isReadOnly() +{ + return !isQueryInitialized(); +} + +void FileCache::assertInitialized() const +{ + if (!is_initialized) + throw Exception(ErrorCodes::REMOTE_FS_OBJECT_CACHE_ERROR, "Cache not initialized"); +} + +FileCache::QueryContextPtr FileCache::getCurrentQueryContext(std::lock_guard & cache_lock) +{ + if (!isQueryInitialized()) + return nullptr; + + return getQueryContext(CurrentThread::getQueryId().toString(), cache_lock); +} + +FileCache::QueryContextPtr FileCache::getQueryContext(const String & query_id, std::lock_guard & /* cache_lock */) +{ + auto query_iter = query_map.find(query_id); + return (query_iter == query_map.end()) ? nullptr : query_iter->second; +} + +void FileCache::removeQueryContext(const String & query_id) +{ + std::lock_guard cache_lock(mutex); + auto query_iter = query_map.find(query_id); + + if (query_iter == query_map.end()) + { + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Attempt to release query context that does not exist (query_id: {})", + query_id); + } + + query_map.erase(query_iter); +} + +FileCache::QueryContextPtr FileCache::getOrSetQueryContext( + const String & query_id, const ReadSettings & settings, std::lock_guard & cache_lock) +{ + if (query_id.empty()) + return nullptr; + + auto context = getQueryContext(query_id, cache_lock); + if (context) + return context; + + auto query_context = std::make_shared(settings.max_query_cache_size, settings.skip_download_if_exceeds_query_cache); + auto query_iter = query_map.emplace(query_id, query_context).first; + return query_iter->second; +} + +FileCache::QueryContextHolder FileCache::getQueryContextHolder(const String & query_id, const ReadSettings & settings) +{ + std::lock_guard cache_lock(mutex); + + if (!enable_filesystem_query_cache_limit || settings.max_query_cache_size == 0) + return {}; + + /// if enable_filesystem_query_cache_limit is true, and max_query_cache_size large than zero, + /// we create context query for current query. + auto context = getOrSetQueryContext(query_id, settings, cache_lock); + return QueryContextHolder(query_id, this, context); +} + +void FileCache::QueryContext::remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) +{ + if (cache_size < size) + throw Exception(ErrorCodes::LOGICAL_ERROR, "Deleted cache size exceeds existing cache size"); + + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record != records.end()) + { + record->second->remove(cache_lock); + records.erase({key, offset}); + } + } + cache_size -= size; +} + +void FileCache::QueryContext::reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) +{ + if (cache_size + size > max_cache_size) + { + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Reserved cache size exceeds the remaining cache size (key: {}, offset: {})", + key.toString(), offset); + } + + if (!skip_download_if_exceeds_query_cache) + { + auto record = records.find({key, offset}); + if (record == records.end()) + { + auto queue_iter = priority->add(key, offset, 0, cache_lock); + record = records.insert({{key, offset}, queue_iter}).first; + } + record->second->incrementSize(size, cache_lock); + } + cache_size += size; +} + +void FileCache::QueryContext::use(const Key & key, size_t offset, std::lock_guard & cache_lock) +{ + if (skip_download_if_exceeds_query_cache) + return; + + auto record = records.find({key, offset}); + if (record != records.end()) + record->second->use(cache_lock); +} + +FileCache::QueryContextHolder::QueryContextHolder( + const String & query_id_, + FileCache * cache_, + FileCache::QueryContextPtr context_) + : query_id(query_id_) + , cache(cache_) + , context(context_) +{ +} + +FileCache::QueryContextHolder::~QueryContextHolder() +{ + /// If only the query_map and the current holder hold the context_query, + /// the query has been completed and the query_context is released. + if (context && context.use_count() == 2) + cache->removeQueryContext(query_id); +} + void FileCache::initialize() { std::lock_guard cache_lock(mutex); @@ -115,7 +290,7 @@ FileSegments FileCache::getImpl( files.erase(key); /// Note: it is guaranteed that there is no concurrency with files deletion, - /// because cache files are deleted only inside IFileCache and under cache lock. + /// because cache files are deleted only inside FileCache and under cache lock. if (fs::exists(key_path)) fs::remove_all(key_path); @@ -387,7 +562,7 @@ FileCache::FileSegmentCell * FileCache::addCell( if (stash_priority->getElementsNum(cache_lock) > max_stash_element_size) { - auto remove_priority_iter = stash_priority->getNewIterator(cache_lock); + auto remove_priority_iter = stash_priority->getNewIterator(cache_lock)->getWriteIterator(); stash_records.erase({remove_priority_iter->key(), remove_priority_iter->offset()}); remove_priority_iter->remove(cache_lock); } @@ -473,7 +648,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc auto * cell_for_reserve = getCell(key, offset, cache_lock); - std::vector ghost; + std::vector ghost; std::vector trash; std::vector to_evict; @@ -496,7 +671,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc { /// The cache corresponding to this record may be swapped out by /// other queries, so it has become invalid. - ghost.push_back(iter->getSnapshot()); + ghost.push_back(iter->getWriteIterator()); removed_size += iter->size(); } else @@ -844,10 +1019,9 @@ void FileCache::loadCacheInfoIntoMemory(std::lock_guard & cache_lock Key key; UInt64 offset = 0; size_t size = 0; - std::vector>> queue_entries; + std::vector>> queue_entries; /// cache_base_path / key_prefix / key / offset - if (!files.empty()) throw Exception( ErrorCodes::REMOTE_FS_OBJECT_CACHE_ERROR, diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index 5aa32ac94ca..bccd2dbd458 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -3,50 +3,196 @@ #include #include #include +#include #include #include #include #include #include #include -#include -#include +#include +#include +#include #include -#include - +#include +#include +#include namespace DB { /** * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. - * Implements LRU eviction policy. - */ -class FileCache final : public IFileCache + */ +class FileCache : private boost::noncopyable { + friend class FileSegment; + friend class IFileCachePriority; + friend struct FileSegmentsHolder; + friend class FileSegmentRangeWriter; + public: - FileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_); + using Key = DB::FileCacheKey; - FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) override; + FileCache(const String & cache_base_path_, const FileCacheSettings & cache_settings_); - FileSegmentsHolder get(const Key & key, size_t offset, size_t size) override; + ~FileCache() = default; - FileSegments getSnapshot() const override; + /// Restore cache from local filesystem. + void initialize(); - void initialize() override; + void removeIfExists(const Key & key); - void removeIfExists(const Key & key) override; + void removeIfReleasable(bool remove_persistent_files); - void removeIfReleasable(bool remove_persistent_files) override; + static bool isReadOnly(); - std::vector tryGetCachePaths(const Key & key) override; + /// Cache capacity in bytes. + size_t capacity() const { return max_size; } - size_t getUsedCacheSize() const override; + static Key hash(const String & path); - size_t getFileSegmentsNum() const override; + String getPathInLocalCache(const Key & key, size_t offset, bool is_persistent) const; + + String getPathInLocalCache(const Key & key) const; + + const String & getBasePath() const { return cache_base_path; } + + std::vector tryGetCachePaths(const Key & key); + + /** + * Given an `offset` and `size` representing [offset, offset + size) bytes interval, + * return list of cached non-overlapping non-empty + * file segments `[segment1, ..., segmentN]` which intersect with given interval. + * + * Segments in returned list are ordered in ascending order and represent a full contiguous + * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. + * + * As long as pointers to returned file segments are hold + * it is guaranteed that these file segments are not removed from cache. + */ + FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent); + + /** + * Segments in returned list are ordered in ascending order and represent a full contiguous + * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. + * + * If file segment has state EMPTY, then it is also marked as "detached". E.g. it is "detached" + * from cache (not owned by cache), and as a result will never change it's state and will be destructed + * with the destruction of the holder, while in getOrSet() EMPTY file segments can eventually change + * it's state (and become DOWNLOADED). + */ + FileSegmentsHolder get(const Key & key, size_t offset, size_t size); + + FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent); + + FileSegments getSnapshot() const; + + /// For debug. + String dumpStructure(const Key & key); + + size_t getUsedCacheSize() const; + + size_t getFileSegmentsNum() const; + +private: + String cache_base_path; + size_t max_size; + size_t max_element_size; + size_t max_file_segment_size; + + bool is_initialized = false; + + mutable std::mutex mutex; + + bool tryReserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); + + void remove(Key key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & segment_lock); + + bool isLastFileSegmentHolder( + const Key & key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & segment_lock); + + void reduceSizeToDownloaded( + const Key & key, size_t offset, std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */); + + void assertInitialized() const; + + using AccessKeyAndOffset = std::pair; + struct KeyAndOffsetHash + { + std::size_t operator()(const AccessKeyAndOffset & key) const + { + return std::hash()(key.first.key) ^ std::hash()(key.second); + } + }; + + using FileCacheRecords = std::unordered_map; + + /// Used to track and control the cache access of each query. + /// Through it, we can realize the processing of different queries by the cache layer. + struct QueryContext + { + FileCacheRecords records; + FileCachePriorityPtr priority; + + size_t cache_size = 0; + size_t max_cache_size; + + bool skip_download_if_exceeds_query_cache; + + QueryContext(size_t max_cache_size_, bool skip_download_if_exceeds_query_cache_) + : max_cache_size(max_cache_size_), skip_download_if_exceeds_query_cache(skip_download_if_exceeds_query_cache_) + { + } + + void remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); + + void reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); + + void use(const Key & key, size_t offset, std::lock_guard & cache_lock); + + size_t getMaxCacheSize() const { return max_cache_size; } + + size_t getCacheSize() const { return cache_size; } + + FileCachePriorityPtr getPriority() { return priority; } + + bool isSkipDownloadIfExceed() const { return skip_download_if_exceeds_query_cache; } + }; + + using QueryContextPtr = std::shared_ptr; + using QueryContextMap = std::unordered_map; + + QueryContextMap query_map; + + bool enable_filesystem_query_cache_limit; + + QueryContextPtr getCurrentQueryContext(std::lock_guard & cache_lock); + + QueryContextPtr getQueryContext(const String & query_id, std::lock_guard & cache_lock); + + void removeQueryContext(const String & query_id); + + QueryContextPtr getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard &); + +public: + /// Save a query context information, and adopt different cache policies + /// for different queries through the context cache layer. + struct QueryContextHolder : private boost::noncopyable + { + QueryContextHolder(const String & query_id_, FileCache * cache_, QueryContextPtr context_); + + QueryContextHolder() = default; + + ~QueryContextHolder(); + + String query_id; + FileCache * cache = nullptr; + QueryContextPtr context; + }; + + QueryContextHolder getQueryContextHolder(const String & query_id, const ReadSettings & settings); private: struct FileSegmentCell : private boost::noncopyable @@ -54,20 +200,21 @@ private: FileSegmentPtr file_segment; /// Iterator is put here on first reservation attempt, if successful. - IFileCachePriority::Iterator queue_iterator; + IFileCachePriority::WriteIterator queue_iterator; /// Pointer to file segment is always hold by the cache itself. /// Apart from pointer in cache, it can be hold by cache users, when they call /// getorSet(), but cache users always hold it via FileSegmentsHolder. - bool releasable() const {return file_segment.unique(); } + bool releasable() const { return file_segment.unique(); } size_t size() const { return file_segment->reserved_size; } FileSegmentCell(FileSegmentPtr file_segment_, FileCache * cache, std::lock_guard & cache_lock); FileSegmentCell(FileSegmentCell && other) noexcept - : file_segment(std::move(other.file_segment)) - , queue_iterator(other.queue_iterator) {} + : file_segment(std::move(other.file_segment)), queue_iterator(other.queue_iterator) + { + } }; using FileSegmentsByOffset = std::map; @@ -85,52 +232,44 @@ private: Poco::Logger * log; bool allow_to_remove_persistent_segments_from_cache_by_default; - FileSegments getImpl( - const Key & key, const FileSegment::Range & range, - std::lock_guard & cache_lock); + FileSegments getImpl(const Key & key, const FileSegment::Range & range, std::lock_guard & cache_lock); - FileSegmentCell * getCell( - const Key & key, size_t offset, std::lock_guard & cache_lock); + FileSegmentCell * getCell(const Key & key, size_t offset, std::lock_guard & cache_lock); FileSegmentCell * addCell( - const Key & key, size_t offset, size_t size, - FileSegment::State state, bool is_persistent, + const Key & key, + size_t offset, + size_t size, + FileSegment::State state, + bool is_persistent, std::lock_guard & cache_lock); void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); - bool tryReserve( - const Key & key, size_t offset, size_t size, - std::lock_guard & cache_lock) override; - bool tryReserveForMainList( - const Key & key, size_t offset, size_t size, - QueryContextPtr query_context, - std::lock_guard & cache_lock); - - void remove( - Key key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; - - bool isLastFileSegmentHolder( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) override; + const Key & key, size_t offset, size_t size, QueryContextPtr query_context, std::lock_guard & cache_lock); size_t getAvailableCacheSize() const; void loadCacheInfoIntoMemory(std::lock_guard & cache_lock); FileSegments splitRangeIntoCells( - const Key & key, size_t offset, size_t size, FileSegment::State state, bool is_persistent, std::lock_guard & cache_lock); + const Key & key, + size_t offset, + size_t size, + FileSegment::State state, + bool is_persistent, + std::lock_guard & cache_lock); String dumpStructureUnlocked(const Key & key_, std::lock_guard & cache_lock); void fillHolesWithEmptyFileSegments( - FileSegments & file_segments, const Key & key, const FileSegment::Range & range, bool fill_with_detached_file_segments, bool is_persistent, std::lock_guard & cache_lock); - - FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent) override; + FileSegments & file_segments, + const Key & key, + const FileSegment::Range & range, + bool fill_with_detached_file_segments, + bool is_persistent, + std::lock_guard & cache_lock); size_t getUsedCacheSizeUnlocked(std::lock_guard & cache_lock) const; @@ -140,13 +279,7 @@ private: void assertCacheCellsCorrectness(const FileSegmentsByOffset & cells_by_offset, std::lock_guard & cache_lock); - void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, std::lock_guard & /* segment_lock */) override; - public: - String dumpStructure(const Key & key_) override; - void assertCacheCorrectness(const Key & key, std::lock_guard & cache_lock); void assertCacheCorrectness(std::lock_guard & cache_lock); diff --git a/src/Common/FileCacheType.h b/src/Common/FileCacheType.h new file mode 100644 index 00000000000..9b3ec5a6af0 --- /dev/null +++ b/src/Common/FileCacheType.h @@ -0,0 +1,28 @@ +#pragma once +#include + +namespace DB +{ + +struct FileCacheKey +{ + UInt128 key; + String toString() const; + + FileCacheKey() = default; + explicit FileCacheKey(const UInt128 & key_) : key(key_) { } + + bool operator==(const FileCacheKey & other) const { return key == other.key; } +}; + +} + +namespace std +{ +template <> +struct hash +{ + std::size_t operator()(const DB::FileCacheKey & k) const { return hash()(k.key); } +}; + +} diff --git a/src/Common/FileCache_fwd.h b/src/Common/FileCache_fwd.h index 8a7c2eeb458..9f6b2a740fc 100644 --- a/src/Common/FileCache_fwd.h +++ b/src/Common/FileCache_fwd.h @@ -9,8 +9,8 @@ static constexpr int REMOTE_FS_OBJECTS_CACHE_DEFAULT_MAX_FILE_SEGMENT_SIZE = 100 static constexpr int REMOTE_FS_OBJECTS_CACHE_DEFAULT_MAX_ELEMENTS = 1024 * 1024; static constexpr int REMOTE_FS_OBJECTS_CACHE_ENABLE_HITS_THRESHOLD = 0; -class IFileCache; -using FileCachePtr = std::shared_ptr; +class FileCache; +using FileCachePtr = std::shared_ptr; struct FileCacheSettings; diff --git a/src/Common/FileSegment.cpp b/src/Common/FileSegment.cpp index c16d4658ae5..2aba93bbdb0 100644 --- a/src/Common/FileSegment.cpp +++ b/src/Common/FileSegment.cpp @@ -5,7 +5,7 @@ #include #include #include - +#include namespace CurrentMetrics { @@ -25,7 +25,7 @@ FileSegment::FileSegment( size_t offset_, size_t size_, const Key & key_, - IFileCache * cache_, + FileCache * cache_, State download_state_, bool is_persistent_) : segment_range(offset_, offset_ + size_ - 1) @@ -787,7 +787,7 @@ FileSegmentsHolder::~FileSegmentsHolder() /// FileSegmentsHolder right after calling file_segment->complete(), so on destruction here /// remain only uncompleted file segments. - IFileCache * cache = nullptr; + FileCache * cache = nullptr; for (auto file_segment_it = file_segments.begin(); file_segment_it != file_segments.end();) { diff --git a/src/Common/FileSegment.h b/src/Common/FileSegment.h index 4404d0e14be..b129b851d7c 100644 --- a/src/Common/FileSegment.h +++ b/src/Common/FileSegment.h @@ -1,11 +1,11 @@ #pragma once #include -#include #include #include #include #include +#include namespace Poco { class Logger; } @@ -17,7 +17,7 @@ extern const Metric CacheFileSegments; namespace DB { -class IFileCache; +class FileCache; class FileSegment; using FileSegmentPtr = std::shared_ptr; @@ -32,7 +32,7 @@ friend struct FileSegmentsHolder; friend class FileSegmentRangeWriter; public: - using Key = IFileCache::Key; + using Key = FileCacheKey; using RemoteFileReaderPtr = std::shared_ptr; using LocalCacheWriterPtr = std::unique_ptr; @@ -74,7 +74,7 @@ public: size_t offset_, size_t size_, const Key & key_, - IFileCache * cache_, + FileCache * cache_, State download_state_, bool is_persistent_ = false); @@ -234,7 +234,7 @@ private: mutable std::mutex download_mutex; Key file_key; - IFileCache * cache; + FileCache * cache; Poco::Logger * log; diff --git a/src/Common/IFileCache.cpp b/src/Common/IFileCache.cpp deleted file mode 100644 index e3ed82d7b62..00000000000 --- a/src/Common/IFileCache.cpp +++ /dev/null @@ -1,201 +0,0 @@ -#include "IFileCache.h" - -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace DB -{ - -namespace ErrorCodes -{ - extern const int REMOTE_FS_OBJECT_CACHE_ERROR; - extern const int LOGICAL_ERROR; -} - -IFileCache::IFileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_) - : cache_base_path(cache_base_path_) - , max_size(cache_settings_.max_size) - , max_element_size(cache_settings_.max_elements) - , max_file_segment_size(cache_settings_.max_file_segment_size) - , enable_filesystem_query_cache_limit(cache_settings_.enable_filesystem_query_cache_limit) -{ -} - -String IFileCache::Key::toString() const -{ - return getHexUIntLowercase(key); -} - -IFileCache::Key IFileCache::hash(const String & path) -{ - return Key(sipHash128(path.data(), path.size())); -} - -String IFileCache::getPathInLocalCache(const Key & key, size_t offset, bool is_persistent) const -{ - auto key_str = key.toString(); - return fs::path(cache_base_path) - / key_str.substr(0, 3) - / key_str - / (std::to_string(offset) + (is_persistent ? "_persistent" : "")); -} - -String IFileCache::getPathInLocalCache(const Key & key) const -{ - auto key_str = key.toString(); - return fs::path(cache_base_path) / key_str.substr(0, 3) / key_str; -} - -static bool isQueryInitialized() -{ - return CurrentThread::isInitialized() - && CurrentThread::get().getQueryContext() - && !CurrentThread::getQueryId().empty(); -} - -bool IFileCache::isReadOnly() -{ - return !isQueryInitialized(); -} - -void IFileCache::assertInitialized() const -{ - if (!is_initialized) - throw Exception(ErrorCodes::REMOTE_FS_OBJECT_CACHE_ERROR, "Cache not initialized"); -} - -IFileCache::QueryContextPtr IFileCache::getCurrentQueryContext(std::lock_guard & cache_lock) -{ - if (!isQueryInitialized()) - return nullptr; - - return getQueryContext(std::string(CurrentThread::getQueryId()), cache_lock); -} - -IFileCache::QueryContextPtr IFileCache::getQueryContext(const String & query_id, std::lock_guard & /* cache_lock */) -{ - auto query_iter = query_map.find(query_id); - return (query_iter == query_map.end()) ? nullptr : query_iter->second; -} - -void IFileCache::removeQueryContext(const String & query_id) -{ - std::lock_guard cache_lock(mutex); - auto query_iter = query_map.find(query_id); - - if (query_iter == query_map.end()) - { - throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Attempt to release query context that does not exist (query_id: {})", - query_id); - } - - query_map.erase(query_iter); -} - -IFileCache::QueryContextPtr IFileCache::getOrSetQueryContext( - const String & query_id, const ReadSettings & settings, std::lock_guard & cache_lock) -{ - if (query_id.empty()) - return nullptr; - - auto context = getQueryContext(query_id, cache_lock); - if (context) - return context; - - auto query_context = std::make_shared(settings.max_query_cache_size, settings.skip_download_if_exceeds_query_cache); - auto query_iter = query_map.emplace(query_id, query_context).first; - return query_iter->second; -} - -IFileCache::QueryContextHolder IFileCache::getQueryContextHolder(const String & query_id, const ReadSettings & settings) -{ - std::lock_guard cache_lock(mutex); - - if (!enable_filesystem_query_cache_limit || settings.max_query_cache_size == 0) - return {}; - - /// if enable_filesystem_query_cache_limit is true, and max_query_cache_size large than zero, - /// we create context query for current query. - auto context = getOrSetQueryContext(query_id, settings, cache_lock); - return QueryContextHolder(query_id, this, context); -} - -void IFileCache::QueryContext::remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) -{ - if (cache_size < size) - throw Exception(ErrorCodes::LOGICAL_ERROR, "Deleted cache size exceeds existing cache size"); - - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record != records.end()) - { - record->second->remove(cache_lock); - records.erase({key, offset}); - } - } - cache_size -= size; -} - -void IFileCache::QueryContext::reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) -{ - if (cache_size + size > max_cache_size) - { - throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Reserved cache size exceeds the remaining cache size (key: {}, offset: {})", - key.toString(), offset); - } - - if (!skip_download_if_exceeds_query_cache) - { - auto record = records.find({key, offset}); - if (record == records.end()) - { - auto queue_iter = priority->add(key, offset, 0, cache_lock); - record = records.insert({{key, offset}, queue_iter}).first; - } - record->second->incrementSize(size, cache_lock); - } - cache_size += size; -} - -void IFileCache::QueryContext::use(const Key & key, size_t offset, std::lock_guard & cache_lock) -{ - if (skip_download_if_exceeds_query_cache) - return; - - auto record = records.find({key, offset}); - if (record != records.end()) - record->second->use(cache_lock); -} - -IFileCache::QueryContextHolder::QueryContextHolder( - const String & query_id_, - IFileCache * cache_, - IFileCache::QueryContextPtr context_) - : query_id(query_id_) - , cache(cache_) - , context(context_) -{ -} - -IFileCache::QueryContextHolder::~QueryContextHolder() -{ - /// If only the query_map and the current holder hold the context_query, - /// the query has been completed and the query_context is released. - if (context && context.use_count() == 2) - cache->removeQueryContext(query_id); -} - -} diff --git a/src/Common/IFileCache.h b/src/Common/IFileCache.h deleted file mode 100644 index f46a83d52cf..00000000000 --- a/src/Common/IFileCache.h +++ /dev/null @@ -1,216 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include -#include -#include - - -namespace DB -{ - -class FileSegment; -using FileSegmentPtr = std::shared_ptr; -using FileSegments = std::list; -struct FileSegmentsHolder; -struct ReadSettings; - -/** - * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. - */ -class IFileCache : private boost::noncopyable -{ -friend class FileSegment; -friend struct FileSegmentsHolder; -friend class FileSegmentRangeWriter; - -public: - using Key = IFileCachePriority::Key; - - IFileCache( - const String & cache_base_path_, - const FileCacheSettings & cache_settings_); - - virtual ~IFileCache() = default; - - /// Restore cache from local filesystem. - virtual void initialize() = 0; - - virtual void removeIfExists(const Key & key) = 0; - - virtual void removeIfReleasable(bool remove_persistent_files) = 0; - - static bool isReadOnly(); - - /// Cache capacity in bytes. - size_t capacity() const { return max_size; } - - static Key hash(const String & path); - - String getPathInLocalCache(const Key & key, size_t offset, bool is_persistent) const; - - String getPathInLocalCache(const Key & key) const; - - const String & getBasePath() const { return cache_base_path; } - - virtual std::vector tryGetCachePaths(const Key & key) = 0; - - /** - * Given an `offset` and `size` representing [offset, offset + size) bytes interval, - * return list of cached non-overlapping non-empty - * file segments `[segment1, ..., segmentN]` which intersect with given interval. - * - * Segments in returned list are ordered in ascending order and represent a full contiguous - * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. - * - * As long as pointers to returned file segments are hold - * it is guaranteed that these file segments are not removed from cache. - */ - virtual FileSegmentsHolder getOrSet(const Key & key, size_t offset, size_t size, bool is_persistent) = 0; - - /** - * Segments in returned list are ordered in ascending order and represent a full contiguous - * interval (no holes). Each segment in returned list has state: DOWNLOADED, DOWNLOADING or EMPTY. - * - * If file segment has state EMPTY, then it is also marked as "detached". E.g. it is "detached" - * from cache (not owned by cache), and as a result will never change it's state and will be destructed - * with the destruction of the holder, while in getOrSet() EMPTY file segments can eventually change - * it's state (and become DOWNLOADED). - */ - virtual FileSegmentsHolder get(const Key & key, size_t offset, size_t size) = 0; - - virtual FileSegmentsHolder setDownloading(const Key & key, size_t offset, size_t size, bool is_persistent) = 0; - - virtual FileSegments getSnapshot() const = 0; - - /// For debug. - virtual String dumpStructure(const Key & key) = 0; - - virtual size_t getUsedCacheSize() const = 0; - - virtual size_t getFileSegmentsNum() const = 0; - -protected: - String cache_base_path; - size_t max_size; - size_t max_element_size; - size_t max_file_segment_size; - - bool is_initialized = false; - - mutable std::mutex mutex; - - virtual bool tryReserve( - const Key & key, size_t offset, size_t size, - std::lock_guard & cache_lock) = 0; - - virtual void remove( - Key key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - virtual bool isLastFileSegmentHolder( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & segment_lock) = 0; - - virtual void reduceSizeToDownloaded( - const Key & key, size_t offset, - std::lock_guard & cache_lock, - std::lock_guard & /* segment_lock */) = 0; - - void assertInitialized() const; - - using AccessKeyAndOffset = std::pair; - struct KeyAndOffsetHash - { - std::size_t operator()(const AccessKeyAndOffset & key) const - { - return std::hash()(key.first.key) ^ std::hash()(key.second); - } - }; - - using FileCacheRecords = std::unordered_map; - - /// Used to track and control the cache access of each query. - /// Through it, we can realize the processing of different queries by the cache layer. - struct QueryContext - { - FileCacheRecords records; - FileCachePriorityPtr priority; - - size_t cache_size = 0; - size_t max_cache_size; - - bool skip_download_if_exceeds_query_cache; - - QueryContext(size_t max_cache_size_, bool skip_download_if_exceeds_query_cache_) - : max_cache_size(max_cache_size_) - , skip_download_if_exceeds_query_cache(skip_download_if_exceeds_query_cache_) {} - - void remove(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); - - void reserve(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock); - - void use(const Key & key, size_t offset, std::lock_guard & cache_lock); - - size_t getMaxCacheSize() const { return max_cache_size; } - - size_t getCacheSize() const { return cache_size; } - - FileCachePriorityPtr getPriority() { return priority; } - - bool isSkipDownloadIfExceed() const { return skip_download_if_exceeds_query_cache; } - }; - - using QueryContextPtr = std::shared_ptr; - using QueryContextMap = std::unordered_map; - - QueryContextMap query_map; - - bool enable_filesystem_query_cache_limit; - - QueryContextPtr getCurrentQueryContext(std::lock_guard & cache_lock); - - QueryContextPtr getQueryContext(const String & query_id, std::lock_guard & cache_lock); - - void removeQueryContext(const String & query_id); - - QueryContextPtr getOrSetQueryContext(const String & query_id, const ReadSettings & settings, std::lock_guard &); - -public: - /// Save a query context information, and adopt different cache policies - /// for different queries through the context cache layer. - struct QueryContextHolder : private boost::noncopyable - { - QueryContextHolder(const String & query_id_, IFileCache * cache_, QueryContextPtr context_); - - QueryContextHolder() = default; - - ~QueryContextHolder(); - - String query_id; - IFileCache * cache = nullptr; - QueryContextPtr context; - }; - - QueryContextHolder getQueryContextHolder(const String & query_id, const ReadSettings & settings); - -}; - -using FileCachePtr = std::shared_ptr; - -} - -namespace std -{ -template <> struct hash -{ - std::size_t operator()(const DB::IFileCache::Key & k) const { return hash()(k.key); } -}; - -} diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 2e73bb92841..84e1a386d24 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -5,7 +5,8 @@ #include #include #include -#include +#include +#include namespace DB { @@ -15,6 +16,8 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; } +class FileCache; + class IFileCachePriority; using FileCachePriorityPtr = std::shared_ptr; @@ -22,20 +25,14 @@ using FileCachePriorityPtr = std::shared_ptr; class IFileCachePriority { public: - struct Key - { - UInt128 key; - String toString() const; - - Key() = default; - explicit Key(const UInt128 & key_) : key(key_) { } - - bool operator==(const Key & other) const { return key == other.key; } - }; - class IIterator; friend class IIterator; - using Iterator = std::shared_ptr; + + using ReadIterator = std::shared_ptr; + using WriteIterator = std::shared_ptr; + + friend class FileCache; + using Key = FileCacheKey; struct FileCacheRecord { @@ -56,30 +53,6 @@ public: public: virtual ~IIterator() = default; - virtual void next() { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support next() for IIterator."); } - - virtual bool valid() const { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support valid() for IIterator."); } - - /// Mark a cache record as recently used, it will update the priority - /// of the cache record according to different cache algorithms. - virtual void use(std::lock_guard &) - { - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support use() for IIterator."); - } - - /// Deletes an existing cached record. - virtual void remove(std::lock_guard &) - { - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support remove() for IIterator."); - } - - virtual Iterator getSnapshot() { throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support getSnapshot() for IIterator."); } - - virtual void incrementSize(size_t, std::lock_guard &) - { - throw Exception(ErrorCodes::NOT_IMPLEMENTED, "Not support incrementSize() for IIterator."); - } - virtual Key key() const = 0; virtual size_t offset() const = 0; @@ -87,6 +60,23 @@ public: virtual size_t size() const = 0; virtual size_t hits() const = 0; + + virtual void next() const = 0; + + virtual bool valid() const = 0; + + /// Mark a cache record as recently used, it will update the priority + /// of the cache record according to different cache algorithms. + virtual void use(std::lock_guard &) = 0; + + /// Deletes an existing cached record. + virtual void remove(std::lock_guard &) = 0; + + virtual WriteIterator getWriteIterator() const = 0; + + virtual void incrementSize(size_t, std::lock_guard &) = 0; + + virtual void seekToLowestPriority() const = 0; }; public: @@ -94,7 +84,7 @@ public: /// Add a cache record that did not exist before, and throw a /// logical exception if the cache block already exists. - virtual Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) = 0; + virtual WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) = 0; /// Query whether a cache record exists. If it exists, return true. If not, return false. virtual bool contains(const Key & key, size_t offset, std::lock_guard & cache_lock) = 0; @@ -103,7 +93,7 @@ public: /// Returns an iterator pointing to the lowest priority cached record. /// We can traverse all cached records through the iterator's next(). - virtual Iterator getNewIterator(std::lock_guard & cache_lock) = 0; + virtual ReadIterator getNewIterator(std::lock_guard & cache_lock) = 0; virtual size_t getElementsNum(std::lock_guard & cache_lock) const = 0; diff --git a/src/Common/LRUFileCache.h b/src/Common/LRUFileCachePriority.h similarity index 61% rename from src/Common/LRUFileCache.h rename to src/Common/LRUFileCachePriority.h index 6e42a7732d4..bc9badc0af6 100644 --- a/src/Common/LRUFileCache.h +++ b/src/Common/LRUFileCachePriority.h @@ -5,19 +5,23 @@ namespace DB { -class LRUFileCache : public IFileCachePriority +/// Based on the LRU algorithm implementation, the data with the lowest priority is stored at +/// the head of the queue, and the data with the highest priority is stored at the tail. +class LRUFileCachePriority : public IFileCachePriority { public: using LRUQueue = std::list; using LRUQueueIterator = typename LRUQueue::iterator; - class WriteableIterator; - class ReadableIterator : public IIterator + class LRUFileCacheIterator : public IIterator { public: - ReadableIterator(LRUFileCache * file_cache_, LRUQueueIterator queue_iter_) : file_cache(file_cache_), queue_iter(queue_iter_) { } + LRUFileCacheIterator(LRUFileCachePriority * file_cache_, LRUQueueIterator queue_iter_) + : file_cache(file_cache_), queue_iter(queue_iter_) + { + } - void next() override { queue_iter++; } + void next() const override { queue_iter++; } bool valid() const override { return (file_cache->queue.size() && (queue_iter != file_cache->queue.end())); } @@ -29,17 +33,9 @@ public: size_t hits() const override { return queue_iter->hits; } - Iterator getSnapshot() override { return std::make_shared(file_cache, queue_iter); } + WriteIterator getWriteIterator() const override { return std::make_shared(file_cache, queue_iter); } - protected: - LRUFileCache * file_cache; - LRUQueueIterator queue_iter; - }; - - class WriteableIterator : public ReadableIterator - { - public: - WriteableIterator(LRUFileCache * file_cache_, LRUQueueIterator queue_iter_) : ReadableIterator(file_cache_, queue_iter_) { } + void seekToLowestPriority() const override { queue_iter = file_cache->queue.begin(); } void remove(std::lock_guard &) override { @@ -58,16 +54,20 @@ public: queue_iter->hits++; file_cache->queue.splice(file_cache->queue.end(), file_cache->queue, queue_iter); } + + private: + mutable LRUFileCachePriority * file_cache; + mutable LRUQueueIterator queue_iter; }; public: - LRUFileCache() = default; + LRUFileCachePriority() = default; - Iterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override + WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override { auto iter = queue.insert(queue.end(), FileCacheRecord(key, offset, size)); cache_size += size; - return std::make_shared(this, iter); + return std::make_shared(this, iter); } bool contains(const Key & key, size_t offset, std::lock_guard &) override @@ -86,7 +86,10 @@ public: cache_size = 0; } - Iterator getNewIterator(std::lock_guard &) override { return std::make_shared(this, queue.begin()); } + ReadIterator getNewIterator(std::lock_guard &) override + { + return std::make_shared(this, queue.begin()); + } size_t getElementsNum(std::lock_guard &) const override { return queue.size(); } diff --git a/src/Common/tests/gtest_lru_file_cache.cpp b/src/Common/tests/gtest_lru_file_cache.cpp index ac942d97a32..3f481ee25ca 100644 --- a/src/Common/tests/gtest_lru_file_cache.cpp +++ b/src/Common/tests/gtest_lru_file_cache.cpp @@ -47,7 +47,7 @@ std::vector fromHolder(const DB::FileSegmentsHolder & holder return std::vector(holder.file_segments.begin(), holder.file_segments.end()); } -String getFileSegmentPath(const String & base_path, const DB::IFileCache::Key & key, size_t offset) +String getFileSegmentPath(const String & base_path, const DB::FileCache::Key & key, size_t offset) { auto key_str = key.toString(); return fs::path(base_path) / key_str.substr(0, 3) / key_str / DB::toString(offset); diff --git a/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp b/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp index a3d5cfc408d..a10e136334e 100644 --- a/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp +++ b/src/Disks/IO/CachedReadBufferFromRemoteFS.cpp @@ -1024,7 +1024,7 @@ std::optional CachedReadBufferFromRemoteFS::getLastNonDownloadedOffset() void CachedReadBufferFromRemoteFS::assertCorrectness() const { - if (IFileCache::isReadOnly() && !settings.read_from_filesystem_cache_if_exists_otherwise_bypass_cache) + if (FileCache::isReadOnly() && !settings.read_from_filesystem_cache_if_exists_otherwise_bypass_cache) throw Exception(ErrorCodes::LOGICAL_ERROR, "Cache usage is not allowed"); } diff --git a/src/Disks/IO/CachedReadBufferFromRemoteFS.h b/src/Disks/IO/CachedReadBufferFromRemoteFS.h index aff29dd200c..7fe3af29ef7 100644 --- a/src/Disks/IO/CachedReadBufferFromRemoteFS.h +++ b/src/Disks/IO/CachedReadBufferFromRemoteFS.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include @@ -81,7 +81,7 @@ private: bool writeCache(char * data, size_t size, size_t offset, FileSegment & file_segment); Poco::Logger * log; - IFileCache::Key cache_key; + FileCache::Key cache_key; String remote_fs_object_path; FileCachePtr cache; ReadSettings settings; @@ -128,7 +128,7 @@ private: CurrentMetrics::Increment metric_increment{CurrentMetrics::FilesystemCacheReadBuffers}; ProfileEvents::Counters current_file_segment_counters; - IFileCache::QueryContextHolder query_context_holder; + FileCache::QueryContextHolder query_context_holder; bool is_persistent; }; diff --git a/src/Disks/ObjectStorages/DiskObjectStorage.cpp b/src/Disks/ObjectStorages/DiskObjectStorage.cpp index 970a971d5dc..0849a3f09e3 100644 --- a/src/Disks/ObjectStorages/DiskObjectStorage.cpp +++ b/src/Disks/ObjectStorages/DiskObjectStorage.cpp @@ -10,7 +10,8 @@ #include #include #include -#include +#include +#include #include #include #include diff --git a/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp b/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp index b8ab2f49202..499791caf94 100644 --- a/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp +++ b/src/Disks/ObjectStorages/DiskObjectStorageCommon.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/Disks/ObjectStorages/IObjectStorage.h b/src/Disks/ObjectStorages/IObjectStorage.h index 1ab2d75ff86..69c1c31403d 100644 --- a/src/Disks/ObjectStorages/IObjectStorage.h +++ b/src/Disks/ObjectStorages/IObjectStorage.h @@ -16,6 +16,7 @@ #include #include #include +#include #include diff --git a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp index 25dafac4120..901deeebefc 100644 --- a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp +++ b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/IO/WriteBufferFromS3.cpp b/src/IO/WriteBufferFromS3.cpp index 51f0c0d0743..79da7832a34 100644 --- a/src/IO/WriteBufferFromS3.cpp +++ b/src/IO/WriteBufferFromS3.cpp @@ -3,8 +3,8 @@ #if USE_AWS_S3 #include -#include #include +#include #include #include diff --git a/src/Interpreters/AsynchronousMetrics.cpp b/src/Interpreters/AsynchronousMetrics.cpp index 9fd27fc28b6..f9bc22dd110 100644 --- a/src/Interpreters/AsynchronousMetrics.cpp +++ b/src/Interpreters/AsynchronousMetrics.cpp @@ -12,9 +12,9 @@ #include #include #include -#include #include #include +#include #include #include #include diff --git a/src/Interpreters/InterpreterDescribeCacheQuery.cpp b/src/Interpreters/InterpreterDescribeCacheQuery.cpp index dd6df26c6af..d7c13dbb077 100644 --- a/src/Interpreters/InterpreterDescribeCacheQuery.cpp +++ b/src/Interpreters/InterpreterDescribeCacheQuery.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/Interpreters/InterpreterSystemQuery.cpp b/src/Interpreters/InterpreterSystemQuery.cpp index 695ea53e65e..b37274a3152 100644 --- a/src/Interpreters/InterpreterSystemQuery.cpp +++ b/src/Interpreters/InterpreterSystemQuery.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/Storages/System/StorageSystemFilesystemCache.cpp b/src/Storages/System/StorageSystemFilesystemCache.cpp index 2baddadec90..6d711498091 100644 --- a/src/Storages/System/StorageSystemFilesystemCache.cpp +++ b/src/Storages/System/StorageSystemFilesystemCache.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/Storages/System/StorageSystemRemoteDataPaths.cpp b/src/Storages/System/StorageSystemRemoteDataPaths.cpp index a482f5d87ca..b224a72e787 100644 --- a/src/Storages/System/StorageSystemRemoteDataPaths.cpp +++ b/src/Storages/System/StorageSystemRemoteDataPaths.cpp @@ -1,7 +1,7 @@ #include "StorageSystemRemoteDataPaths.h" #include #include -#include +#include #include #include #include From 081cd4938ad2f3f8a1cddcbe42d9dc2698e37abf Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 28 Jun 2022 03:35:37 +0800 Subject: [PATCH 07/18] fix style --- src/Common/FileCache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index bccd2dbd458..4d2654d1491 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -24,7 +24,7 @@ namespace DB /** * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. - */ + */ class FileCache : private boost::noncopyable { friend class FileSegment; From 61b580aba449a7d7bb756659c287c3f8fbfc2564 Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 28 Jun 2022 03:50:44 +0800 Subject: [PATCH 08/18] add note --- src/Common/FileCache.h | 5 ++--- src/Common/IFileCachePriority.h | 3 +++ src/Common/LRUFileCachePriority.h | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index 4d2654d1491..f809f57f389 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -22,9 +22,8 @@ namespace DB { -/** - * Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. - */ +/// Local cache for remote filesystem files, represented as a set of non-overlapping non-empty file segments. +/// Different caching algorithms are implemented based on IFileCachePriority. class FileCache : private boost::noncopyable { friend class FileSegment; diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 84e1a386d24..568b778d296 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -61,6 +61,7 @@ public: virtual size_t hits() const = 0; + /// Point the iterator to the next higher priority cache record. virtual void next() const = 0; virtual bool valid() const = 0; @@ -72,6 +73,8 @@ public: /// Deletes an existing cached record. virtual void remove(std::lock_guard &) = 0; + /// Get an iterator to handle write operations. Write iterators should only + /// be allowed to call remove, use and incrementSize methods. virtual WriteIterator getWriteIterator() const = 0; virtual void incrementSize(size_t, std::lock_guard &) = 0; diff --git a/src/Common/LRUFileCachePriority.h b/src/Common/LRUFileCachePriority.h index bc9badc0af6..10ad21672dd 100644 --- a/src/Common/LRUFileCachePriority.h +++ b/src/Common/LRUFileCachePriority.h @@ -5,8 +5,8 @@ namespace DB { -/// Based on the LRU algorithm implementation, the data with the lowest priority is stored at -/// the head of the queue, and the data with the highest priority is stored at the tail. +/// Based on the LRU algorithm implementation, the record with the lowest priority is stored at +/// the head of the queue, and the record with the highest priority is stored at the tail. class LRUFileCachePriority : public IFileCachePriority { public: From 164fa1ab0e5a76c5e124df11d5fb12345c0c131a Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Tue, 28 Jun 2022 14:36:41 +0800 Subject: [PATCH 09/18] fix build and style --- src/Common/FileCache.cpp | 4 +--- src/Common/FileCache.h | 2 +- src/Common/IFileCachePriority.h | 9 ++------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 2a2fc68e768..8358504c70e 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -234,7 +234,7 @@ void FileCache::initialize() } void FileCache::useCell( - const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock) + const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock) const { auto file_segment = cell.file_segment; @@ -945,8 +945,6 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) || remove_persistent_files || allow_to_remove_persistent_segments_from_cache_by_default)) { - std::lock_guard segment_lock(file_segment->mutex); - file_segment->detach(cache_lock, segment_lock); to_remove.emplace_back(file_segment); } } diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index f809f57f389..8c6a9396b43 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -243,7 +243,7 @@ private: bool is_persistent, std::lock_guard & cache_lock); - void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock); + void useCell(const FileSegmentCell & cell, FileSegments & result, std::lock_guard & cache_lock) const; bool tryReserveForMainList( const Key & key, size_t offset, size_t size, QueryContextPtr query_context, std::lock_guard & cache_lock); diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 568b778d296..df3ffd9fd9c 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -11,13 +11,7 @@ namespace DB { -namespace ErrorCodes -{ - extern const int NOT_IMPLEMENTED; -} - class FileCache; - class IFileCachePriority; using FileCachePriorityPtr = std::shared_ptr; @@ -66,7 +60,7 @@ public: virtual bool valid() const = 0; - /// Mark a cache record as recently used, it will update the priority + /// Mark a cache record as recently used, it will update the priority /// of the cache record according to different cache algorithms. virtual void use(std::lock_guard &) = 0; @@ -79,6 +73,7 @@ public: virtual void incrementSize(size_t, std::lock_guard &) = 0; + /// Repoint the iterator to the record with the lowest priority. virtual void seekToLowestPriority() const = 0; }; From 1b01cc8ed943c0053cebe98a7464636f2b643c33 Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Wed, 29 Jun 2022 17:44:38 +0800 Subject: [PATCH 10/18] fix --- src/Common/FileCache.cpp | 38 +++++++++++++------------- src/Common/FileCacheType.h | 5 +++- src/Common/IFileCachePriority.h | 26 ++++++++---------- src/Common/LRUFileCachePriority.h | 45 ++++++++++++++++++++++--------- 4 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 8358504c70e..818cc0c1b76 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -40,11 +39,6 @@ FileCache::FileCache( { } -String FileCache::Key::toString() const -{ - return getHexUIntLowercase(key); -} - FileCache::Key FileCache::hash(const String & path) { return Key(sipHash128(path.data(), path.size())); @@ -323,8 +317,11 @@ FileSegments FileCache::getImpl( if (range.left <= prev_cell_range.right) { + /// segment{k-1} segment{k} /// [________] [_____ /// [___________ + /// ^ + /// range.left useCell(prev_cell, result, cache_lock); } } @@ -562,7 +559,7 @@ FileCache::FileSegmentCell * FileCache::addCell( if (stash_priority->getElementsNum(cache_lock) > max_stash_element_size) { - auto remove_priority_iter = stash_priority->getNewIterator(cache_lock)->getWriteIterator(); + auto remove_priority_iter = stash_priority->getLowestPriorityWriteIterator(cache_lock); stash_records.erase({remove_priority_iter->key(), remove_priority_iter->offset()}); remove_priority_iter->remove(cache_lock); } @@ -648,7 +645,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc auto * cell_for_reserve = getCell(key, offset, cache_lock); - std::vector ghost; + std::vector> ghost; std::vector trash; std::vector to_evict; @@ -660,7 +657,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc }; /// Select the cache from the LRU queue held by query for expulsion. - for (auto iter = query_context->getPriority()->getNewIterator(cache_lock); iter->valid(); iter->next()) + for (auto iter = query_context->getPriority()->getLowestPriorityWriteIterator(cache_lock); iter->valid();) { if (!is_overflow()) break; @@ -671,8 +668,10 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc { /// The cache corresponding to this record may be swapped out by /// other queries, so it has become invalid. - ghost.push_back(iter->getWriteIterator()); removed_size += iter->size(); + ghost.push_back({iter->key(), iter->offset(), iter->size()}); + /// next() + iter->remove(cache_lock); } else { @@ -700,6 +699,8 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc removed_size += cell_size; --queue_size; } + + iter->next(); } } @@ -718,8 +719,8 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc remove_file_segment(file_segment, cell->size()); } - for (auto & iter : ghost) - query_context->remove(iter->key(), iter->offset(), iter->size(), cache_lock); + for (auto & entry : ghost) + query_context->remove(std::get<0>(entry), std::get<1>(entry), std::get<2>(entry), cache_lock); if (is_overflow()) return false; @@ -770,7 +771,7 @@ bool FileCache::tryReserveForMainList( std::vector to_evict; std::vector trash; - for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) + for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { auto entry_key = it->key(); auto entry_offset = it->offset(); @@ -926,9 +927,9 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) std::lock_guard cache_lock(mutex); std::vector to_remove; - for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) + for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { - auto key = it->key(); + const auto & key = it->key(); auto offset = it->offset(); auto * cell = getCell(key, offset, cache_lock); @@ -1247,7 +1248,7 @@ String FileCache::dumpStructure(const Key & key) return dumpStructureUnlocked(key, cache_lock); } -String FileCache::dumpStructureUnlocked(const Key & key, std::lock_guard & cache_lock) +String FileCache::dumpStructureUnlocked(const Key & key, std::lock_guard &) { WriteBufferFromOwnString result; const auto & cells_by_offset = files[key]; @@ -1255,7 +1256,6 @@ String FileCache::dumpStructureUnlocked(const Key & key, std::lock_guardgetInfoForLog() << "\n"; - result << "\n\nPriority: " << main_priority->toString(cache_lock); return result.str(); } @@ -1291,9 +1291,9 @@ void FileCache::assertCacheCorrectness(std::lock_guard & cache_lock) void FileCache::assertPriorityCorrectness(std::lock_guard & cache_lock) { [[maybe_unused]] size_t total_size = 0; - for (auto it = main_priority->getNewIterator(cache_lock); it->valid(); it->next()) + for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { - auto key = it->key(); + const auto & key = it->key(); auto offset = it->offset(); auto size = it->size(); diff --git a/src/Common/FileCacheType.h b/src/Common/FileCacheType.h index 9b3ec5a6af0..cf4ab5d20c5 100644 --- a/src/Common/FileCacheType.h +++ b/src/Common/FileCacheType.h @@ -1,5 +1,6 @@ #pragma once #include +#include namespace DB { @@ -7,9 +8,11 @@ namespace DB struct FileCacheKey { UInt128 key; - String toString() const; + + String toString() const { return getHexUIntLowercase(key); } FileCacheKey() = default; + explicit FileCacheKey(const UInt128 & key_) : key(key_) { } bool operator==(const FileCacheKey & other) const { return key == other.key; } diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index df3ffd9fd9c..8b448a5ef9d 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -21,13 +21,13 @@ class IFileCachePriority public: class IIterator; friend class IIterator; + friend class FileCache; + + using Key = FileCacheKey; using ReadIterator = std::shared_ptr; using WriteIterator = std::shared_ptr; - friend class FileCache; - using Key = FileCacheKey; - struct FileCacheRecord { Key key; @@ -47,7 +47,7 @@ public: public: virtual ~IIterator() = default; - virtual Key key() const = 0; + virtual const Key & key() const = 0; virtual size_t offset() const = 0; @@ -64,17 +64,11 @@ public: /// of the cache record according to different cache algorithms. virtual void use(std::lock_guard &) = 0; - /// Deletes an existing cached record. + /// Deletes an existing cached record. And to avoid pointer suspension + /// the iterator should automatically point to the next record. virtual void remove(std::lock_guard &) = 0; - /// Get an iterator to handle write operations. Write iterators should only - /// be allowed to call remove, use and incrementSize methods. - virtual WriteIterator getWriteIterator() const = 0; - virtual void incrementSize(size_t, std::lock_guard &) = 0; - - /// Repoint the iterator to the record with the lowest priority. - virtual void seekToLowestPriority() const = 0; }; public: @@ -84,6 +78,7 @@ public: /// logical exception if the cache block already exists. virtual WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard & cache_lock) = 0; + /// This method is used for assertions in debug mode. So we do not care about complexity here. /// Query whether a cache record exists. If it exists, return true. If not, return false. virtual bool contains(const Key & key, size_t offset, std::lock_guard & cache_lock) = 0; @@ -91,14 +86,15 @@ public: /// Returns an iterator pointing to the lowest priority cached record. /// We can traverse all cached records through the iterator's next(). - virtual ReadIterator getNewIterator(std::lock_guard & cache_lock) = 0; + virtual ReadIterator getLowestPriorityReadIterator(std::lock_guard & cache_lock) = 0; + + /// The same as getLowestPriorityReadIterator(), but it is writeable. + virtual WriteIterator getLowestPriorityWriteIterator(std::lock_guard & cache_lock) = 0; virtual size_t getElementsNum(std::lock_guard & cache_lock) const = 0; size_t getCacheSize(std::lock_guard &) const { return cache_size; } - virtual std::string toString(std::lock_guard & cache_lock) const = 0; - protected: size_t max_cache_size = 0; size_t cache_size = 0; diff --git a/src/Common/LRUFileCachePriority.h b/src/Common/LRUFileCachePriority.h index 10ad21672dd..ecbe2b47bd8 100644 --- a/src/Common/LRUFileCachePriority.h +++ b/src/Common/LRUFileCachePriority.h @@ -5,11 +5,16 @@ namespace DB { +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + /// Based on the LRU algorithm implementation, the record with the lowest priority is stored at /// the head of the queue, and the record with the highest priority is stored at the tail. class LRUFileCachePriority : public IFileCachePriority { -public: +private: using LRUQueue = std::list; using LRUQueueIterator = typename LRUQueue::iterator; @@ -23,9 +28,9 @@ public: void next() const override { queue_iter++; } - bool valid() const override { return (file_cache->queue.size() && (queue_iter != file_cache->queue.end())); } + bool valid() const override { return queue_iter != file_cache->queue.end(); } - Key key() const override { return queue_iter->key; } + const Key & key() const override { return queue_iter->key; } size_t offset() const override { return queue_iter->offset; } @@ -33,14 +38,12 @@ public: size_t hits() const override { return queue_iter->hits; } - WriteIterator getWriteIterator() const override { return std::make_shared(file_cache, queue_iter); } - - void seekToLowestPriority() const override { queue_iter = file_cache->queue.begin(); } - void remove(std::lock_guard &) override { - file_cache->cache_size -= queue_iter->size; - file_cache->queue.erase(queue_iter); + auto remove_iter = queue_iter; + queue_iter++; + file_cache->cache_size -= remove_iter->size; + file_cache->queue.erase(remove_iter); } void incrementSize(size_t size_increment, std::lock_guard &) override @@ -65,6 +68,18 @@ public: WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override { +#ifndef NDEBUG + for (const auto & entry : queue) + { + if (entry.key() == key && entry.offset() == offset) + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Attempt to add duplicate queue entry to queue. (Key: {}, offset: {}, size: {})", + entry.key().toString(), + entry.offset(), + entry.size()); + } +#endif auto iter = queue.insert(queue.end(), FileCacheRecord(key, offset, size)); cache_size += size; return std::make_shared(this, iter); @@ -86,14 +101,20 @@ public: cache_size = 0; } - ReadIterator getNewIterator(std::lock_guard &) override + ReadIterator getLowestPriorityReadIterator(std::lock_guard &) override { return std::make_shared(this, queue.begin()); } - size_t getElementsNum(std::lock_guard &) const override { return queue.size(); } + WriteIterator getLowestPriorityWriteIterator(std::lock_guard &) override + { + return std::make_shared(this, queue.begin()); + } - std::string toString(std::lock_guard &) const override { return {}; } + size_t getElementsNum(std::lock_guard &) const override + { + return queue.size(); + } private: LRUQueue queue; From d2b5581632e8025a605d7832212d689a4fd85266 Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Wed, 29 Jun 2022 21:28:19 +0800 Subject: [PATCH 11/18] fix --- src/Common/FileCache.cpp | 2 +- src/Common/IFileCachePriority.h | 1 - src/Common/LRUFileCachePriority.cpp | 62 ++++++++++++ src/Common/LRUFileCachePriority.h | 146 ++++++++++------------------ 4 files changed, 115 insertions(+), 96 deletions(-) create mode 100644 src/Common/LRUFileCachePriority.cpp diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 818cc0c1b76..f49f441969c 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -773,7 +773,7 @@ bool FileCache::tryReserveForMainList( for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { - auto entry_key = it->key(); + const auto & entry_key = it->key(); auto entry_offset = it->offset(); if (!is_overflow()) diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 8b448a5ef9d..691f23a1b54 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -24,7 +24,6 @@ public: friend class FileCache; using Key = FileCacheKey; - using ReadIterator = std::shared_ptr; using WriteIterator = std::shared_ptr; diff --git a/src/Common/LRUFileCachePriority.cpp b/src/Common/LRUFileCachePriority.cpp new file mode 100644 index 00000000000..ed13e58e5fd --- /dev/null +++ b/src/Common/LRUFileCachePriority.cpp @@ -0,0 +1,62 @@ +#include + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int LOGICAL_ERROR; +} + +IFileCachePriority::WriteIterator +LRUFileCachePriority::add(const Key & key, size_t offset, size_t size, std::lock_guard &) override +{ +#ifndef NDEBUG + for (const auto & entry : queue) + { + if (entry.key == key && entry.offset == offset) + throw Exception( + ErrorCodes::LOGICAL_ERROR, + "Attempt to add duplicate queue entry to queue. (Key: {}, offset: {}, size: {})", + entry.key.toString(), + entry.offset, + entry.size); + } +#endif + auto iter = queue.insert(queue.end(), FileCacheRecord(key, offset, size)); + cache_size += size; + return std::make_shared(this, iter); +} + +bool LRUFileCachePriority::contains(const Key & key, size_t offset, std::lock_guard &) override +{ + for (const auto & record : queue) + { + if (key == record.key && offset == record.offset) + return true; + } + return false; +} + +void LRUFileCachePriority::removeAll(std::lock_guard &) override +{ + queue.clear(); + cache_size = 0; +} + +IFileCachePriority::ReadIterator LRUFileCachePriority::getLowestPriorityReadIterator(std::lock_guard &) override +{ + return std::make_shared(this, queue.begin()); +} + +IFileCachePriority::WriteIterator LRUFileCachePriority::getLowestPriorityWriteIterator(std::lock_guard &) override +{ + return std::make_shared(this, queue.begin()); +} + +size_t LRUFileCachePriority::getElementsNum(std::lock_guard &) const override +{ + return queue.size(); +} + +}; diff --git a/src/Common/LRUFileCachePriority.h b/src/Common/LRUFileCachePriority.h index ecbe2b47bd8..20d1e05b9f0 100644 --- a/src/Common/LRUFileCachePriority.h +++ b/src/Common/LRUFileCachePriority.h @@ -5,11 +5,6 @@ namespace DB { -namespace ErrorCodes -{ - extern const int LOGICAL_ERROR; -} - /// Based on the LRU algorithm implementation, the record with the lowest priority is stored at /// the head of the queue, and the record with the highest priority is stored at the tail. class LRUFileCachePriority : public IFileCachePriority @@ -17,107 +12,70 @@ class LRUFileCachePriority : public IFileCachePriority private: using LRUQueue = std::list; using LRUQueueIterator = typename LRUQueue::iterator; - - class LRUFileCacheIterator : public IIterator - { - public: - LRUFileCacheIterator(LRUFileCachePriority * file_cache_, LRUQueueIterator queue_iter_) - : file_cache(file_cache_), queue_iter(queue_iter_) - { - } - - void next() const override { queue_iter++; } - - bool valid() const override { return queue_iter != file_cache->queue.end(); } - - const Key & key() const override { return queue_iter->key; } - - size_t offset() const override { return queue_iter->offset; } - - size_t size() const override { return queue_iter->size; } - - size_t hits() const override { return queue_iter->hits; } - - void remove(std::lock_guard &) override - { - auto remove_iter = queue_iter; - queue_iter++; - file_cache->cache_size -= remove_iter->size; - file_cache->queue.erase(remove_iter); - } - - void incrementSize(size_t size_increment, std::lock_guard &) override - { - file_cache->cache_size += size_increment; - queue_iter->size += size_increment; - } - - void use(std::lock_guard &) override - { - queue_iter->hits++; - file_cache->queue.splice(file_cache->queue.end(), file_cache->queue, queue_iter); - } - - private: - mutable LRUFileCachePriority * file_cache; - mutable LRUQueueIterator queue_iter; - }; + class LRUFileCacheIterator; public: LRUFileCachePriority() = default; - WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override - { -#ifndef NDEBUG - for (const auto & entry : queue) - { - if (entry.key() == key && entry.offset() == offset) - throw Exception( - ErrorCodes::LOGICAL_ERROR, - "Attempt to add duplicate queue entry to queue. (Key: {}, offset: {}, size: {})", - entry.key().toString(), - entry.offset(), - entry.size()); - } -#endif - auto iter = queue.insert(queue.end(), FileCacheRecord(key, offset, size)); - cache_size += size; - return std::make_shared(this, iter); - } + WriteIterator add(const Key & key, size_t offset, size_t size, std::lock_guard &) override; - bool contains(const Key & key, size_t offset, std::lock_guard &) override - { - for (const auto & record : queue) - { - if (key == record.key && offset == record.offset) - return true; - } - return false; - } + bool contains(const Key & key, size_t offset, std::lock_guard &) override; - void removeAll(std::lock_guard &) override - { - queue.clear(); - cache_size = 0; - } + void removeAll(std::lock_guard &) override; - ReadIterator getLowestPriorityReadIterator(std::lock_guard &) override - { - return std::make_shared(this, queue.begin()); - } + ReadIterator getLowestPriorityReadIterator(std::lock_guard &) override; - WriteIterator getLowestPriorityWriteIterator(std::lock_guard &) override - { - return std::make_shared(this, queue.begin()); - } + WriteIterator getLowestPriorityWriteIterator(std::lock_guard &) override; - size_t getElementsNum(std::lock_guard &) const override - { - return queue.size(); - } + size_t getElementsNum(std::lock_guard &) const override; private: LRUQueue queue; }; +class LRUFileCachePriority::LRUFileCacheIterator : public IFileCachePriority::IIterator +{ +public: + LRUFileCacheIterator(LRUFileCachePriority * file_cache_, LRUFileCachePriority::LRUQueueIterator queue_iter_) + : file_cache(file_cache_), queue_iter(queue_iter_) + { + } + + void next() const override { queue_iter++; } + + bool valid() const override { return queue_iter != file_cache->queue.end(); } + + const Key & key() const override { return queue_iter->key; } + + size_t offset() const override { return queue_iter->offset; } + + size_t size() const override { return queue_iter->size; } + + size_t hits() const override { return queue_iter->hits; } + + void remove(std::lock_guard &) override + { + auto remove_iter = queue_iter; + queue_iter++; + file_cache->cache_size -= remove_iter->size; + file_cache->queue.erase(remove_iter); + } + + void incrementSize(size_t size_increment, std::lock_guard &) override + { + file_cache->cache_size += size_increment; + queue_iter->size += size_increment; + } + + void use(std::lock_guard &) override + { + queue_iter->hits++; + file_cache->queue.splice(file_cache->queue.end(), file_cache->queue, queue_iter); + } + +private: + mutable LRUFileCachePriority * file_cache; + mutable LRUFileCachePriority::LRUQueueIterator queue_iter; +}; + }; From f6a58bff4ca1ef3ada0b0b942f675a812dec6e47 Mon Sep 17 00:00:00 2001 From: KinderRiven <1339764596@qq.com> Date: Wed, 29 Jun 2022 22:45:34 +0800 Subject: [PATCH 12/18] fix build --- src/Common/IFileCachePriority.h | 6 ------ src/Common/LRUFileCachePriority.cpp | 13 ++++++------- src/Common/LRUFileCachePriority.h | 3 ++- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 691f23a1b54..fe925fd275d 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -1,17 +1,14 @@ #pragma once -#include #include #include #include #include -#include #include namespace DB { -class FileCache; class IFileCachePriority; using FileCachePriorityPtr = std::shared_ptr; @@ -20,9 +17,6 @@ class IFileCachePriority { public: class IIterator; - friend class IIterator; - friend class FileCache; - using Key = FileCacheKey; using ReadIterator = std::shared_ptr; using WriteIterator = std::shared_ptr; diff --git a/src/Common/LRUFileCachePriority.cpp b/src/Common/LRUFileCachePriority.cpp index ed13e58e5fd..c54b65f6ee0 100644 --- a/src/Common/LRUFileCachePriority.cpp +++ b/src/Common/LRUFileCachePriority.cpp @@ -8,8 +8,7 @@ namespace ErrorCodes extern const int LOGICAL_ERROR; } -IFileCachePriority::WriteIterator -LRUFileCachePriority::add(const Key & key, size_t offset, size_t size, std::lock_guard &) override +IFileCachePriority::WriteIterator LRUFileCachePriority::add(const Key & key, size_t offset, size_t size, std::lock_guard &) { #ifndef NDEBUG for (const auto & entry : queue) @@ -28,7 +27,7 @@ LRUFileCachePriority::add(const Key & key, size_t offset, size_t size, std::lock return std::make_shared(this, iter); } -bool LRUFileCachePriority::contains(const Key & key, size_t offset, std::lock_guard &) override +bool LRUFileCachePriority::contains(const Key & key, size_t offset, std::lock_guard &) { for (const auto & record : queue) { @@ -38,23 +37,23 @@ bool LRUFileCachePriority::contains(const Key & key, size_t offset, std::lock_gu return false; } -void LRUFileCachePriority::removeAll(std::lock_guard &) override +void LRUFileCachePriority::removeAll(std::lock_guard &) { queue.clear(); cache_size = 0; } -IFileCachePriority::ReadIterator LRUFileCachePriority::getLowestPriorityReadIterator(std::lock_guard &) override +IFileCachePriority::ReadIterator LRUFileCachePriority::getLowestPriorityReadIterator(std::lock_guard &) { return std::make_shared(this, queue.begin()); } -IFileCachePriority::WriteIterator LRUFileCachePriority::getLowestPriorityWriteIterator(std::lock_guard &) override +IFileCachePriority::WriteIterator LRUFileCachePriority::getLowestPriorityWriteIterator(std::lock_guard &) { return std::make_shared(this, queue.begin()); } -size_t LRUFileCachePriority::getElementsNum(std::lock_guard &) const override +size_t LRUFileCachePriority::getElementsNum(std::lock_guard &) const { return queue.size(); } diff --git a/src/Common/LRUFileCachePriority.h b/src/Common/LRUFileCachePriority.h index 20d1e05b9f0..250a55480f9 100644 --- a/src/Common/LRUFileCachePriority.h +++ b/src/Common/LRUFileCachePriority.h @@ -1,5 +1,6 @@ #pragma once +#include #include namespace DB @@ -10,9 +11,9 @@ namespace DB class LRUFileCachePriority : public IFileCachePriority { private: + class LRUFileCacheIterator; using LRUQueue = std::list; using LRUQueueIterator = typename LRUQueue::iterator; - class LRUFileCacheIterator; public: LRUFileCachePriority() = default; From fbaa70b3130e1ae66ec0fdefed5f2921dc55c9cf Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Thu, 28 Jul 2022 13:23:57 +0800 Subject: [PATCH 13/18] fix --- src/Common/FileCache.cpp | 4 ++-- src/Disks/ObjectStorages/LocalObjectStorage.cpp | 2 +- src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index f49f441969c..2d13c23e837 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -63,7 +63,7 @@ static bool isQueryInitialized() { return CurrentThread::isInitialized() && CurrentThread::get().getQueryContext() - && CurrentThread::getQueryId().size != 0; + && CurrentThread::getQueryId().size() != 0; } bool FileCache::isReadOnly() @@ -82,7 +82,7 @@ FileCache::QueryContextPtr FileCache::getCurrentQueryContext(std::lock_guard & /* cache_lock */) diff --git a/src/Disks/ObjectStorages/LocalObjectStorage.cpp b/src/Disks/ObjectStorages/LocalObjectStorage.cpp index c052f2f0d77..af6dab0b8a6 100644 --- a/src/Disks/ObjectStorages/LocalObjectStorage.cpp +++ b/src/Disks/ObjectStorages/LocalObjectStorage.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp index 901deeebefc..e017e19c06c 100644 --- a/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp +++ b/src/Disks/ObjectStorages/S3/S3ObjectStorage.cpp @@ -128,7 +128,7 @@ void S3ObjectStorage::removeCacheIfExists(const std::string & path_key) if (!cache || path_key.empty()) return; - IFileCache::Key key = cache->hash(path_key); + FileCache::Key key = cache->hash(path_key); cache->removeIfExists(key); } @@ -500,7 +500,7 @@ ReadSettings S3ObjectStorage::patchSettings(const ReadSettings & read_settings) ReadSettings settings{read_settings}; if (cache) { - if (IFileCache::isReadOnly()) + if (FileCache::isReadOnly()) settings.read_from_filesystem_cache_if_exists_otherwise_bypass_cache = true; settings.remote_fs_cache = cache; From 76e0aad69e361e9a25823b7c9287b785a5111517 Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Tue, 9 Aug 2022 20:21:57 +0800 Subject: [PATCH 14/18] fix --- src/Common/FileCache.cpp | 32 ++++++++++++------- src/Common/FileCache.h | 4 +-- src/Common/IFileCachePriority.h | 2 +- src/Common/LRUFileCachePriority.h | 10 +++--- src/Disks/IO/ReadBufferFromRemoteFSGather.cpp | 2 +- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 2d13c23e837..d97d20310c8 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -30,8 +30,8 @@ FileCache::FileCache( , max_element_size(cache_settings_.max_elements) , max_file_segment_size(cache_settings_.max_file_segment_size) , enable_filesystem_query_cache_limit(cache_settings_.enable_filesystem_query_cache_limit) - , main_priority(std::make_shared()) - , stash_priority(std::make_shared()) + , main_priority(std::make_unique()) + , stash_priority(std::make_unique()) , max_stash_element_size(cache_settings_.max_elements) , enable_cache_hits_threshold(cache_settings_.enable_cache_hits_threshold) , log(&Poco::Logger::get("FileCache")) @@ -145,7 +145,7 @@ void FileCache::QueryContext::remove(const Key & key, size_t offset, size_t size auto record = records.find({key, offset}); if (record != records.end()) { - record->second->remove(cache_lock); + record->second->removeAndGetNext(cache_lock); records.erase({key, offset}); } } @@ -561,7 +561,7 @@ FileCache::FileSegmentCell * FileCache::addCell( { auto remove_priority_iter = stash_priority->getLowestPriorityWriteIterator(cache_lock); stash_records.erase({remove_priority_iter->key(), remove_priority_iter->offset()}); - remove_priority_iter->remove(cache_lock); + remove_priority_iter->removeAndGetNext(cache_lock); } /// For segments that do not reach the download threshold, we do not download them, but directly read them result_state = FileSegment::State::SKIP_CACHE; @@ -645,7 +645,17 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc auto * cell_for_reserve = getCell(key, offset, cache_lock); - std::vector> ghost; + struct Segment + { + Key key; + size_t offset; + size_t size; + + Segment(Key key_, size_t offset_, size_t size_) + : key(key_), offset(offset_), size(size_) {} + }; + + std::vector ghost; std::vector trash; std::vector to_evict; @@ -669,9 +679,9 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc /// The cache corresponding to this record may be swapped out by /// other queries, so it has become invalid. removed_size += iter->size(); - ghost.push_back({iter->key(), iter->offset(), iter->size()}); + ghost.push_back(Segment(iter->key(), iter->offset(), iter->size())); /// next() - iter->remove(cache_lock); + iter->removeAndGetNext(cache_lock); } else { @@ -720,7 +730,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc } for (auto & entry : ghost) - query_context->remove(std::get<0>(entry), std::get<1>(entry), std::get<2>(entry), cache_lock); + query_context->remove(entry.key, entry.offset, entry.size, cache_lock); if (is_overflow()) return false; @@ -926,7 +936,7 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) std::lock_guard cache_lock(mutex); - std::vector to_remove; + std::vector to_remove; for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { const auto & key = it->key(); @@ -946,7 +956,7 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) || remove_persistent_files || allow_to_remove_persistent_segments_from_cache_by_default)) { - to_remove.emplace_back(file_segment); + to_remove.emplace_back(file_segment.get()); } } } @@ -981,7 +991,7 @@ void FileCache::remove( if (cell->queue_iterator) { - cell->queue_iterator->remove(cache_lock); + cell->queue_iterator->removeAndGetNext(cache_lock); } auto & offsets = files[key]; diff --git a/src/Common/FileCache.h b/src/Common/FileCache.h index 8c6a9396b43..7a25632be68 100644 --- a/src/Common/FileCache.h +++ b/src/Common/FileCache.h @@ -220,10 +220,10 @@ private: using CachedFiles = std::unordered_map; CachedFiles files; - FileCachePriorityPtr main_priority; + std::unique_ptr main_priority; FileCacheRecords stash_records; - FileCachePriorityPtr stash_priority; + std::unique_ptr stash_priority; size_t max_stash_element_size; size_t enable_cache_hits_threshold; diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index fe925fd275d..59ce3c0aebb 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -59,7 +59,7 @@ public: /// Deletes an existing cached record. And to avoid pointer suspension /// the iterator should automatically point to the next record. - virtual void remove(std::lock_guard &) = 0; + virtual void removeAndGetNext(std::lock_guard &) = 0; virtual void incrementSize(size_t, std::lock_guard &) = 0; }; diff --git a/src/Common/LRUFileCachePriority.h b/src/Common/LRUFileCachePriority.h index 250a55480f9..0f5755e1cb8 100644 --- a/src/Common/LRUFileCachePriority.h +++ b/src/Common/LRUFileCachePriority.h @@ -54,12 +54,10 @@ public: size_t hits() const override { return queue_iter->hits; } - void remove(std::lock_guard &) override + void removeAndGetNext(std::lock_guard &) override { - auto remove_iter = queue_iter; - queue_iter++; - file_cache->cache_size -= remove_iter->size; - file_cache->queue.erase(remove_iter); + file_cache->cache_size -= queue_iter->size; + queue_iter = file_cache->queue.erase(queue_iter); } void incrementSize(size_t size_increment, std::lock_guard &) override @@ -75,7 +73,7 @@ public: } private: - mutable LRUFileCachePriority * file_cache; + LRUFileCachePriority * file_cache; mutable LRUFileCachePriority::LRUQueueIterator queue_iter; }; diff --git a/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp b/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp index 3ac4ea07945..f21e2bd7642 100644 --- a/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp +++ b/src/Disks/IO/ReadBufferFromRemoteFSGather.cpp @@ -35,7 +35,7 @@ ReadBufferFromRemoteFSGather::ReadBufferFromRemoteFSGather( with_cache = settings.remote_fs_cache && settings.enable_filesystem_cache - && (!IFileCache::isReadOnly() || settings.read_from_filesystem_cache_if_exists_otherwise_bypass_cache); + && (!FileCache::isReadOnly() || settings.read_from_filesystem_cache_if_exists_otherwise_bypass_cache); } SeekableReadBufferPtr ReadBufferFromRemoteFSGather::createImplementationBuffer(const String & path, size_t file_size) From 2ae02a49214f86ff2f00e59a6d599e21db7dc2ee Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Tue, 9 Aug 2022 20:38:49 +0800 Subject: [PATCH 15/18] fix style --- src/Common/FileCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index d97d20310c8..2c8e62ac124 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -651,7 +651,7 @@ bool FileCache::tryReserve(const Key & key, size_t offset, size_t size, std::loc size_t offset; size_t size; - Segment(Key key_, size_t offset_, size_t size_) + Segment(Key key_, size_t offset_, size_t size_) : key(key_), offset(offset_), size(size_) {} }; From 9ba94e64f97d738e17c0d99dfb5ed7c556ac9956 Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Wed, 10 Aug 2022 16:11:06 +0800 Subject: [PATCH 16/18] fix --- src/Common/FileCache.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 2c8e62ac124..5a0145f0018 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -936,7 +936,7 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) std::lock_guard cache_lock(mutex); - std::vector to_remove; + std::vector to_remove; for (auto it = main_priority->getLowestPriorityReadIterator(cache_lock); it->valid(); it->next()) { const auto & key = it->key(); @@ -956,7 +956,7 @@ void FileCache::removeIfReleasable(bool remove_persistent_files) || remove_persistent_files || allow_to_remove_persistent_segments_from_cache_by_default)) { - to_remove.emplace_back(file_segment.get()); + to_remove.emplace_back(file_segment); } } } From 9b7f87677dd4fa8f09c4c58c90630941a4d97746 Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Wed, 10 Aug 2022 22:04:43 +0800 Subject: [PATCH 17/18] fix --- src/Common/FileCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/FileCache.cpp b/src/Common/FileCache.cpp index 5a0145f0018..47b7d57ae66 100644 --- a/src/Common/FileCache.cpp +++ b/src/Common/FileCache.cpp @@ -63,7 +63,7 @@ static bool isQueryInitialized() { return CurrentThread::isInitialized() && CurrentThread::get().getQueryContext() - && CurrentThread::getQueryId().size() != 0; + && !CurrentThread::getQueryId().empty(); } bool FileCache::isReadOnly() From 1aa7bbcbbd8b8c0bbd4f4dca6bc35ebde7837945 Mon Sep 17 00:00:00 2001 From: KinderRiven Date: Wed, 10 Aug 2022 23:19:26 +0800 Subject: [PATCH 18/18] fix unique_ptr --- src/Common/IFileCachePriority.h | 2 +- src/Common/LRUFileCachePriority.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Common/IFileCachePriority.h b/src/Common/IFileCachePriority.h index 59ce3c0aebb..f80266f9eea 100644 --- a/src/Common/IFileCachePriority.h +++ b/src/Common/IFileCachePriority.h @@ -18,7 +18,7 @@ class IFileCachePriority public: class IIterator; using Key = FileCacheKey; - using ReadIterator = std::shared_ptr; + using ReadIterator = std::unique_ptr; using WriteIterator = std::shared_ptr; struct FileCacheRecord diff --git a/src/Common/LRUFileCachePriority.cpp b/src/Common/LRUFileCachePriority.cpp index c54b65f6ee0..b4c4bfa338b 100644 --- a/src/Common/LRUFileCachePriority.cpp +++ b/src/Common/LRUFileCachePriority.cpp @@ -45,7 +45,7 @@ void LRUFileCachePriority::removeAll(std::lock_guard &) IFileCachePriority::ReadIterator LRUFileCachePriority::getLowestPriorityReadIterator(std::lock_guard &) { - return std::make_shared(this, queue.begin()); + return std::make_unique(this, queue.begin()); } IFileCachePriority::WriteIterator LRUFileCachePriority::getLowestPriorityWriteIterator(std::lock_guard &)