/src/rocksdb/db/blob/blob_file_partition_manager.h
Line | Count | Source |
1 | | // Copyright (c) Meta Platforms, Inc. and affiliates. |
2 | | // This source code is licensed under both the GPLv2 (found in the |
3 | | // COPYING file in the root directory) and Apache 2.0 License |
4 | | // (found in the LICENSE.Apache file in the root directory). |
5 | | #pragma once |
6 | | |
7 | | #include <atomic> |
8 | | #include <cstdint> |
9 | | #include <deque> |
10 | | #include <functional> |
11 | | #include <memory> |
12 | | #include <string> |
13 | | #include <unordered_map> |
14 | | #include <vector> |
15 | | |
16 | | #include "db/blob/blob_file_addition.h" |
17 | | #include "db/blob/blob_file_garbage.h" |
18 | | #include "db/blob/blob_log_format.h" |
19 | | #include "db/blob/blob_write_batch_transformer.h" |
20 | | #include "port/port.h" |
21 | | #include "rocksdb/compression_type.h" |
22 | | #include "rocksdb/env.h" |
23 | | #include "rocksdb/file_system.h" |
24 | | #include "rocksdb/options.h" |
25 | | #include "rocksdb/rocksdb_namespace.h" |
26 | | #include "rocksdb/slice.h" |
27 | | #include "rocksdb/status.h" |
28 | | #include "util/hash_containers.h" |
29 | | namespace ROCKSDB_NAMESPACE { |
30 | | |
31 | | class BlobFileCache; |
32 | | class BlobFileCompletionCallback; |
33 | | class BlobIndex; |
34 | | class BlobLogWriter; |
35 | | class FilePrefetchBuffer; |
36 | | class IOTracer; |
37 | | class Logger; |
38 | | class PinnableSlice; |
39 | | class Version; |
40 | | class WritableFileWriter; |
41 | | struct FileOptions; |
42 | | struct ReadOptions; |
43 | | |
44 | | // Manages per-partition blob files for write-path blob direct write. |
45 | | // |
46 | | // The v1 design keeps each memtable switch as one FIFO generation batch. |
47 | | // Direct-write files for that memtable are sealed and registered when the |
48 | | // matching flush commits. The current implementation still serializes blob |
49 | | // appends through one manager mutex; follow-up PRs can add independent |
50 | | // per-partition locks to allow concurrent blob writes without changing the |
51 | | // generation/flush contract. Follow-up PRs can also broaden compatibility as |
52 | | // the feature matures. |
53 | | class BlobFilePartitionManager { |
54 | | public: |
55 | | using FileNumberAllocator = std::function<uint64_t()>; |
56 | | |
57 | | // Creates the per-column-family manager for write-path blob files. |
58 | | BlobFilePartitionManager( |
59 | | uint32_t num_partitions, |
60 | | std::shared_ptr<BlobFilePartitionStrategy> strategy, |
61 | | FileNumberAllocator file_number_allocator, FileSystem* fs, |
62 | | SystemClock* clock, Statistics* statistics, |
63 | | const FileOptions& file_options, std::string db_path, |
64 | | std::string column_family_name, uint64_t blob_file_size, bool use_fsync, |
65 | | uint64_t blob_writer_max_buffer_size, BlobFileCache* blob_file_cache, |
66 | | BlobFileCompletionCallback* blob_callback, |
67 | | const std::vector<std::shared_ptr<EventListener>>& listeners, |
68 | | FileChecksumGenFactory* file_checksum_gen_factory, |
69 | | const FileTypeSet& checksum_handoff_file_types, |
70 | | const std::shared_ptr<IOTracer>& io_tracer, std::string db_id, |
71 | | std::string db_session_id, Logger* info_log); |
72 | | |
73 | | // Evicts cached readers for manager-owned files during shutdown. |
74 | | ~BlobFilePartitionManager(); |
75 | | |
76 | | // Appends one blob record to a partition file and returns the resulting |
77 | | // BlobIndex location metadata. |
78 | | Status WriteBlob(const WriteOptions& write_options, uint32_t column_family_id, |
79 | | CompressionType compression, const Slice& key, |
80 | | const Slice& value, uint64_t* blob_file_number, |
81 | | uint64_t* blob_offset, uint64_t* blob_size, |
82 | | const BlobDirectWriteSettings& settings, |
83 | | const uint32_t* partition_idx = nullptr); |
84 | | |
85 | | // Selects the partition to use for all blob-backed columns of one PutEntity |
86 | | // operation. The return value is already normalized to [0, num_partitions_). |
87 | | uint32_t SelectWideColumnPartition(uint32_t column_family_id, |
88 | | const Slice& key, |
89 | | const WideColumns& columns) const; |
90 | | |
91 | | // Move the current active partition files into the generation of the |
92 | | // memtable `memtable_id`, which just became immutable. Called from |
93 | | // SwitchMemtable() while DB mutex is held. No I/O is performed here. |
94 | | void RotateCurrentGeneration(uint64_t memtable_id); |
95 | | |
96 | | // Seal the queued generations of the memtables `memtable_ids` and return all |
97 | | // blob additions and initial-garbage updates that must be registered with |
98 | | // the flush of those memtables. Concurrent flushes pick different |
99 | | // memtables, so generations are matched by memtable ID, not by queue |
100 | | // position. Generations stay queued until CommitFlushedGenerations() removes |
101 | | // them. If `generation_blob_file_numbers` is non-null, it receives the |
102 | | // sealed blob file numbers for each memtable, in `memtable_ids` order. |
103 | | Status PrepareFlushAdditions(const WriteOptions& write_options, |
104 | | const std::vector<uint64_t>& memtable_ids, |
105 | | std::vector<BlobFileAddition>* additions, |
106 | | std::vector<BlobFileGarbage>* garbages, |
107 | | std::vector<std::vector<uint64_t>>* |
108 | | generation_blob_file_numbers = nullptr); |
109 | | |
110 | | // Remove the generations of the memtables older than |
111 | | // `earliest_unflushed_memtable_id`, whose flush results are installed, and |
112 | | // stop tracking their sealed files as manager-owned. A flush that finishes |
113 | | // before an older one is installed with it, or rolled back if it fails, so |
114 | | // the flush that prepared a generation cannot remove it on its own. |
115 | | void CommitFlushedGenerations(uint64_t earliest_unflushed_memtable_id); |
116 | | |
117 | | // In v1 flush-on-write mode each AddRecord flushes to the OS immediately, so |
118 | | // there is nothing extra to do for the non-sync write path. |
119 | 0 | Status FlushAllOpenFiles(const WriteOptions& /*write_options*/) { |
120 | 0 | return Status::OK(); |
121 | 0 | } |
122 | | |
123 | | // Sync the active open blob files before a sync WAL write. |
124 | | Status SyncAllOpenFiles(const WriteOptions& write_options); |
125 | | |
126 | | // Returns blob files still owned by the manager and therefore not yet safe |
127 | | // to consider obsolete. |
128 | | void GetActiveBlobFileNumbers(UnorderedSet<uint64_t>* file_numbers) const; |
129 | | |
130 | | // Returns sealed blob files that are still reachable through live memtables |
131 | | // or old SuperVersions and therefore must not be purged yet. |
132 | | void GetProtectedBlobFileNumbers(UnorderedSet<uint64_t>* file_numbers) const; |
133 | | |
134 | | // Returns true when the blob file is still owned by the write path or |
135 | | // protected by a live memtable / old SuperVersion. |
136 | | bool IsTrackedBlobFileNumber(uint64_t file_number) const; |
137 | | |
138 | 0 | void SetBlobWriterMaxBufferSize(uint64_t blob_writer_max_buffer_size) { |
139 | 0 | blob_writer_max_buffer_size_.store(blob_writer_max_buffer_size, |
140 | 0 | std::memory_order_relaxed); |
141 | 0 | } |
142 | | |
143 | | // Increments / decrements memtable-held protection on sealed blob files. |
144 | | void ProtectSealedBlobFileNumbers(const std::vector<uint64_t>& file_numbers); |
145 | | void UnprotectSealedBlobFileNumbers( |
146 | | const std::vector<uint64_t>& file_numbers); |
147 | | |
148 | | // Stops protecting file numbers that are now registered in MANIFEST. |
149 | | void RemoveFilePartitionMappings(const std::vector<uint64_t>& file_numbers); |
150 | | |
151 | | // Marks blob records from a failed transformed write as initial garbage for |
152 | | // the target blob file while keeping the physical bytes in place. |
153 | | // Returns Corruption if the manager can no longer match the blob file. |
154 | | Status MarkBlobWriteAsGarbage(uint64_t file_number, uint64_t blob_count, |
155 | | uint64_t blob_bytes); |
156 | | |
157 | | // Resolves a direct-write BlobIndex by consulting manifest-visible state |
158 | | // first, then falling back to direct blob-file reads only when the target |
159 | | // blob file is still write-path-owned and therefore not yet tracked by |
160 | | // Version. Existing manifest-visible read results, including I/O failures, |
161 | | // are returned directly rather than masked by fallback logic. |
162 | | static Status ResolveBlobDirectWriteIndex( |
163 | | const ReadOptions& read_options, const Slice& user_key, |
164 | | const BlobIndex& blob_idx, const Version* version, |
165 | | BlobFileCache* blob_file_cache, FilePrefetchBuffer* prefetch_buffer, |
166 | | PinnableSlice* blob_value, uint64_t* bytes_read); |
167 | | |
168 | | private: |
169 | | struct Partition { |
170 | | // Active writer for this partition, or null when the partition is idle. |
171 | | std::unique_ptr<BlobLogWriter> writer; |
172 | | // Metadata tracked while the active file remains open. |
173 | | uint64_t file_number = 0; |
174 | | uint64_t file_size = 0; |
175 | | uint64_t blob_count = 0; |
176 | | uint64_t total_blob_bytes = 0; |
177 | | // Failed transformed writes already appended to this physical file. |
178 | | uint64_t garbage_blob_count = 0; |
179 | | uint64_t garbage_blob_bytes = 0; |
180 | | uint32_t column_family_id = 0; |
181 | | CompressionType compression = kNoCompression; |
182 | | bool sync_required = false; |
183 | | }; |
184 | | |
185 | | struct DeferredFile { |
186 | 0 | DeferredFile() = default; |
187 | 0 | DeferredFile(DeferredFile&&) = default; |
188 | | DeferredFile& operator=(DeferredFile&&) = default; |
189 | | ~DeferredFile(); |
190 | | |
191 | | // Blob writer moved out of an active partition at memtable rotation. |
192 | | std::unique_ptr<BlobLogWriter> writer; |
193 | | // Metadata preserved until flush preparation seals the file. |
194 | | uint64_t file_number = 0; |
195 | | uint64_t blob_count = 0; |
196 | | uint64_t total_blob_bytes = 0; |
197 | | // Failed transformed writes already appended before the file is sealed. |
198 | | uint64_t garbage_blob_count = 0; |
199 | | uint64_t garbage_blob_bytes = 0; |
200 | | // Set once sealing fails. The writer cannot be finalized again, and |
201 | | // memtables still reference the file, so every flush of its generation |
202 | | // fails with this status. |
203 | | Status seal_status; |
204 | | }; |
205 | | |
206 | | struct SealedFile { |
207 | | // Blob-file metadata ready to be added to MANIFEST. |
208 | | BlobFileAddition addition; |
209 | | // Initial unreachable bytes that should be registered as garbage |
210 | | // alongside the file addition. |
211 | | uint64_t garbage_blob_count = 0; |
212 | | uint64_t garbage_blob_bytes = 0; |
213 | | }; |
214 | | |
215 | | struct GenerationBatch { |
216 | | // The memtable that became immutable when this generation was rotated. |
217 | | uint64_t memtable_id = 0; |
218 | | // Files that were still open when the memtable became immutable. |
219 | | std::deque<DeferredFile> deferred_files; |
220 | | // Files already sealed for this memtable generation. |
221 | | std::vector<SealedFile> sealed_files; |
222 | | }; |
223 | | |
224 | | // Clears all active-file bookkeeping from a partition after sealing. |
225 | | void ResetPartitionState(Partition* partition); |
226 | | // Returns the queued generation of `memtable_id`, or null. |
227 | | GenerationBatch* FindPendingGeneration(uint64_t memtable_id); |
228 | | // Moves a partition's open file out of the partition and resets it. |
229 | | DeferredFile DetachPartitionFile(Partition* partition); |
230 | | // Marks a blob file as manager-owned until it is committed or abandoned. |
231 | | void AddFilePartitionMapping(uint64_t file_number, uint32_t partition_idx); |
232 | | // Stops tracking a blob file after open failure. |
233 | | void RemoveFilePartitionMapping(uint64_t file_number); |
234 | | |
235 | | // Opens a new active blob file for one partition. |
236 | | Status OpenNewBlobFile(Partition* partition, uint32_t column_family_id, |
237 | | CompressionType compression, uint32_t partition_idx); |
238 | | // Finalizes one active partition file and returns the MANIFEST addition. |
239 | | // Sets `*space_limit_reached` if the SstFileManager's max allowed space is |
240 | | // reached; the seal still succeeds. |
241 | | Status SealActiveBlobFile(const WriteOptions& write_options, |
242 | | Partition* partition, SealedFile* sealed_file, |
243 | | bool* space_limit_reached); |
244 | | // Finalizes one deferred file that was carried over to flush preparation. |
245 | | Status SealDeferredFile(const WriteOptions& write_options, |
246 | | DeferredFile* deferred, SealedFile* sealed_file); |
247 | | // Appends the footer, reports file completion, and builds the resulting |
248 | | // BlobFileAddition for one sealed blob file. |
249 | | Status FinalizeBlobFile(const WriteOptions& write_options, |
250 | | BlobLogWriter* writer, uint64_t file_number, |
251 | | uint64_t blob_count, uint64_t total_blob_bytes, |
252 | | BlobFileAddition* addition, |
253 | | bool* space_limit_reached); |
254 | | // Appends one initial-garbage record to the flush output if any failed |
255 | | // transformed writes targeted the sealed blob file. |
256 | | static void AddSealedFileGarbage(const SealedFile& sealed_file, |
257 | | std::vector<BlobFileGarbage>* garbages); |
258 | | // Returns true if the active open file matches `file_number` and consumes |
259 | | // the failed-write garbage accounting. |
260 | | static bool MarkPartitionGarbage(Partition* partition, uint64_t file_number, |
261 | | uint64_t blob_count, uint64_t blob_bytes); |
262 | | // Returns true if the deferred flush-preparation file matches `file_number` |
263 | | // and consumes the failed-write garbage accounting. |
264 | | static bool MarkDeferredFileGarbage(DeferredFile* deferred, |
265 | | uint64_t file_number, uint64_t blob_count, |
266 | | uint64_t blob_bytes); |
267 | | // Returns true if one already-sealed file matches `file_number` and |
268 | | // consumes the failed-write garbage accounting. |
269 | | static bool MarkSealedFileGarbage(std::vector<SealedFile>* sealed_files, |
270 | | uint64_t file_number, uint64_t blob_count, |
271 | | uint64_t blob_bytes); |
272 | | // Seeds the blob cache with the original uncompressed value when configured. |
273 | | Status MaybePrepopulateBlobCache(const BlobDirectWriteSettings& settings, |
274 | | const Slice& original_value, |
275 | | uint64_t blob_file_number, |
276 | | uint64_t blob_offset); |
277 | | |
278 | | // Configured partition fanout and write-selection strategy. |
279 | | const uint32_t num_partitions_; |
280 | | std::shared_ptr<BlobFilePartitionStrategy> strategy_; |
281 | | // Shared services needed to create, track, and cache blob files. |
282 | | FileNumberAllocator file_number_allocator_; |
283 | | FileSystem* fs_; |
284 | | SystemClock* clock_; |
285 | | Statistics* statistics_; |
286 | | FileOptions file_options_; |
287 | | // Column-family context used for file naming and callbacks. |
288 | | std::string db_path_; |
289 | | std::string column_family_name_; |
290 | | // File creation policy and shared blob-file infrastructure. |
291 | | uint64_t blob_file_size_; |
292 | | bool use_fsync_; |
293 | | std::atomic<uint64_t> blob_writer_max_buffer_size_; |
294 | | BlobFileCache* blob_file_cache_; |
295 | | BlobFileCompletionCallback* blob_callback_; |
296 | | std::vector<std::shared_ptr<EventListener>> listeners_; |
297 | | FileChecksumGenFactory* file_checksum_gen_factory_; |
298 | | FileTypeSet checksum_handoff_file_types_; |
299 | | std::shared_ptr<IOTracer> io_tracer_; |
300 | | // DB identity used when constructing cache keys. |
301 | | std::string db_id_; |
302 | | std::string db_session_id_; |
303 | | Logger* info_log_; |
304 | | |
305 | | // One active writer slot per configured partition. |
306 | | std::vector<std::unique_ptr<Partition>> partitions_; |
307 | | |
308 | | // Protects partition state and generation queues. |
309 | | mutable port::Mutex mutex_; |
310 | | // Files already sealed while the current mutable memtable was active. |
311 | | std::vector<SealedFile> current_generation_sealed_files_; |
312 | | // Files whose seal failed while the current mutable memtable was active. |
313 | | std::vector<DeferredFile> current_generation_failed_files_; |
314 | | // FIFO immutable memtable generations waiting to be flushed. |
315 | | std::deque<GenerationBatch> pending_generations_; |
316 | | |
317 | | // Tracks blob files still owned by active or deferred write-path state until |
318 | | // MANIFEST commit publishes them. |
319 | | std::unordered_map<uint64_t, uint32_t> file_to_partition_; |
320 | | // Sealed direct-write files that remain reachable from live memtables, such |
321 | | // as old SuperVersions serving lazy iterator reads after flush commit. |
322 | | std::unordered_map<uint64_t, uint32_t> protected_blob_file_refs_; |
323 | | // Protects file_to_partition_ and protected_blob_file_refs_. |
324 | | mutable port::RWMutex file_partition_mutex_; |
325 | | }; |
326 | | |
327 | | } // namespace ROCKSDB_NAMESPACE |