Coverage Report

Created: 2026-09-28 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/table/block_based/block_builder.cc
Line
Count
Source
1
//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
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
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
7
// Use of this source code is governed by a BSD-style license that can be
8
// found in the LICENSE file. See the AUTHORS file for names of contributors.
9
//
10
// BlockBuilder generates blocks where keys are prefix-compressed:
11
//
12
// When we store a key, we drop the prefix shared with the previous
13
// string.  This helps reduce the space requirement significantly.
14
// Furthermore, once every K keys, we do not apply the prefix
15
// compression and store the entire key.  We call this a "restart
16
// point".  The tail end of the block stores the offsets of all of the
17
// restart points, and can be used to do a binary search when looking
18
// for a particular key.  Values are stored as-is (without compression)
19
// immediately following the corresponding key.
20
//
21
// An entry for a particular key-value pair has the form:
22
//     shared_bytes: varint32
23
//     unshared_bytes: varint32
24
//     value_length: varint32 (NOTE1)
25
//     key_delta: char[unshared_bytes]
26
//     value: char[value_length]
27
// shared_bytes == 0 (explicitly stored) for restart points.
28
//
29
// The trailer of the block has the form:
30
//     restarts: uint32[num_restarts]
31
//     num_restarts: uint32
32
// restarts[i] contains the offset within the block of the ith restart point.
33
//
34
// NOTE1: omitted for format_version >= 4 index blocks, because the value is
35
// composed of one (shared_bytes > 0) or two (shared_bytes == 0) varints, whose
36
// length is self-describing.
37
38
#include "table/block_based/block_builder.h"
39
40
#include <algorithm>
41
#include <cassert>
42
#include <cmath>
43
44
#include "db/dbformat.h"
45
#include "monitoring/statistics_impl.h"
46
#include "rocksdb/comparator.h"
47
#include "table/block_based/block_util.h"
48
#include "table/block_based/data_block_footer.h"
49
#include "util/coding.h"
50
51
namespace ROCKSDB_NAMESPACE {
52
53
namespace {
54
55
// Tracks whether restart-point keys are uniformly distributed using Welford's
56
// online algorithm to incrementally compute the coefficient of variation (CV)
57
// of gaps between consecutive restart keys.
58
class UniformDataTracker {
59
 public:
60
0
  void AddKey(uint64_t key_value) {
61
0
    if (num_keys_ > 0) {
62
0
      double gap = static_cast<double>(key_value - prev_key_value_);
63
0
      size_t gap_count = num_keys_;
64
0
      double delta = gap - mean_;
65
0
      mean_ += delta / static_cast<double>(gap_count);
66
0
      double delta2 = gap - mean_;
67
0
      m2_ += delta * delta2;
68
0
    }
69
0
    prev_key_value_ = key_value;
70
0
    num_keys_++;
71
0
  }
72
73
  // Returns the coefficient of variation (CV) of the key gaps, or -1.0 if
74
  // there are not enough data points to compute it.
75
0
  double GetCV() const {
76
0
    size_t gap_count = num_keys_ > 0 ? num_keys_ - 1 : 0;
77
0
    if (gap_count < 2 || mean_ <= 0) {
78
0
      return -1.0;
79
0
    }
80
0
    return std::sqrt(m2_ / static_cast<double>(gap_count)) / mean_;
81
0
  }
82
83
 private:
84
  uint64_t prev_key_value_ = 0;
85
  size_t num_keys_ = 0;
86
  double mean_ = 0;
87
  double m2_ = 0;
88
};
89
90
}  // namespace
91
92
BlockBuilder::BlockBuilder(
93
    int block_restart_interval, bool use_delta_encoding,
94
    bool use_value_delta_encoding,
95
    BlockBasedTableOptions::DataBlockIndexType index_type,
96
    double data_block_hash_table_util_ratio, size_t ts_sz,
97
    bool persist_user_defined_timestamps, bool is_user_key,
98
    bool use_separated_kv_storage, Statistics* statistics,
99
    double uniform_cv_threshold, bool use_common_prefix)
100
143k
    : block_restart_interval_(block_restart_interval),
101
143k
      use_delta_encoding_(use_delta_encoding),
102
143k
      use_value_delta_encoding_(use_value_delta_encoding),
103
143k
      strip_ts_sz_(persist_user_defined_timestamps ? 0 : ts_sz),
104
143k
      is_user_key_(is_user_key),
105
143k
      restarts_(1, 0),  // First restart point is at offset 0
106
143k
      counter_(0),
107
143k
      finished_(false),
108
143k
      is_uniform_(false),
109
143k
      uniform_cv_threshold_(uniform_cv_threshold),
110
143k
      statistics_(statistics),
111
143k
      use_separated_kv_storage_(use_separated_kv_storage),
112
143k
      use_common_prefix_(use_common_prefix) {
113
143k
  switch (index_type) {
114
143k
    case BlockBasedTableOptions::kDataBlockBinarySearch:
115
143k
      break;
116
0
    case BlockBasedTableOptions::kDataBlockBinaryAndHash:
117
0
      data_block_hash_index_builder_.Initialize(
118
0
          data_block_hash_table_util_ratio);
119
0
      break;
120
0
    default:
121
0
      assert(0);
122
143k
  }
123
143k
  assert(block_restart_interval_ >= 1);
124
  // The common-prefix feature relies on delta encoding and requires user keys
125
  // without stripped timestamps. Value delta encoding (fv4 index blocks) is
126
  // supported: the restart-key rewrite branches on use_value_delta_encoding_.
127
143k
  assert(!use_common_prefix_ || (use_delta_encoding_ && strip_ts_sz_ == 0));
128
143k
  estimate_ = sizeof(uint32_t) + sizeof(uint32_t) +
129
143k
              (use_separated_kv_storage_ ? sizeof(uint32_t) : 0);
130
143k
}
131
132
BlockBuilder::BlockBuilder(ForMetaBlock, int block_restart_interval)
133
46.5k
    : BlockBuilder(block_restart_interval, /*use_delta_encoding=*/true,
134
46.5k
                   /*use_value_delta_encoding=*/false,
135
46.5k
                   BlockBasedTableOptions::kDataBlockBinarySearch,
136
46.5k
                   /*data_block_hash_table_util_ratio=*/0.75, /*ts_sz=*/0,
137
46.5k
                   /*persist_user_defined_timestamps=*/true,
138
46.5k
                   /*is_user_key=*/false, /*use_separated_kv_storage=*/false,
139
46.5k
                   /*statistics=*/nullptr, /*uniform_cv_threshold=*/-1.0,
140
46.5k
                   /*use_common_prefix=*/false) {}
141
142
29.5k
void BlockBuilder::Reset() {
143
29.5k
  buffer_.clear();
144
  // First restart point is at offset 0. The common-prefix rewrite may have set
145
  // restarts_[0] to the prefix-section offset, so reset it explicitly rather
146
  // than assuming resize(1) leaves it at 0.
147
29.5k
  restarts_.assign(1, 0);
148
29.5k
  estimate_ = sizeof(uint32_t) + sizeof(uint32_t) +
149
29.5k
              (use_separated_kv_storage_ ? sizeof(uint32_t) : 0);
150
29.5k
  counter_ = 0;
151
29.5k
  finished_ = false;
152
29.5k
  is_uniform_ = false;
153
29.5k
  last_key_.clear();
154
29.5k
  if (data_block_hash_index_builder_.Valid()) {
155
0
    data_block_hash_index_builder_.Reset();
156
0
  }
157
29.5k
  values_buffer_.clear();
158
159
29.5k
  first_key_prefix_.clear();
160
29.5k
  finishing_ = false;
161
162
#ifndef NDEBUG
163
  add_with_last_key_called_ = false;
164
#endif
165
29.5k
}
166
167
0
void BlockBuilder::SwapAndReset(std::string& buffer) {
168
0
  std::swap(buffer_, buffer);
169
0
  Reset();
170
0
}
171
172
size_t BlockBuilder::EstimateSizeAfterKV(const Slice& key,
173
32.3k
                                         const Slice& value) const {
174
32.3k
  size_t estimate = CurrentSizeEstimate();
175
  // Note: this is an imprecise estimate as it accounts for the whole key size
176
  // instead of non-shared key size.
177
32.3k
  estimate += key.size();
178
32.3k
  if (strip_ts_sz_ > 0) {
179
0
    estimate -= strip_ts_sz_;
180
0
  }
181
  // In value delta encoding we estimate the value delta size as half the full
182
  // value size since only the size field of block handle is encoded.
183
32.3k
  estimate +=
184
32.3k
      !use_value_delta_encoding_ || (counter_ >= block_restart_interval_)
185
32.3k
          ? value.size()
186
32.3k
          : value.size() / 2;
187
188
32.3k
  if (counter_ >= block_restart_interval_) {
189
480
    estimate += sizeof(uint32_t);  // a new restart entry.
190
480
  }
191
192
  // For separated KV storage, value_offset varint is written at restart points
193
32.3k
  if (use_separated_kv_storage_ &&
194
0
      (counter_ == 0 || counter_ >= block_restart_interval_)) {
195
0
    estimate += VarintLength(values_buffer_.size());
196
0
  }
197
198
32.3k
  estimate += sizeof(int32_t);  // varint for shared prefix length.
199
  // Note: this is an imprecise estimate as we will have to encoded size, one
200
  // for shared key and one for non-shared key.
201
32.3k
  estimate += VarintLength(key.size());  // varint for key length.
202
32.3k
  if (!use_value_delta_encoding_ || (counter_ >= block_restart_interval_)) {
203
32.3k
    estimate += VarintLength(value.size());  // varint for value length.
204
32.3k
  }
205
206
32.3k
  return estimate;
207
32.3k
}
208
209
102k
Slice BlockBuilder::Finish() {
210
  // Common-prefix feature: rewrite restart-point keys in place, stripping the
211
  // block's common user-key prefix (stored once in the block's leading bytes,
212
  // [0, restarts[0])). No footer bit is needed -- a non-zero restarts[0]
213
  // self-signals the prefix to the reader. Only done when there is a non-empty
214
  // common prefix to remove.
215
102k
  if (use_common_prefix_ && !buffer_.empty() && !first_key_prefix_.empty()) {
216
0
    RewriteRestartKeysStrippingPrefix();
217
0
  }
218
219
  // Safe to run after the strip above: stripping removes the same block-common
220
  // prefix from every restart key, so difference_offset() (hence prefix_len)
221
  // shrinks by exactly that length while each key's start shifts by the same
222
  // amount -- ReadBe64FromKey ends up reading the identical suffix bytes, so
223
  // the uniformity decision matches running on the original keys.
224
  // (ReadBe64FromKey also strips the internal trailer, so footer bytes are
225
  // never read as key data.)
226
102k
  is_uniform_ = ScanForUniformity();
227
228
  // Append restart array
229
102k
  size_t values_buffer_offset = buffer_.size();
230
231
102k
  if (use_separated_kv_storage_) {
232
0
    buffer_.append(values_buffer_);
233
0
  }
234
235
329k
  for (size_t i = 0; i < restarts_.size(); i++) {
236
227k
    PutFixed32(&buffer_, restarts_[i]);
237
227k
  }
238
239
102k
  DataBlockFooter footer;
240
102k
  footer.num_restarts = static_cast<uint32_t>(restarts_.size());
241
102k
  footer.index_type = BlockBasedTableOptions::kDataBlockBinarySearch;
242
102k
  footer.is_uniform = is_uniform_;
243
102k
  if (data_block_hash_index_builder_.Valid() &&
244
0
      CurrentSizeEstimate() <= kMaxBlockSizeSupportedByHashIndex) {
245
0
    data_block_hash_index_builder_.Finish(buffer_);
246
0
    footer.index_type = BlockBasedTableOptions::kDataBlockBinaryAndHash;
247
0
  }
248
249
102k
  if (use_separated_kv_storage_) {
250
0
    footer.separated_kv = true;
251
0
    footer.values_section_offset = static_cast<uint32_t>(values_buffer_offset);
252
0
  }
253
102k
  footer.EncodeTo(&buffer_);
254
102k
  finished_ = true;
255
102k
  return Slice(buffer_);
256
102k
}
257
258
void BlockBuilder::Add(const Slice& key, const Slice& value,
259
1.20M
                       const Slice& delta_value, bool skip_delta_encoding) {
260
  // Ensure no unsafe mixing of Add and AddWithLastKey
261
1.20M
  assert(!add_with_last_key_called_);
262
263
1.20M
  AddWithLastKeyImpl(key, value, last_key_, delta_value, skip_delta_encoding,
264
1.20M
                     buffer_.size());
265
1.20M
  if (use_delta_encoding_) {
266
    // Update state
267
    // We used to just copy the changed data, but it appears to be
268
    // faster to just copy the whole thing.
269
1.20M
    last_key_.assign(key.data(), key.size());
270
1.20M
  }
271
1.20M
}
272
273
void BlockBuilder::AddWithLastKey(const Slice& key, const Slice& value,
274
                                  const Slice& last_key_param,
275
                                  const Slice& delta_value,
276
61.0k
                                  bool skip_delta_encoding) {
277
  // Ensure no unsafe mixing of Add and AddWithLastKey
278
61.0k
  assert(last_key_.empty());
279
#ifndef NDEBUG
280
  add_with_last_key_called_ = false;
281
#endif
282
283
  // Here we make sure to use an empty `last_key` on first call after creation
284
  // or Reset. This is more convenient for the caller and we can be more
285
  // clever inside BlockBuilder. On this hot code path, we want to avoid
286
  // conditional jumps like `buffer_.empty() ? ... : ...` so we can use a
287
  // fast arithmetic operation instead, with an assertion to be sure our logic
288
  // is sound.
289
61.0k
  size_t buffer_size = buffer_.size();
290
61.0k
  size_t last_key_size = last_key_param.size();
291
61.0k
  assert(buffer_size == 0 || buffer_size >= last_key_size - strip_ts_sz_);
292
293
61.0k
  Slice last_key(last_key_param.data(), last_key_size * (buffer_size > 0));
294
295
61.0k
  AddWithLastKeyImpl(key, value, last_key, delta_value, skip_delta_encoding,
296
61.0k
                     buffer_size);
297
61.0k
}
298
299
inline void BlockBuilder::AddWithLastKeyImpl(
300
    const Slice& key, const Slice& value, const Slice& last_key,
301
1.26M
    const Slice& delta_value, bool skip_delta_encoding, size_t buffer_size) {
302
1.26M
  assert(!finished_);
303
1.26M
  assert(counter_ <= block_restart_interval_);
304
  // Verify < 4GB assumption (see API comments on Add())
305
1.26M
  assert(key.size() < uint64_t{1} << 32);
306
1.26M
  assert(value.size() < uint64_t{1} << 32);
307
1.26M
  assert(last_key.size() < uint64_t{1} << 32);
308
1.26M
  assert(delta_value.size() < uint64_t{1} << 32);
309
1.26M
  std::string key_buf;
310
1.26M
  std::string last_key_buf;
311
1.26M
  const Slice key_to_persist = MaybeStripTimestampFromKey(&key_buf, key);
312
  // For delta key encoding, the first key in each restart interval doesn't have
313
  // a last key to share bytes with.
314
1.26M
  const Slice last_key_persisted =
315
1.26M
      last_key.size() == 0
316
1.26M
          ? last_key
317
1.26M
          : MaybeStripTimestampFromKey(&last_key_buf, last_key);
318
319
  // FIXME: check/enforce that buffer_ hasn't exceeded 4GB. The concern
320
  // with adding that check and propagating the result is inner-loop
321
  // performance. This case is HIGH concern because blocks like range deletions
322
  // and non-partitioned indexes could pile large keys together into one block.
323
1.26M
  const uint32_t buffer_size32 = static_cast<uint32_t>(buffer_size);
324
  // NOTE: assuming all slice sizes < 4GB (see API comments on Add())
325
1.26M
  uint32_t shared = 0;  // number of bytes shared with prev key
326
1.26M
  if (counter_ >= block_restart_interval_) {
327
    // Restart compression
328
132k
    restarts_.push_back(buffer_size32);
329
132k
    estimate_ += sizeof(uint32_t);
330
132k
    counter_ = 0;
331
1.13M
  } else if (use_delta_encoding_ && !skip_delta_encoding) {
332
    // See how much sharing to do with previous string
333
1.13M
    shared = static_cast<uint32_t>(
334
1.13M
        key_to_persist.difference_offset(last_key_persisted));
335
1.13M
  }
336
337
  // Common-prefix feature: track the running common user-key prefix across all
338
  // keys in the block. The prefix is removed from restart-point keys later, in
339
  // RewriteRestartKeysStrippingPrefix() at Finish(). To keep this nearly free
340
  // in the common (stable-prefix) case, we exploit `shared` (bytes shared with
341
  // the previous key): a non-restart key that shares >= prefix_len bytes with
342
  // its predecessor still starts with the whole running prefix (keys are
343
  // sorted), so the prefix can only shrink when shared < prefix_len, in which
344
  // case the new prefix length is exactly `shared`. Restart entries have shared
345
  // forced to 0, so they are rechecked with a real comparison (cheap:
346
  // 1/restart_interval).
347
1.26M
  if (use_common_prefix_) {
348
    // The reader treats every shared==0 entry as a stripped restart key and
349
    // prepends the block's common prefix. skip_delta_encoding would create a
350
    // non-restart shared==0 entry (full key stored) that the reader would
351
    // wrongly re-prefix, so common-prefix is gated off wherever
352
    // skip_delta_encoding can occur (index blocks: super_block_alignment_size
353
    // == 0). Enforce that invariant here.
354
0
    assert(!skip_delta_encoding);
355
0
    if (buffer_size == 0) {
356
0
      const Slice uk =
357
0
          is_user_key_ ? key_to_persist : ExtractUserKey(key_to_persist);
358
0
      first_key_prefix_.assign(uk.data(), uk.size());
359
0
    } else if (counter_ == 0) {
360
      // Restart entry (shared was forced to 0); recompute the real overlap.
361
0
      const Slice uk =
362
0
          is_user_key_ ? key_to_persist : ExtractUserKey(key_to_persist);
363
0
      size_t common = Slice(first_key_prefix_).difference_offset(uk);
364
0
      if (common < first_key_prefix_.size()) {
365
0
        first_key_prefix_.resize(common);
366
0
      }
367
0
    } else if (shared < first_key_prefix_.size()) {
368
0
      first_key_prefix_.resize(shared);
369
0
    }
370
0
  }
371
372
1.26M
  const uint32_t non_shared =
373
1.26M
      static_cast<uint32_t>(key_to_persist.size()) - shared;
374
1.26M
  const size_t prev_values_size = values_buffer_.size();
375
376
  // FIXME: check/enforce that values_buffer_ hasn't exceeded 4GB. The concern
377
  // with adding that check and propagating the result is inner-loop
378
  // performance. This case is low concern because it (at time of writing) only
379
  // applies to data blocks and those are flushed as soon as the size exceeds
380
  // the block size.
381
1.26M
  const uint32_t prev_values_size32 = static_cast<uint32_t>(prev_values_size);
382
1.26M
  const uint32_t value_size = static_cast<uint32_t>(value.size());
383
1.26M
  if (use_value_delta_encoding_) {
384
59.1k
    if (use_separated_kv_storage_ && counter_ == 0) {
385
      // Add "<shared><non_shared><value_offset>" to buffer_
386
0
      PutVarint32(&buffer_, shared, non_shared, prev_values_size32);
387
59.1k
    } else {
388
      // Add "<shared><non_shared>" to buffer_
389
59.1k
      PutVarint32(&buffer_, shared, non_shared);
390
59.1k
    }
391
1.20M
  } else {
392
1.20M
    if (use_separated_kv_storage_ && counter_ == 0) {
393
      // Add "<shared><non_shared><value_size><value_offset>" to buffer_
394
0
      PutVarint32(&buffer_, shared, non_shared, value_size, prev_values_size32);
395
1.20M
    } else {
396
      // Add "<shared><non_shared><value_size>" to buffer_
397
1.20M
      PutVarint32(&buffer_, shared, non_shared, value_size);
398
1.20M
    }
399
1.20M
  }
400
401
  // Add string delta to buffer_ (using only bottom 32 bits of size for
402
  // consistent treatment in case of corruption)
403
1.26M
  buffer_.append(key_to_persist.data() + shared, non_shared);
404
405
1.26M
  auto& values_buffer = use_separated_kv_storage_ ? values_buffer_ : buffer_;
406
  // Use value delta encoding only when the key has shared bytes. This would
407
  // simplify the decoding, where it can figure which decoding to use simply by
408
  // looking at the shared bytes size.
409
1.26M
  if (shared != 0 && use_value_delta_encoding_) {
410
    // Using only bottom 32 bits of size for consistent treatment in case of
411
    // corruption
412
    // NOTE: callers may pass an empty delta_value when they had no previous
413
    // handle to delta against, relying on shared == 0 for a block's first
414
    // entry. Catch that coupling breaking, which would otherwise silently
415
    // write a zero-length value: a real delta encoding is never empty.
416
0
    assert(!delta_value.empty());
417
0
    values_buffer.append(delta_value.data(),
418
0
                         static_cast<uint32_t>(delta_value.size()));
419
1.26M
  } else {
420
    // Using only bottom 32 bits of size for consistent treatment in case of
421
    // corruption
422
1.26M
    values_buffer.append(value.data(), value_size);
423
1.26M
  }
424
425
  // TODO(yuzhangyu): make user defined timestamp work with block hash index.
426
1.26M
  if (data_block_hash_index_builder_.Valid()) {
427
    // Only data blocks should be using `kDataBlockBinaryAndHash` index type.
428
    // And data blocks should always be built with internal keys instead of
429
    // user keys.
430
0
    assert(!is_user_key_);
431
0
    data_block_hash_index_builder_.Add(ExtractUserKey(key),
432
0
                                       restarts_.size() - 1);
433
0
  }
434
435
1.26M
  counter_++;
436
1.26M
  estimate_ +=
437
1.26M
      buffer_.size() - buffer_size + values_buffer_.size() - prev_values_size;
438
1.26M
}
439
440
0
void BlockBuilder::RewriteRestartKeysStrippingPrefix() {
441
0
  assert(use_common_prefix_);
442
0
  assert(!first_key_prefix_.empty());
443
0
  assert(!buffer_.empty());
444
445
0
  finishing_ = true;
446
0
  const uint32_t p = static_cast<uint32_t>(first_key_prefix_.size());
447
0
  const char* base = buffer_.data();
448
  // At this point buffer_ holds only the entries section (inline values for
449
  // non-separated storage; keys-only for separated, values in values_buffer_).
450
0
  const size_t keys_end = buffer_.size();
451
452
0
  std::string new_buf;
453
0
  new_buf.reserve(keys_end + p);
454
  // Common user-key prefix section at the start of the block: the raw prefix
455
  // bytes, with no length field. restarts[0] (the offset of the first entry,
456
  // recorded below) is the prefix length.
457
0
  new_buf.append(first_key_prefix_.data(), p);
458
459
0
  std::vector<uint32_t> new_restarts;
460
0
  const size_t num_restarts = restarts_.size();
461
0
  new_restarts.reserve(num_restarts);
462
463
0
  for (size_t k = 0; k < num_restarts; ++k) {
464
0
    const size_t r_start = restarts_[k];
465
0
    const size_t interval_end =
466
0
        (k + 1 < num_restarts) ? restarts_[k + 1] : keys_end;
467
0
    const char* in = base + r_start;
468
0
    const char* limit = base + interval_end;
469
470
    // Decode the restart-point entry header. The layout differs by value
471
    // encoding:
472
    //   non-V4 (data blocks):
473
    //     <shared=0><non_shared><value_size>[<value_offset> if separated]<key>
474
    //     [<value> if not separated]
475
    //   V4 (index blocks with value delta encoding): no value_size field --
476
    //     the value is a self-delimiting BlockHandle.
477
    //     <shared=0><non_shared>[<value_offset> if separated]<key><value>
478
0
    uint32_t shared = 0, non_shared = 0, value_size = 0, value_offset = 0;
479
0
    in = GetVarint32Ptr(in, limit, &shared);
480
0
    in = GetVarint32Ptr(in, limit, &non_shared);
481
0
    if (!use_value_delta_encoding_) {
482
0
      in = GetVarint32Ptr(in, limit, &value_size);
483
0
    }
484
0
    if (use_separated_kv_storage_) {
485
0
      in = GetVarint32Ptr(in, limit, &value_offset);
486
0
    }
487
0
    assert(in != nullptr);
488
0
    assert(shared == 0);
489
0
    assert(non_shared >= p);
490
0
    const char* key_ptr = in;
491
492
    // Re-emit the restart entry header with the common prefix removed from the
493
    // key length. `shared` stays 0 so the reader still recognizes a restart
494
    // point (and prepends the block's common prefix).
495
0
    new_restarts.push_back(static_cast<uint32_t>(new_buf.size()));
496
0
    if (!use_value_delta_encoding_) {
497
0
      if (use_separated_kv_storage_) {
498
0
        PutVarint32(&new_buf, 0, non_shared - p, value_size, value_offset);
499
0
      } else {
500
0
        PutVarint32(&new_buf, 0, non_shared - p, value_size);
501
0
      }
502
0
    } else {
503
0
      if (use_separated_kv_storage_) {
504
0
        PutVarint32(&new_buf, 0, non_shared - p, value_offset);
505
0
      } else {
506
0
        PutVarint32(&new_buf, 0, non_shared - p);
507
0
      }
508
0
    }
509
0
    new_buf.append(key_ptr + p, non_shared - p);
510
511
    // Bulk-copy the rest of the interval verbatim: the inline value (full or
512
    // delta BlockHandle for V4; sized value for non-V4 non-separated; nothing
513
    // inline for separated storage) plus all non-restart entries, whose encoded
514
    // bytes are independent of the block's common prefix.
515
0
    const size_t after_key_off = (key_ptr - base) + non_shared;
516
0
    new_buf.append(base + after_key_off, interval_end - after_key_off);
517
0
  }
518
519
0
  buffer_.swap(new_buf);
520
0
  restarts_.swap(new_restarts);
521
522
  // Recompute estimate_ for the post-strip block so the hash-index size gate in
523
  // Finish() (via CurrentSizeEstimate(), which no longer adjusts once
524
  // finishing_ is set) sees the actual size.
525
0
  estimate_ = buffer_.size() +
526
0
              (use_separated_kv_storage_ ? values_buffer_.size() : 0) +
527
0
              restarts_.size() * sizeof(uint32_t) + sizeof(uint32_t) +
528
0
              (use_separated_kv_storage_ ? sizeof(uint32_t) : 0);
529
0
}
530
531
const Slice BlockBuilder::MaybeStripTimestampFromKey(std::string* key_buf,
532
2.40M
                                                     const Slice& key) {
533
  // Only use bottom 32 bits of size for internal consistency (see API
534
  // comments on Add())
535
2.40M
  Slice stripped_key(key.data(), static_cast<uint32_t>(key.size()));
536
2.40M
  if (strip_ts_sz_ > 0) {
537
0
    if (is_user_key_) {
538
0
      stripped_key.remove_suffix(strip_ts_sz_);
539
0
    } else {
540
0
      StripTimestampFromInternalKey(key_buf, stripped_key, strip_ts_sz_);
541
0
      stripped_key = *key_buf;
542
0
    }
543
0
  }
544
2.40M
  return stripped_key;
545
2.40M
}
546
547
0
Slice BlockBuilder::GetRestartKey(uint32_t index, const char* limit) const {
548
0
  assert(index < restarts_.size());
549
0
  const char* p = buffer_.data() + restarts_[index];
550
0
  uint32_t shared;
551
0
  uint32_t non_shared;
552
  // When separated KV storage is enabled, restart point entries include an
553
  // extra value_offset varint that must be consumed to find the key delta.
554
0
  uint32_t value_offset;
555
0
  uint32_t* value_offset_ptr =
556
0
      use_separated_kv_storage_ ? &value_offset : nullptr;
557
0
  if (use_value_delta_encoding_) {
558
0
    p = DecodeKeyV4()(p, limit, &shared, &non_shared, value_offset_ptr);
559
0
  } else {
560
0
    p = DecodeKey()(p, limit, &shared, &non_shared, value_offset_ptr);
561
0
  }
562
0
  assert(p != nullptr);
563
0
  assert(shared == 0);
564
0
  (void)shared;
565
0
  return Slice(p, non_shared);
566
0
}
567
568
102k
bool BlockBuilder::ScanForUniformity() const {
569
102k
  if (uniform_cv_threshold_ < 0 || restarts_.size() < 3) {
570
102k
    return false;
571
102k
  }
572
573
0
  const char* limit = buffer_.data() + buffer_.size();
574
575
0
  Slice first_key = GetRestartKey(0, limit);
576
0
  Slice last_key =
577
0
      GetRestartKey(static_cast<uint32_t>(restarts_.size() - 1), limit);
578
579
  // Keys must be long enough for ReadBe64FromKey which strips internal bytes
580
0
  if (!is_user_key_ && (first_key.size() < kNumInternalBytes ||
581
0
                        last_key.size() < kNumInternalBytes)) {
582
0
    return false;
583
0
  }
584
585
0
  size_t prefix_len = first_key.difference_offset(last_key);
586
587
0
  UniformDataTracker tracker;
588
0
  for (size_t i = 0; i < restarts_.size(); i++) {
589
0
    Slice key = GetRestartKey(static_cast<uint32_t>(i), limit);
590
0
    if (!is_user_key_ && key.size() < kNumInternalBytes) {
591
0
      return false;
592
0
    }
593
0
    tracker.AddKey(ReadBe64FromKey(key, is_user_key_, prefix_len));
594
0
  }
595
596
0
  double cv = tracker.GetCV();
597
0
  if (statistics_ != nullptr && cv >= 0) {
598
0
    RecordInHistogram(statistics_, BLOCK_KEY_DISTRIBUTION_CV,
599
0
                      static_cast<uint64_t>(cv * 10000));
600
0
  }
601
602
0
  return cv >= 0 && cv < uniform_cv_threshold_;
603
0
}
604
605
}  // namespace ROCKSDB_NAMESPACE