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