diff --git a/src/include/duckdb/storage/block_manager.hpp b/src/include/duckdb/storage/block_manager.hpp index 371535a44ac1..8ebfeb33db50 100644 --- a/src/include/duckdb/storage/block_manager.hpp +++ b/src/include/duckdb/storage/block_manager.hpp @@ -13,6 +13,7 @@ #include "duckdb/common/optional_idx.hpp" #include "duckdb/common/unordered_map.hpp" #include "duckdb/storage/block.hpp" +#include "duckdb/storage/buffer/buffer_handle.hpp" #include "duckdb/storage/storage_info.hpp" namespace duckdb { @@ -182,6 +183,12 @@ class BlockManager { return reinterpret_cast(*this); } +public: + //! Begin deferring eviction queue additions (call before checkpoint writes) + void BeginDeferEviction(); + //! End deferring and release all retained blocks to the eviction queue + void EndDeferEviction(); + protected: //! A flag to be flipped in the destructor of the subclass, which is called first. //! Relevant for some Windows edge cases. @@ -192,6 +199,10 @@ class BlockManager { mutex blocks_lock; //! A mapping of block id -> BlockHandle unordered_map> blocks; + //! When true, ConvertToPersistent pins blocks instead of adding to eviction queue + bool defer_eviction = false; + //! Blocks pinned during deferred eviction (kept alive until EndDeferEviction) + vector deferred_pins; //! The metadata manager unique_ptr metadata_manager; //! The allocation size of blocks managed by this block manager. Defaults to DEFAULT_BLOCK_ALLOC_SIZE diff --git a/src/storage/buffer/block_manager.cpp b/src/storage/buffer/block_manager.cpp index 0ac33b53e5b2..b46b89ed2bbb 100644 --- a/src/storage/buffer/block_manager.cpp +++ b/src/storage/buffer/block_manager.cpp @@ -104,10 +104,13 @@ shared_ptr BlockManager::ConvertToPersistent(QueryContext context, old_handle.Destroy(); old_block.reset(); - // potentially purge the queue - auto purge_queue = buffer_manager.GetBufferPool().AddToEvictionQueue(new_block); - if (purge_queue) { - buffer_manager.GetBufferPool().PurgeQueue(*new_block); + if (defer_eviction) { + deferred_pins.push_back(buffer_manager.Pin(new_block)); + } else { + auto purge_queue = buffer_manager.GetBufferPool().AddToEvictionQueue(new_block); + if (purge_queue) { + buffer_manager.GetBufferPool().PurgeQueue(*new_block); + } } return new_block; } @@ -148,4 +151,17 @@ void BlockManager::Write(QueryContext context, FileBuffer &block, block_id_t blo void BlockManager::Truncate() { } +void BlockManager::BeginDeferEviction() { + defer_eviction = true; +} + +void BlockManager::EndDeferEviction() { + defer_eviction = false; + // Release all pins — blocks become eligible for eviction + for (auto &pin : deferred_pins) { + pin.Destroy(); + } + deferred_pins.clear(); +} + } // namespace duckdb diff --git a/src/storage/checkpoint_manager.cpp b/src/storage/checkpoint_manager.cpp index a33f4e05c435..7543c28f6c22 100644 --- a/src/storage/checkpoint_manager.cpp +++ b/src/storage/checkpoint_manager.cpp @@ -247,6 +247,7 @@ void SingleFileCheckpointWriter::CreateCheckpoint() { dependency_manager.ReorderEntries(catalog_entries); // write the actual data into the database + block_manager.BeginDeferEviction(); // Create a serializer to write the checkpoint data // The serialized format is roughly: @@ -277,6 +278,8 @@ void SingleFileCheckpointWriter::CreateCheckpoint() { metadata_writer->Flush(); table_metadata_writer->Flush(); + block_manager.EndDeferEviction(); + auto debug_checkpoint_abort = Settings::Get(db.GetDatabase()); if (debug_checkpoint_abort == CheckpointAbort::DEBUG_ABORT_BEFORE_HEADER) { throw FatalException("Checkpoint aborted before header write because of PRAGMA checkpoint_abort flag");