/src/rocksdb/db/wide/read_path_blob_resolver.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 | | |
6 | | #pragma once |
7 | | |
8 | | #include <memory> |
9 | | #include <string> |
10 | | #include <utility> |
11 | | #include <vector> |
12 | | |
13 | | #include "db/blob/blob_constants.h" |
14 | | #include "db/blob/blob_fetcher.h" |
15 | | #include "db/blob/blob_index.h" |
16 | | #include "rocksdb/cleanable.h" |
17 | | #include "rocksdb/options.h" |
18 | | #include "rocksdb/slice.h" |
19 | | #include "rocksdb/status.h" |
20 | | #include "rocksdb/wide_columns.h" |
21 | | |
22 | | namespace ROCKSDB_NAMESPACE { |
23 | | |
24 | | class Version; |
25 | | class SameFileBlobReader; |
26 | | |
27 | | // TODO: ReadPathBlobResolver and CompactionBlobResolver (in |
28 | | // compaction_iterator.h) share significant logic for blob column resolution |
29 | | // and caching. A refactoring into a common base class or shared utility |
30 | | // could reduce duplication. The two classes differ in fetcher ownership and |
31 | | // their surrounding contexts (read path vs compaction path with stats |
32 | | // tracking). |
33 | | // |
34 | | // Enables lazy (on-demand) resolution of blob column values in the read path. |
35 | | // When a wide-column entity contains blob references (V2 format), the resolver |
36 | | // stores the blob metadata and fetches blob values only when explicitly |
37 | | // requested via ResolveColumn(). Resolved values are cached to avoid |
38 | | // re-fetching. |
39 | | // |
40 | | // Used by both the iterator path (DBIter) and the point-lookup path |
41 | | // (GetEntity via PinnableWideColumns). |
42 | | // |
43 | | // Thread safety: not thread-safe. A resolver instance is used by a single |
44 | | // thread at a time. The Version* must remain valid for the lifetime of the |
45 | | // resolver (ensured by SuperVersion pinning in the caller). |
46 | | class ReadPathBlobResolver { |
47 | | public: |
48 | | ReadPathBlobResolver(const Version* version, const ReadOptions& read_options, |
49 | | BlobFileCache* blob_file_cache = nullptr, |
50 | | bool allow_write_path_fallback = false); |
51 | | |
52 | | // Reset the resolver for a new entity. Clears all cached values. |
53 | | // The columns and blob_columns pointers must remain valid for the lifetime |
54 | | // of the resolver (or until the next Reset call). |
55 | | // |
56 | | // same_file_reader: when non-null, same-file ("embedded") blob references are |
57 | | // resolved against it (the SST that held the entity) via an |
58 | | // EmbeddedAwareBlobFetcher; it must outlive the resolver (ensured, on the |
59 | | // lazy read path, by the same SuperVersion pin that keeps the Version alive, |
60 | | // combined with immortal table readers when max_open_files == -1). When null |
61 | | // (e.g. the DBIter path, which resolves embedded refs separately), only |
62 | | // separate-file references are resolvable. |
63 | | void Reset(const Slice& user_key, const std::vector<WideColumn>* columns, |
64 | | const std::vector<std::pair<size_t, BlobIndex>>* blob_columns, |
65 | | const SameFileBlobReader* same_file_reader = nullptr); |
66 | | |
67 | | // Resolve the value for the column at the given index. |
68 | | // For blob columns, fetches the blob value from the blob file (or returns |
69 | | // from cache if already resolved). For inline columns, returns the inline |
70 | | // value directly. |
71 | | // Returns an error status if: |
72 | | // - column_index is out of bounds |
73 | | // - I/O error occurred while fetching the blob |
74 | | Status ResolveColumn(size_t column_index, Slice* resolved_value); |
75 | | |
76 | | // Resolve a byte sub-range [range_offset, range_offset + range_length) of the |
77 | | // column at `column_index` into *result (zero-copy). `range_length` may be |
78 | | // larger than the remaining bytes (or SIZE_MAX for "to the end"); it is |
79 | | // clamped to the column's logical size. An offset at/past the end yields an |
80 | | // empty result (not an error). |
81 | | // |
82 | | // Reads only the requested bytes -- skipping whole-record checksum |
83 | | // verification and blob-cache population -- only for a strict sub-range of an |
84 | | // uncompressed blob reference (in a separate blob file, or |
85 | | // embedded/same-file) that is not already resolved, when !force_verify and |
86 | | // the read is servable (a Version-backed range read for separate-file refs, |
87 | | // or a SameFileBlobReader for embedded refs). Every other case (inline |
88 | | // column, already-resolved column, inlined blob, compressed reference, a |
89 | | // whole-column read, or force_verify) resolves the whole column (caching it, |
90 | | // verifying under ReadOptions::verify_checksums) and slices the requested |
91 | | // range out of it. |
92 | | // |
93 | | // Unlike ResolveColumn, a partial read is NOT cached: *result owns (or pins) |
94 | | // its own bytes, independent of this resolver's whole-column cache. |
95 | | Status ResolveColumnRange(size_t column_index, uint64_t range_offset, |
96 | | size_t range_length, bool force_verify, |
97 | | PinnableSlice* result); |
98 | | |
99 | | // How the cross-key batch coalescing path |
100 | | // (LazyWideColumnsBatch::MultiResolve) should resolve a byte-range read of |
101 | | // one column. |
102 | | enum class LazyColumnReadPlan { |
103 | | // Resolve individually via ResolveColumnRange (inline column, already |
104 | | // resolved, TTL-inlined blob, empty range, force-verify, or an unservable |
105 | | // reference); no cross-key coalescing benefit. |
106 | | kServeIndividually, |
107 | | // Fetch the whole blob value, then cache + slice; coalesce per blob file. |
108 | | kFetchWholeSeparateFile, |
109 | | // Fetch the whole embedded value, then cache + slice; coalesce per SST. |
110 | | kFetchWholeSameFile, |
111 | | // Fetch only the (uncompressed) sub-range; coalesce per blob file. |
112 | | kFetchRangeSeparateFile, |
113 | | // Fetch only the (uncompressed) embedded sub-range; coalesce per SST. |
114 | | kFetchRangeSameFile, |
115 | | }; |
116 | | |
117 | | struct LazyColumnReadClassification { |
118 | | LazyColumnReadPlan plan = LazyColumnReadPlan::kServeIndividually; |
119 | | // For fetch plans: the reference to read (owned by blob_columns_). |
120 | | const BlobIndex* blob_index = nullptr; |
121 | | // For range-fetch plans: the effective (clamped) sub-range to read. |
122 | | uint64_t range_offset = 0; |
123 | | size_t range_length = 0; |
124 | | }; |
125 | | |
126 | | // Classifies how a byte-range read of `column_index` should be resolved by |
127 | | // the batch coalescing path. The decision mirrors ResolveColumnRange exactly, |
128 | | // so that pre-fetching the whole/range values (per the returned plan) and |
129 | | // then calling ResolveColumnRange for the whole reads yields the same bytes |
130 | | // with no redundant I/O. Out-of-bounds / non-blob / cached / inlined / |
131 | | // empty-range / force-verify / unservable reads return kServeIndividually. |
132 | | LazyColumnReadClassification ClassifyColumnRange(size_t column_index, |
133 | | uint64_t range_offset, |
134 | | size_t range_length, |
135 | | bool force_verify) const; |
136 | | |
137 | | // Inserts an externally fetched whole column value into the resolver's cache |
138 | | // (idempotent: if the column is already cached the passed value is dropped |
139 | | // and the existing entry kept). Used by the batch coalescing path, which |
140 | | // fetches whole blob values in bulk; a subsequent ResolveColumnRange of that |
141 | | // column then slices from the cache with no I/O. |
142 | | void AdoptResolvedWholeColumn(size_t column_index, PinnableSlice&& value); |
143 | | |
144 | | // Accessors used by the batch coalescing path to group and dispatch reads. |
145 | 0 | const Version* version() const { return blob_fetcher_.version(); } |
146 | 0 | const SameFileBlobReader* same_file_reader() const { |
147 | 0 | return same_file_reader_; |
148 | 0 | } |
149 | 0 | const ReadOptions& read_options() const { |
150 | 0 | return blob_fetcher_.read_options(); |
151 | 0 | } |
152 | 0 | const Slice& user_key() const { return user_key_; } |
153 | | |
154 | | // Resolve multiple columns in the order provided by `column_indices`. |
155 | | // Resolved blob values are cached exactly as if ResolveColumn() were called |
156 | | // repeatedly. |
157 | | Status ResolveColumns(const std::vector<size_t>& column_indices, |
158 | | std::vector<Slice>* resolved_values); |
159 | | |
160 | | // Resolve all unresolved blob columns at once. |
161 | | Status ResolveAllColumns(); |
162 | | |
163 | | // Check if the column at the given index is an unresolved blob reference. |
164 | | // Returns false if column_index is out of bounds or the column is inline |
165 | | // or already resolved. |
166 | | bool IsUnresolvedColumn(size_t column_index) const; |
167 | | |
168 | | // Returns true if any blob columns have not yet been resolved. |
169 | | bool HasUnresolvedColumns() const; |
170 | | |
171 | | // Returns the total number of columns in the entity. |
172 | | size_t NumColumns() const; |
173 | | |
174 | | // Register a cleanup function that will be called when the resolver is |
175 | | // destroyed. Used to pin resources (e.g., SuperVersion) that must remain |
176 | | // alive while the resolver exists. |
177 | | void RegisterCleanup(Cleanable::CleanupFunction function, void* arg1, |
178 | 0 | void* arg2) { |
179 | 0 | cleanable_.RegisterCleanup(function, arg1, arg2); |
180 | 0 | } |
181 | | |
182 | | private: |
183 | | // Maps the public (ReadOptions::verify_checksums, force_verify) pair to the |
184 | | // internal 3-valued verify policy, once at this boundary (see |
185 | | // BlobVerifyPolicy). Every downstream blob read is driven by the resulting |
186 | | // policy rather than by re-deriving verification from two bools. |
187 | | static BlobVerifyPolicy DeriveVerifyPolicy(bool verify_checksums, |
188 | | bool force_verify); |
189 | | |
190 | | // Reads a blob reference into *out, applying `policy`. range_length == |
191 | | // kWholeBlobLength selects the whole value; any other length selects the |
192 | | // strict sub-range [range_offset, range_offset + range_length). Routes |
193 | | // same-file ("embedded") references to the current SST's SameFileBlobReader |
194 | | // and separate-file references to the Version-backed fetcher. |
195 | | Status FetchBlobRef(const BlobIndex& blob_index, uint64_t range_offset, |
196 | | size_t range_length, BlobVerifyPolicy policy, |
197 | | PinnableSlice* out); |
198 | | |
199 | | // Shared implementation of ResolveColumn / the whole-column path of |
200 | | // ResolveColumnRange: resolves and caches the whole column value under the |
201 | | // given verify policy (kVerifyIfPresent forces whole-record verification even |
202 | | // when ReadOptions::verify_checksums is off; see LazyColumnReadRequest). |
203 | | Status ResolveColumnInternal(size_t column_index, BlobVerifyPolicy policy, |
204 | | Slice* resolved_value); |
205 | | |
206 | | // Owns its ReadOptions: a resolver's lifetime is independent of the caller |
207 | | // that created it (e.g. it may outlive the originating ReadOptions once |
208 | | // returned as part of a lazy result). |
209 | | OwningVersionBlobFetcher blob_fetcher_; |
210 | | |
211 | | Slice user_key_; |
212 | | const std::vector<WideColumn>* columns_ = nullptr; |
213 | | const std::vector<std::pair<size_t, BlobIndex>>* blob_columns_ = nullptr; |
214 | | // Non-null on the lazy read path for entities with same-file blob references; |
215 | | // see Reset(). |
216 | | const SameFileBlobReader* same_file_reader_ = nullptr; |
217 | | |
218 | | // Cache for resolved blob values to avoid re-fetching. |
219 | | // Uses a vector of (column_index, PinnableSlice) pairs. Typical entities |
220 | | // have few blob columns (<5), making linear scan cheaper than hash map |
221 | | // overhead. PinnableSlice values need stable addresses, so we use |
222 | | // unique_ptr to prevent invalidation when the vector grows. |
223 | | std::vector<std::pair<size_t, std::unique_ptr<PinnableSlice>>> |
224 | | resolved_cache_; |
225 | | |
226 | | // Cleanable for pinning resources (e.g., SuperVersion). |
227 | | Cleanable cleanable_; |
228 | | }; |
229 | | |
230 | | } // namespace ROCKSDB_NAMESPACE |