Coverage Report

Created: 2026-08-14 08:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/db/seqno_to_time_mapping.h
Line
Count
Source
1
//  Copyright (c) Meta Platforms, Inc. and affiliates.
2
//
3
//  This source code is licensed under both the GPLv2 (found in the
4
//  COPYING file in the root directory) and Apache 2.0 License
5
//  (found in the LICENSE.Apache file in the root directory).
6
7
#pragma once
8
9
#include <algorithm>
10
#include <cinttypes>
11
#include <cstdint>
12
#include <deque>
13
#include <functional>
14
#include <iterator>
15
#include <string>
16
17
#include "db/dbformat.h"
18
#include "rocksdb/status.h"
19
#include "rocksdb/types.h"
20
21
namespace ROCKSDB_NAMESPACE {
22
23
constexpr uint64_t kUnknownTimeBeforeAll = 0;
24
constexpr SequenceNumber kUnknownSeqnoBeforeAll = 0;
25
26
// Maximum number of entries can be encoded into SST. The data is delta encode
27
// so the maximum data usage for each SST is < 0.3K
28
constexpr uint64_t kMaxSeqnoTimePairsPerSST = 100;
29
30
// Maximum number of entries per CF. If there's only CF with this feature on,
31
// the max span divided by this number, so for example, if
32
// preclude_last_level_data_seconds = 100000 (~1day), then it will sample the
33
// seqno -> time every 1000 seconds (~17minutes). Then the maximum entry it
34
// needs is 100.
35
// When there are multiple CFs having this feature on, the sampling cadence is
36
// determined by the smallest setting, the capacity is determined the largest
37
// setting, also it's caped by kMaxSeqnoTimePairsPerCF * 10.
38
constexpr uint64_t kMaxSeqnoTimePairsPerCF = 100;
39
40
constexpr uint64_t kMaxSeqnoToTimeEntries = kMaxSeqnoTimePairsPerCF * 10;
41
42
// SeqnoToTimeMapping stores a sampled mapping from sequence numbers to
43
// unix times (seconds since epoch). This information provides rough bounds
44
// between sequence numbers and their write times, but is primarily designed
45
// for getting a best lower bound on the sequence number of data written no
46
// later than a specified time.
47
//
48
// For ease of sampling, it is assumed that the recorded time in each pair
49
// comes at or after the sequence number and before the next sequence number,
50
// so this example:
51
//
52
// Seqno: 10,       11, ... 20,       21, ... 30,       31, ...
53
// Time:  ...  500      ...      600      ...      700      ...
54
//
55
// would be represented as
56
//   10 -> 500
57
//   20 -> 600
58
//   30 -> 700
59
//
60
// In typical operation, the list is in "enforced" operation to maintain
61
// invariants on sortedness, capacity, and time span of entries. However, some
62
// operations will put the object into "unenforced" mode where those invariants
63
// are relaxed until explicitly or implicitly re-enforced (which will sort and
64
// filter the data).
65
//
66
// NOT thread safe - requires external synchronization, except a const
67
// object allows concurrent reads.
68
class SeqnoToTimeMapping {
69
 public:
70
  // A simple struct for sequence number to time pair
71
  struct SeqnoTimePair {
72
    SequenceNumber seqno = 0;
73
    uint64_t time = 0;
74
75
0
    SeqnoTimePair() = default;
76
    SeqnoTimePair(SequenceNumber _seqno, uint64_t _time)
77
3.39k
        : seqno(_seqno), time(_time) {}
78
79
    // Encode to dest string
80
    void Encode(std::string& dest) const;
81
82
    // Decode the value from input Slice and remove it from the input
83
    Status Decode(Slice& input);
84
85
    // For delta encoding
86
0
    SeqnoTimePair ComputeDelta(const SeqnoTimePair& base) const {
87
0
      return {seqno - base.seqno, time - base.time};
88
0
    }
89
90
    // For delta decoding
91
0
    void ApplyDelta(const SeqnoTimePair& delta_or_base) {
92
0
      seqno += delta_or_base.seqno;
93
0
      time += delta_or_base.time;
94
0
    }
95
96
    // If another pair can be combined into this one (for optimizing
97
    // normal SeqnoToTimeMapping behavior), then this mapping is modified
98
    // and true is returned, indicating the other mapping can be discarded.
99
    // Otherwise false is returned and nothing is changed.
100
    bool Merge(const SeqnoTimePair& other);
101
102
    // Ordering used for Sort()
103
0
    bool operator<(const SeqnoTimePair& other) const {
104
0
      return std::tie(seqno, time) < std::tie(other.seqno, other.time);
105
0
    }
106
107
0
    bool operator==(const SeqnoTimePair& other) const {
108
0
      return std::tie(seqno, time) == std::tie(other.seqno, other.time);
109
0
    }
110
111
0
    static bool SeqnoLess(const SeqnoTimePair& a, const SeqnoTimePair& b) {
112
0
      return a.seqno < b.seqno;
113
0
    }
114
115
0
    static bool TimeLess(const SeqnoTimePair& a, const SeqnoTimePair& b) {
116
0
      return a.time < b.time;
117
0
    }
118
  };
119
120
  // Construct an empty SeqnoToTimeMapping with no limits.
121
201k
  SeqnoToTimeMapping() {}
122
123
  // ==== Configuration for enforced state ==== //
124
  // Set a time span beyond which old entries can be deleted. Specifically,
125
  // under enforcement mode, the structure will maintian only one entry older
126
  // than the newest entry time minus max_time_span, so that
127
  // GetProximalSeqnoBeforeTime queries back to that time return a good result.
128
  // UINT64_MAX == unlimited. 0 == retain just one latest entry. Returns *this.
129
  SeqnoToTimeMapping& SetMaxTimeSpan(uint64_t max_time_span);
130
131
  // Set the nominal capacity under enforcement mode. The structure is allowed
132
  // to grow some reasonable fraction larger but will automatically compact
133
  // down to this size. UINT64_MAX == unlimited. 0 == retain nothing.
134
  // Returns *this.
135
  SeqnoToTimeMapping& SetCapacity(uint64_t capacity);
136
137
  // ==== Modifiers, enforced ==== //
138
  // Adds a series of mappings interpolating from from_seqno->from_time to
139
  // to_seqno->to_time. This can only be called on an empty object and both
140
  // seqno range and time range are inclusive.
141
  void PrePopulate(SequenceNumber from_seqno, SequenceNumber to_seqno,
142
                   uint64_t from_time, uint64_t to_time);
143
144
  // Append a new entry to the list. The `seqno` should be >= all previous
145
  // entries. This operation maintains enforced mode invariants, and will
146
  // automatically (re-)enter enforced mode if not already in that state.
147
  // Returns false if the entry was merged into the most recent entry
148
  // rather than creating a new entry.
149
  bool Append(SequenceNumber seqno, uint64_t time);
150
151
0
  bool Append(std::pair<SequenceNumber, uint64_t> seqno_time_pair) {
152
0
    return Append(seqno_time_pair.first, seqno_time_pair.second);
153
0
  }
154
155
  // Clear all entries and (re-)enter enforced mode if not already in that
156
  // state. Enforced limits are unchanged.
157
0
  void Clear() {
158
0
    pairs_.clear();
159
0
    enforced_ = true;
160
0
  }
161
162
  // Enters the "enforced" state if not already in that state, which is
163
  // useful before copying or querying. This will
164
  //  * Sort the entries
165
  //  * Discard any obsolete entries, which is aided if the caller specifies
166
  // the `now` time so that entries older than now minus the max time span can
167
  // be discarded.
168
  //  * Compact the entries to the configured capacity.
169
  // Returns *this.
170
  SeqnoToTimeMapping& Enforce(uint64_t now = 0);
171
172
  // ==== Modifiers, unenforced ==== //
173
  // Add a new random entry and enter "unenforced" state. Unlike Append(), it
174
  // can be any historical data.
175
  void AddUnenforced(SequenceNumber seqno, uint64_t time);
176
177
  // Decode and add the entries to this mapping object. Unless starting from
178
  // an empty mapping with no configured enforcement limits, this operation
179
  // enters the unenforced state.
180
  Status DecodeFrom(const std::string& pairs_str);
181
182
  // Copies entries from the src mapping object to this one, limited to entries
183
  // needed to answer GetProximalTimeBeforeSeqno() queries for the given
184
  // *inclusive* seqno range. The source structure must be in enforced
185
  // state as a precondition. Unless starting with this object as empty mapping
186
  // with no configured enforcement limits, this object enters the unenforced
187
  // state.
188
  void CopyFromSeqnoRange(const SeqnoToTimeMapping& src,
189
                          SequenceNumber from_seqno,
190
                          SequenceNumber to_seqno = kMaxSequenceNumber);
191
0
  void CopyFrom(const SeqnoToTimeMapping& src) {
192
0
    CopyFromSeqnoRange(src, kUnknownSeqnoBeforeAll, kMaxSequenceNumber);
193
0
  }
194
195
  // ==== Accessors ==== //
196
  // Given a sequence number, return the best (largest / newest) known time
197
  // that is no later than the write time of that given sequence number.
198
  // If no such specific time is known, returns kUnknownTimeBeforeAll.
199
  // Using the example in the class comment above,
200
  //  GetProximalTimeBeforeSeqno(10) -> kUnknownTimeBeforeAll
201
  //  GetProximalTimeBeforeSeqno(11) -> 500
202
  //  GetProximalTimeBeforeSeqno(20) -> 500
203
  //  GetProximalTimeBeforeSeqno(21) -> 600
204
  // Because this is a const operation depending on sortedness, the structure
205
  // must be in enforced state as a precondition.
206
  uint64_t GetProximalTimeBeforeSeqno(SequenceNumber seqno) const;
207
208
  // Given a time, return the best (largest) sequence number whose write time
209
  // is no later than that given time. If no such specific sequence number is
210
  // known, returns kUnknownSeqnoBeforeAll. Using the example in the class
211
  // comment above,
212
  //  GetProximalSeqnoBeforeTime(499) -> kUnknownSeqnoBeforeAll
213
  //  GetProximalSeqnoBeforeTime(500) -> 10
214
  //  GetProximalSeqnoBeforeTime(599) -> 10
215
  //  GetProximalSeqnoBeforeTime(600) -> 20
216
  // Because this is a const operation depending on sortedness, the structure
217
  // must be in enforced state as a precondition.
218
  SequenceNumber GetProximalSeqnoBeforeTime(uint64_t time) const;
219
220
  // Given current time, the configured `preserve_internal_time_seconds`, and
221
  // `preclude_last_level_data_seconds`, find the relevant cutoff sequence
222
  // numbers for tiering.
223
  void GetCurrentTieringCutoffSeqnos(
224
      uint64_t current_time, uint64_t preserve_internal_time_seconds,
225
      uint64_t preclude_last_level_data_seconds,
226
      SequenceNumber* preserve_time_min_seqno,
227
      SequenceNumber* preclude_last_level_min_seqno) const;
228
229
  // Encode to a binary string by appending to `dest`.
230
  // Because this is a const operation depending on sortedness, the structure
231
  // must be in enforced state as a precondition.
232
  void EncodeTo(std::string& dest) const;
233
234
  // Return the number of entries
235
0
  size_t Size() const { return pairs_.size(); }
236
237
0
  uint64_t GetCapacity() const { return capacity_; }
238
239
  // If the internal list is empty
240
41.7k
  bool Empty() const { return pairs_.empty(); }
241
242
  // return the string for user message
243
  // Note: Not efficient, okay for print
244
  std::string ToHumanString() const;
245
246
#ifndef NDEBUG
247
  const SeqnoTimePair& TEST_GetLastEntry() const { return pairs_.back(); }
248
  const std::deque<SeqnoTimePair>& TEST_GetInternalMapping() const {
249
    return pairs_;
250
  }
251
  bool TEST_IsEnforced() const { return enforced_; }
252
#endif
253
254
 private:
255
  uint64_t max_time_span_ = UINT64_MAX;
256
  uint64_t capacity_ = UINT64_MAX;
257
258
  std::deque<SeqnoTimePair> pairs_;
259
260
  // Whether this object is in the "enforced" state. Between calls to public
261
  // functions, enforced_==true means that
262
  // * `pairs_` is sorted
263
  // * The capacity limit (non-strict) is met
264
  // * The time span limit is met
265
  // However, some places within the implementation (Append()) will temporarily
266
  // violate those last two conditions while enforced_==true. See also the
267
  // Enforce*() and Sort*() private functions below.
268
  bool enforced_ = true;
269
270
  void EnforceMaxTimeSpan(uint64_t now = 0);
271
  void EnforceCapacity(bool strict);
272
  void SortAndMerge();
273
274
  using pair_const_iterator =
275
      std::deque<SeqnoToTimeMapping::SeqnoTimePair>::const_iterator;
276
  pair_const_iterator FindGreaterTime(uint64_t time) const;
277
  pair_const_iterator FindGreaterSeqno(SequenceNumber seqno) const;
278
  pair_const_iterator FindGreaterEqSeqno(SequenceNumber seqno) const;
279
};
280
281
// A struct to help combining settings across column families
282
struct MinAndMaxPreserveSeconds {
283
  uint64_t min_preserve_seconds = std::numeric_limits<uint64_t>::max();
284
  uint64_t max_preserve_seconds = std::numeric_limits<uint64_t>::min();
285
286
125k
  MinAndMaxPreserveSeconds() = default;
287
288
  template <class CFOpts>
289
97.4k
  explicit MinAndMaxPreserveSeconds(const CFOpts& opts) {
290
97.4k
    Combine(opts);
291
97.4k
  }
rocksdb::MinAndMaxPreserveSeconds::MinAndMaxPreserveSeconds<rocksdb::ColumnFamilyOptions>(rocksdb::ColumnFamilyOptions const&)
Line
Count
Source
289
92.3k
  explicit MinAndMaxPreserveSeconds(const CFOpts& opts) {
290
92.3k
    Combine(opts);
291
92.3k
  }
rocksdb::MinAndMaxPreserveSeconds::MinAndMaxPreserveSeconds<rocksdb::MutableCFOptions>(rocksdb::MutableCFOptions const&)
Line
Count
Source
289
5.07k
  explicit MinAndMaxPreserveSeconds(const CFOpts& opts) {
290
5.07k
    Combine(opts);
291
5.07k
  }
292
293
173k
  bool IsEnabled() const {
294
173k
    return min_preserve_seconds != std::numeric_limits<uint64_t>::max();
295
173k
  }
296
297
  // Incorporate another CF's settings into the result. If preserve/preclude are
298
  // disabled for this CF, they are excluded from the result.
299
  template <class CFOpts>
300
262k
  void Combine(const CFOpts& opts) {
301
262k
    uint64_t preserve_seconds = std::max(opts.preserve_internal_time_seconds,
302
262k
                                         opts.preclude_last_level_data_seconds);
303
262k
    if (preserve_seconds > 0) {
304
0
      min_preserve_seconds = std::min(preserve_seconds, min_preserve_seconds);
305
0
      max_preserve_seconds = std::max(preserve_seconds, max_preserve_seconds);
306
0
    }
307
262k
  }
void rocksdb::MinAndMaxPreserveSeconds::Combine<rocksdb::MutableCFOptions>(rocksdb::MutableCFOptions const&)
Line
Count
Source
300
97.4k
  void Combine(const CFOpts& opts) {
301
97.4k
    uint64_t preserve_seconds = std::max(opts.preserve_internal_time_seconds,
302
97.4k
                                         opts.preclude_last_level_data_seconds);
303
97.4k
    if (preserve_seconds > 0) {
304
0
      min_preserve_seconds = std::min(preserve_seconds, min_preserve_seconds);
305
0
      max_preserve_seconds = std::max(preserve_seconds, max_preserve_seconds);
306
0
    }
307
97.4k
  }
void rocksdb::MinAndMaxPreserveSeconds::Combine<rocksdb::ColumnFamilyOptions>(rocksdb::ColumnFamilyOptions const&)
Line
Count
Source
300
165k
  void Combine(const CFOpts& opts) {
301
165k
    uint64_t preserve_seconds = std::max(opts.preserve_internal_time_seconds,
302
165k
                                         opts.preclude_last_level_data_seconds);
303
165k
    if (preserve_seconds > 0) {
304
0
      min_preserve_seconds = std::min(preserve_seconds, min_preserve_seconds);
305
0
      max_preserve_seconds = std::max(preserve_seconds, max_preserve_seconds);
306
0
    }
307
165k
  }
308
309
  // Choose how many seconds between mapping samples
310
53.0k
  uint64_t GetRecodingCadence() const {
311
53.0k
    if (IsEnabled()) {
312
      // round up to 1 when the time_duration is smaller than
313
      // kMaxSeqnoTimePairsPerCF
314
0
      return (min_preserve_seconds + kMaxSeqnoTimePairsPerCF - 1) /
315
0
             kMaxSeqnoTimePairsPerCF;
316
53.0k
    } else {
317
      // disabled
318
53.0k
      return 0;
319
53.0k
    }
320
53.0k
  }
321
};
322
323
// === Utility methods used for TimedPut === //
324
325
// Pack a value Slice and a unix write time into buffer `buf` and return a Slice
326
// for the packed value backed by `buf`.
327
Slice PackValueAndWriteTime(const Slice& value, uint64_t unix_write_time,
328
                            std::string* buf);
329
330
// Pack a value Slice and a sequence number into buffer `buf` and return a Slice
331
// for the packed value backed by `buf`.
332
Slice PackValueAndSeqno(const Slice& value, SequenceNumber seqno,
333
                        std::string* buf);
334
335
// Parse a packed value to get the write time.
336
uint64_t ParsePackedValueForWriteTime(const Slice& value);
337
338
// Parse a packed value to get the value and the write time. The unpacked value
339
// Slice is backed up by the same memory backing up `value`.
340
std::tuple<Slice, uint64_t> ParsePackedValueWithWriteTime(const Slice& value);
341
342
// Parse a packed value to get the sequence number.
343
SequenceNumber ParsePackedValueForSeqno(const Slice& value);
344
345
// Parse a packed value to get the value and the sequence number. The unpacked
346
// value Slice is backed up by the same memory backing up `value`.
347
std::tuple<Slice, SequenceNumber> ParsePackedValueWithSeqno(const Slice& value);
348
349
// Parse a packed value to get the value. The unpacked value Slice is backed up
350
// by the same memory backing up `value`.
351
Slice ParsePackedValueForValue(const Slice& value);
352
353
}  // namespace ROCKSDB_NAMESPACE