Coverage Report

Created: 2026-09-28 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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