| 1 | #include "duckdb/storage/partial_block_manager.hpp" |
| 2 | |
| 3 | namespace duckdb { |
| 4 | |
| 5 | PartialBlockManager::PartialBlockManager(BlockManager &block_manager, CheckpointType checkpoint_type, |
| 6 | uint32_t max_partial_block_size, uint32_t max_use_count) |
| 7 | : block_manager(block_manager), checkpoint_type(checkpoint_type), max_partial_block_size(max_partial_block_size), |
| 8 | max_use_count(max_use_count) { |
| 9 | } |
| 10 | PartialBlockManager::~PartialBlockManager() { |
| 11 | } |
| 12 | //===--------------------------------------------------------------------===// |
| 13 | // Partial Blocks |
| 14 | //===--------------------------------------------------------------------===// |
| 15 | PartialBlockAllocation PartialBlockManager::GetBlockAllocation(uint32_t segment_size) { |
| 16 | PartialBlockAllocation allocation; |
| 17 | allocation.block_manager = &block_manager; |
| 18 | allocation.allocation_size = segment_size; |
| 19 | |
| 20 | // if the block is less than 80% full, we consider it a "partial block" |
| 21 | // which means we will try to fit it with other blocks |
| 22 | // check if there is a partial block available we can write to |
| 23 | if (segment_size <= max_partial_block_size && GetPartialBlock(segment_size, state&: allocation.partial_block)) { |
| 24 | //! there is! increase the reference count of this block |
| 25 | allocation.partial_block->state.block_use_count += 1; |
| 26 | allocation.state = allocation.partial_block->state; |
| 27 | if (checkpoint_type == CheckpointType::FULL_CHECKPOINT) { |
| 28 | block_manager.IncreaseBlockReferenceCount(block_id: allocation.state.block_id); |
| 29 | } |
| 30 | } else { |
| 31 | // full block: get a free block to write to |
| 32 | AllocateBlock(state&: allocation.state, segment_size); |
| 33 | } |
| 34 | return allocation; |
| 35 | } |
| 36 | |
| 37 | bool PartialBlockManager::HasBlockAllocation(uint32_t segment_size) { |
| 38 | return segment_size <= max_partial_block_size && |
| 39 | partially_filled_blocks.lower_bound(x: segment_size) != partially_filled_blocks.end(); |
| 40 | } |
| 41 | |
| 42 | void PartialBlockManager::AllocateBlock(PartialBlockState &state, uint32_t segment_size) { |
| 43 | D_ASSERT(segment_size <= Storage::BLOCK_SIZE); |
| 44 | if (checkpoint_type == CheckpointType::FULL_CHECKPOINT) { |
| 45 | state.block_id = block_manager.GetFreeBlockId(); |
| 46 | } else { |
| 47 | state.block_id = INVALID_BLOCK; |
| 48 | } |
| 49 | state.block_size = Storage::BLOCK_SIZE; |
| 50 | state.offset_in_block = 0; |
| 51 | state.block_use_count = 1; |
| 52 | } |
| 53 | |
| 54 | bool PartialBlockManager::GetPartialBlock(idx_t segment_size, unique_ptr<PartialBlock> &partial_block) { |
| 55 | auto entry = partially_filled_blocks.lower_bound(x: segment_size); |
| 56 | if (entry == partially_filled_blocks.end()) { |
| 57 | return false; |
| 58 | } |
| 59 | // found a partially filled block! fill in the info |
| 60 | partial_block = std::move(entry->second); |
| 61 | partially_filled_blocks.erase(position: entry); |
| 62 | |
| 63 | D_ASSERT(partial_block->state.offset_in_block > 0); |
| 64 | D_ASSERT(ValueIsAligned(partial_block->state.offset_in_block)); |
| 65 | return true; |
| 66 | } |
| 67 | |
| 68 | void PartialBlockManager::RegisterPartialBlock(PartialBlockAllocation &&allocation) { |
| 69 | auto &state = allocation.partial_block->state; |
| 70 | if (state.block_use_count < max_use_count) { |
| 71 | auto unaligned_size = allocation.allocation_size + state.offset_in_block; |
| 72 | auto new_size = AlignValue(n: unaligned_size); |
| 73 | if (new_size != unaligned_size) { |
| 74 | // register the uninitialized region so we can correctly initialize it before writing to disk |
| 75 | allocation.partial_block->AddUninitializedRegion(start: unaligned_size, end: new_size); |
| 76 | } |
| 77 | state.offset_in_block = new_size; |
| 78 | auto new_space_left = state.block_size - new_size; |
| 79 | // check if the block is STILL partially filled after adding the segment_size |
| 80 | if (new_space_left >= Storage::BLOCK_SIZE - max_partial_block_size) { |
| 81 | // the block is still partially filled: add it to the partially_filled_blocks list |
| 82 | partially_filled_blocks.insert(x: make_pair(x&: new_space_left, y: std::move(allocation.partial_block))); |
| 83 | } |
| 84 | } |
| 85 | idx_t free_space = state.block_size - state.offset_in_block; |
| 86 | auto block_to_free = std::move(allocation.partial_block); |
| 87 | if (!block_to_free && partially_filled_blocks.size() > MAX_BLOCK_MAP_SIZE) { |
| 88 | // Free the page with the least space free. |
| 89 | auto itr = partially_filled_blocks.begin(); |
| 90 | block_to_free = std::move(itr->second); |
| 91 | free_space = state.block_size - itr->first; |
| 92 | partially_filled_blocks.erase(position: itr); |
| 93 | } |
| 94 | // Flush any block that we're not going to reuse. |
| 95 | if (block_to_free) { |
| 96 | block_to_free->Flush(free_space_left: free_space); |
| 97 | AddWrittenBlock(block: block_to_free->state.block_id); |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | void PartialBlock::Merge(PartialBlock &other, idx_t offset, idx_t other_size) { |
| 102 | throw InternalException("PartialBlock::Merge not implemented for this block type" ); |
| 103 | } |
| 104 | |
| 105 | void PartialBlockManager::Merge(PartialBlockManager &other) { |
| 106 | if (&other == this) { |
| 107 | throw InternalException("Cannot merge into itself" ); |
| 108 | } |
| 109 | // for each partially filled block in the other manager, check if we can merge it into an existing block in this |
| 110 | // manager |
| 111 | for (auto &e : other.partially_filled_blocks) { |
| 112 | if (!e.second) { |
| 113 | throw InternalException("Empty partially filled block found" ); |
| 114 | } |
| 115 | auto used_space = Storage::BLOCK_SIZE - e.first; |
| 116 | if (HasBlockAllocation(segment_size: used_space)) { |
| 117 | // we can merge this block into an existing block - merge them |
| 118 | // merge blocks |
| 119 | auto allocation = GetBlockAllocation(segment_size: used_space); |
| 120 | allocation.partial_block->Merge(other&: *e.second, offset: allocation.state.offset_in_block, other_size: used_space); |
| 121 | |
| 122 | // re-register the partial block |
| 123 | allocation.state.offset_in_block += used_space; |
| 124 | RegisterPartialBlock(allocation: std::move(allocation)); |
| 125 | } else { |
| 126 | // we cannot merge this block - append it directly to the current block manager |
| 127 | partially_filled_blocks.insert(x: make_pair(x: e.first, y: std::move(e.second))); |
| 128 | } |
| 129 | } |
| 130 | // copy over the written blocks |
| 131 | for (auto &block_id : other.written_blocks) { |
| 132 | AddWrittenBlock(block: block_id); |
| 133 | } |
| 134 | other.written_blocks.clear(); |
| 135 | other.partially_filled_blocks.clear(); |
| 136 | } |
| 137 | |
| 138 | void PartialBlockManager::AddWrittenBlock(block_id_t block) { |
| 139 | auto entry = written_blocks.insert(x: block); |
| 140 | if (!entry.second) { |
| 141 | throw InternalException("Written block already exists" ); |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | void PartialBlockManager::ClearBlocks() { |
| 146 | for (auto &e : partially_filled_blocks) { |
| 147 | e.second->Clear(); |
| 148 | } |
| 149 | partially_filled_blocks.clear(); |
| 150 | } |
| 151 | |
| 152 | void PartialBlockManager::FlushPartialBlocks() { |
| 153 | for (auto &e : partially_filled_blocks) { |
| 154 | e.second->Flush(free_space_left: e.first); |
| 155 | } |
| 156 | partially_filled_blocks.clear(); |
| 157 | } |
| 158 | |
| 159 | void PartialBlockManager::Rollback() { |
| 160 | ClearBlocks(); |
| 161 | for (auto &block_id : written_blocks) { |
| 162 | block_manager.MarkBlockAsFree(block_id); |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | } // namespace duckdb |
| 167 | |