/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 |