Coverage Report

Created: 2026-09-28 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/memtable/hash_skiplist_rep.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
7
#include <atomic>
8
9
#include "db/memtable.h"
10
#include "memory/arena.h"
11
#include "memtable/skiplist.h"
12
#include "port/port.h"
13
#include "rocksdb/memtablerep.h"
14
#include "rocksdb/slice.h"
15
#include "rocksdb/slice_transform.h"
16
#include "rocksdb/utilities/options_type.h"
17
#include "util/murmurhash.h"
18
19
namespace ROCKSDB_NAMESPACE {
20
namespace {
21
22
class HashSkipListRep : public MemTableRep {
23
 public:
24
  HashSkipListRep(const MemTableRep::KeyComparator& compare,
25
                  Allocator* allocator, const SliceTransform* transform,
26
                  size_t bucket_size, int32_t skiplist_height,
27
                  int32_t skiplist_branching_factor);
28
29
  void Insert(KeyHandle handle) override;
30
31
  bool Contains(const char* key) const override;
32
33
  size_t ApproximateMemoryUsage() override;
34
35
  void Get(const LookupKey& k, void* callback_args,
36
           bool (*callback_func)(void* arg, const char* entry)) override;
37
38
  ~HashSkipListRep() override;
39
40
  MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override;
41
42
  MemTableRep::Iterator* GetDynamicPrefixIterator(
43
      Arena* arena = nullptr) override;
44
45
 private:
46
  friend class DynamicIterator;
47
  using Bucket = SkipList<const char*, const MemTableRep::KeyComparator&>;
48
49
  size_t bucket_size_;
50
51
  const int32_t skiplist_height_;
52
  const int32_t skiplist_branching_factor_;
53
54
  // Maps slices (which are transformed user keys) to buckets of keys sharing
55
  // the same transform.
56
  std::atomic<Bucket*>* buckets_;
57
58
  // The user-supplied transform whose domain is the user keys.
59
  const SliceTransform* transform_;
60
61
  const MemTableRep::KeyComparator& compare_;
62
  // immutable after construction
63
  Allocator* const allocator_;
64
65
0
  inline size_t GetHash(const Slice& slice) const {
66
0
    return MurmurHash(slice.data(), static_cast<int>(slice.size()), 0) %
67
0
           bucket_size_;
68
0
  }
69
0
  inline Bucket* GetBucket(size_t i) const {
70
0
    return buckets_[i].load(std::memory_order_acquire);
71
0
  }
72
0
  inline Bucket* GetBucket(const Slice& slice) const {
73
0
    return GetBucket(GetHash(slice));
74
0
  }
75
  // Get a bucket from buckets_. If the bucket hasn't been initialized yet,
76
  // initialize it before returning.
77
  Bucket* GetInitializedBucket(const Slice& transformed);
78
79
  class Iterator : public MemTableRep::Iterator {
80
   public:
81
    explicit Iterator(Bucket* list, bool own_list = true,
82
                      Arena* arena = nullptr)
83
0
        : list_(list), iter_(list), own_list_(own_list), arena_(arena) {}
84
85
0
    ~Iterator() override {
86
      // if we own the list, we should also delete it
87
0
      if (own_list_) {
88
0
        assert(list_ != nullptr);
89
0
        delete list_;
90
0
      }
91
0
    }
92
93
    // Returns true iff the iterator is positioned at a valid node.
94
0
    bool Valid() const override { return list_ != nullptr && iter_.Valid(); }
95
96
    // Returns the key at the current position.
97
    // REQUIRES: Valid()
98
0
    const char* key() const override {
99
0
      assert(Valid());
100
0
      return iter_.key();
101
0
    }
102
103
    // Advances to the next position.
104
    // REQUIRES: Valid()
105
0
    void Next() override {
106
0
      assert(Valid());
107
0
      iter_.Next();
108
0
    }
109
110
    // Advances to the previous position.
111
    // REQUIRES: Valid()
112
0
    void Prev() override {
113
0
      assert(Valid());
114
0
      iter_.Prev();
115
0
    }
116
117
    // Advance to the first entry with a key >= target
118
0
    void Seek(const Slice& internal_key, const char* memtable_key) override {
119
0
      if (list_ != nullptr) {
120
0
        const char* encoded_key = (memtable_key != nullptr)
121
0
                                      ? memtable_key
122
0
                                      : EncodeKey(&tmp_, internal_key);
123
0
        iter_.Seek(encoded_key);
124
0
      }
125
0
    }
126
127
    // Retreat to the last entry with a key <= target
128
    void SeekForPrev(const Slice& /*internal_key*/,
129
0
                     const char* /*memtable_key*/) override {
130
      // not supported
131
0
      assert(false);
132
0
    }
133
134
    // Position at the first entry in collection.
135
    // Final state of iterator is Valid() iff collection is not empty.
136
0
    void SeekToFirst() override {
137
0
      if (list_ != nullptr) {
138
0
        iter_.SeekToFirst();
139
0
      }
140
0
    }
141
142
    // Position at the last entry in collection.
143
    // Final state of iterator is Valid() iff collection is not empty.
144
0
    void SeekToLast() override {
145
0
      if (list_ != nullptr) {
146
0
        iter_.SeekToLast();
147
0
      }
148
0
    }
149
150
   protected:
151
0
    void Reset(Bucket* list) {
152
0
      if (own_list_) {
153
0
        assert(list_ != nullptr);
154
0
        delete list_;
155
0
      }
156
0
      list_ = list;
157
0
      iter_.SetList(list);
158
0
      own_list_ = false;
159
0
    }
160
161
   private:
162
    // if list_ is nullptr, we should NEVER call any methods on iter_
163
    // if list_ is nullptr, this Iterator is not Valid()
164
    Bucket* list_;
165
    Bucket::Iterator iter_;
166
    // here we track if we own list_. If we own it, we are also
167
    // responsible for it's cleaning. This is a poor man's std::shared_ptr
168
    bool own_list_;
169
    std::unique_ptr<Arena> arena_;
170
    std::string tmp_;  // For passing to EncodeKey
171
  };
172
173
  class DynamicIterator : public HashSkipListRep::Iterator {
174
   public:
175
    explicit DynamicIterator(const HashSkipListRep& memtable_rep)
176
0
        : HashSkipListRep::Iterator(nullptr, false),
177
0
          memtable_rep_(memtable_rep) {}
178
179
    // Advance to the first entry with a key >= target
180
0
    void Seek(const Slice& k, const char* memtable_key) override {
181
0
      auto transformed = memtable_rep_.transform_->Transform(ExtractUserKey(k));
182
0
      Reset(memtable_rep_.GetBucket(transformed));
183
0
      HashSkipListRep::Iterator::Seek(k, memtable_key);
184
0
    }
185
186
    // Position at the first entry in collection.
187
    // Final state of iterator is Valid() iff collection is not empty.
188
0
    void SeekToFirst() override {
189
      // Prefix iterator does not support total order.
190
      // We simply set the iterator to invalid state
191
0
      Reset(nullptr);
192
0
    }
193
194
    // Position at the last entry in collection.
195
    // Final state of iterator is Valid() iff collection is not empty.
196
0
    void SeekToLast() override {
197
      // Prefix iterator does not support total order.
198
      // We simply set the iterator to invalid state
199
0
      Reset(nullptr);
200
0
    }
201
202
   private:
203
    // the underlying memtable
204
    const HashSkipListRep& memtable_rep_;
205
  };
206
207
  class EmptyIterator : public MemTableRep::Iterator {
208
    // This is used when there wasn't a bucket. It is cheaper than
209
    // instantiating an empty bucket over which to iterate.
210
   public:
211
    EmptyIterator() = default;
212
0
    bool Valid() const override { return false; }
213
0
    const char* key() const override {
214
0
      assert(false);
215
0
      return nullptr;
216
0
    }
217
0
    void Next() override {}
218
0
    void Prev() override {}
219
    void Seek(const Slice& /*internal_key*/,
220
0
              const char* /*memtable_key*/) override {}
221
    void SeekForPrev(const Slice& /*internal_key*/,
222
0
                     const char* /*memtable_key*/) override {}
223
0
    void SeekToFirst() override {}
224
0
    void SeekToLast() override {}
225
226
   private:
227
  };
228
};
229
230
HashSkipListRep::HashSkipListRep(const MemTableRep::KeyComparator& compare,
231
                                 Allocator* allocator,
232
                                 const SliceTransform* transform,
233
                                 size_t bucket_size, int32_t skiplist_height,
234
                                 int32_t skiplist_branching_factor)
235
0
    : MemTableRep(allocator),
236
0
      bucket_size_(bucket_size),
237
0
      skiplist_height_(skiplist_height),
238
0
      skiplist_branching_factor_(skiplist_branching_factor),
239
0
      transform_(transform),
240
0
      compare_(compare),
241
0
      allocator_(allocator) {
242
0
  auto mem =
243
0
      allocator->AllocateAligned(sizeof(std::atomic<void*>) * bucket_size);
244
0
  buckets_ = new (mem) std::atomic<Bucket*>[bucket_size];
245
246
0
  for (size_t i = 0; i < bucket_size_; ++i) {
247
0
    buckets_[i].store(nullptr, std::memory_order_relaxed);
248
0
  }
249
0
}
250
251
HashSkipListRep::~HashSkipListRep() = default;
252
253
HashSkipListRep::Bucket* HashSkipListRep::GetInitializedBucket(
254
0
    const Slice& transformed) {
255
0
  size_t hash = GetHash(transformed);
256
0
  auto bucket = GetBucket(hash);
257
0
  if (bucket == nullptr) {
258
0
    auto addr = allocator_->AllocateAligned(sizeof(Bucket));
259
0
    bucket = new (addr) Bucket(compare_, allocator_, skiplist_height_,
260
0
                               skiplist_branching_factor_);
261
0
    buckets_[hash].store(bucket, std::memory_order_release);
262
0
  }
263
0
  return bucket;
264
0
}
265
266
0
void HashSkipListRep::Insert(KeyHandle handle) {
267
0
  auto* key = static_cast<char*>(handle);
268
0
  assert(!Contains(key));
269
0
  auto transformed = transform_->Transform(UserKey(key));
270
0
  auto bucket = GetInitializedBucket(transformed);
271
0
  bucket->Insert(key);
272
0
}
273
274
0
bool HashSkipListRep::Contains(const char* key) const {
275
0
  auto transformed = transform_->Transform(UserKey(key));
276
0
  auto bucket = GetBucket(transformed);
277
0
  if (bucket == nullptr) {
278
0
    return false;
279
0
  }
280
0
  return bucket->Contains(key);
281
0
}
282
283
0
size_t HashSkipListRep::ApproximateMemoryUsage() { return 0; }
284
285
void HashSkipListRep::Get(const LookupKey& k, void* callback_args,
286
0
                          bool (*callback_func)(void* arg, const char* entry)) {
287
0
  auto transformed = transform_->Transform(k.user_key());
288
0
  auto bucket = GetBucket(transformed);
289
0
  if (bucket != nullptr) {
290
0
    Bucket::Iterator iter(bucket);
291
0
    for (iter.Seek(k.memtable_key().data());
292
0
         iter.Valid() && callback_func(callback_args, iter.key());
293
0
         iter.Next()) {
294
0
    }
295
0
  }
296
0
}
297
298
0
MemTableRep::Iterator* HashSkipListRep::GetIterator(Arena* arena) {
299
  // allocate a new arena of similar size to the one currently in use
300
0
  Arena* new_arena = new Arena(allocator_->BlockSize());
301
0
  auto list = new Bucket(compare_, new_arena);
302
0
  for (size_t i = 0; i < bucket_size_; ++i) {
303
0
    auto bucket = GetBucket(i);
304
0
    if (bucket != nullptr) {
305
0
      Bucket::Iterator itr(bucket);
306
0
      for (itr.SeekToFirst(); itr.Valid(); itr.Next()) {
307
0
        list->Insert(itr.key());
308
0
      }
309
0
    }
310
0
  }
311
0
  if (arena == nullptr) {
312
0
    return new Iterator(list, true, new_arena);
313
0
  } else {
314
0
    auto mem = arena->AllocateAligned(sizeof(Iterator));
315
0
    return new (mem) Iterator(list, true, new_arena);
316
0
  }
317
0
}
318
319
0
MemTableRep::Iterator* HashSkipListRep::GetDynamicPrefixIterator(Arena* arena) {
320
0
  if (arena == nullptr) {
321
0
    return new DynamicIterator(*this);
322
0
  } else {
323
0
    auto mem = arena->AllocateAligned(sizeof(DynamicIterator));
324
0
    return new (mem) DynamicIterator(*this);
325
0
  }
326
0
}
327
328
struct HashSkipListRepOptions {
329
0
  static const char* kName() { return "HashSkipListRepFactoryOptions"; }
330
  size_t bucket_count;
331
  int32_t skiplist_height;
332
  int32_t skiplist_branching_factor;
333
};
334
335
static std::unordered_map<std::string, OptionTypeInfo> hash_skiplist_info = {
336
    {"bucket_count",
337
     {offsetof(struct HashSkipListRepOptions, bucket_count), OptionType::kSizeT,
338
      OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
339
    {"skiplist_height",
340
     {offsetof(struct HashSkipListRepOptions, skiplist_height),
341
      OptionType::kInt32T, OptionVerificationType::kNormal,
342
      OptionTypeFlags::kNone}},
343
    {"branching_factor",
344
     {offsetof(struct HashSkipListRepOptions, skiplist_branching_factor),
345
      OptionType::kInt32T, OptionVerificationType::kNormal,
346
      OptionTypeFlags::kNone}},
347
};
348
349
class HashSkipListRepFactory : public MemTableRepFactory {
350
 public:
351
  explicit HashSkipListRepFactory(size_t bucket_count, int32_t skiplist_height,
352
0
                                  int32_t skiplist_branching_factor) {
353
0
    options_.bucket_count = bucket_count;
354
0
    options_.skiplist_height = skiplist_height;
355
0
    options_.skiplist_branching_factor = skiplist_branching_factor;
356
0
    RegisterOptions(&options_, &hash_skiplist_info);
357
0
  }
358
359
  using MemTableRepFactory::CreateMemTableRep;
360
  MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& compare,
361
                                 Allocator* allocator,
362
                                 const SliceTransform* transform,
363
                                 Logger* logger) override;
364
365
0
  static const char* kClassName() { return "HashSkipListRepFactory"; }
366
0
  static const char* kNickName() { return "prefix_hash"; }
367
368
0
  const char* Name() const override { return kClassName(); }
369
0
  const char* NickName() const override { return kNickName(); }
370
371
 private:
372
  HashSkipListRepOptions options_;
373
};
374
375
}  // namespace
376
377
MemTableRep* HashSkipListRepFactory::CreateMemTableRep(
378
    const MemTableRep::KeyComparator& compare, Allocator* allocator,
379
0
    const SliceTransform* transform, Logger* /*logger*/) {
380
0
  return new HashSkipListRep(compare, allocator, transform,
381
0
                             options_.bucket_count, options_.skiplist_height,
382
0
                             options_.skiplist_branching_factor);
383
0
}
384
385
MemTableRepFactory* NewHashSkipListRepFactory(
386
    size_t bucket_count, int32_t skiplist_height,
387
0
    int32_t skiplist_branching_factor) {
388
0
  return new HashSkipListRepFactory(bucket_count, skiplist_height,
389
0
                                    skiplist_branching_factor);
390
0
}
391
392
}  // namespace ROCKSDB_NAMESPACE