rrudb 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +26 -0
  5. data/examples/example.rb +39 -0
  6. data/ext/rudb/NuDB/include/nudb/CMakeLists.txt +104 -0
  7. data/ext/rudb/NuDB/include/nudb/_experimental/basic_seconds_clock.hpp +200 -0
  8. data/ext/rudb/NuDB/include/nudb/_experimental/chrono_util.hpp +58 -0
  9. data/ext/rudb/NuDB/include/nudb/_experimental/test/fail_file.hpp +343 -0
  10. data/ext/rudb/NuDB/include/nudb/_experimental/test/temp_dir.hpp +73 -0
  11. data/ext/rudb/NuDB/include/nudb/_experimental/test/test_store.hpp +451 -0
  12. data/ext/rudb/NuDB/include/nudb/_experimental/test/xor_shift_engine.hpp +105 -0
  13. data/ext/rudb/NuDB/include/nudb/_experimental/util.hpp +288 -0
  14. data/ext/rudb/NuDB/include/nudb/basic_store.hpp +461 -0
  15. data/ext/rudb/NuDB/include/nudb/concepts.hpp +205 -0
  16. data/ext/rudb/NuDB/include/nudb/context.hpp +144 -0
  17. data/ext/rudb/NuDB/include/nudb/create.hpp +117 -0
  18. data/ext/rudb/NuDB/include/nudb/detail/arena.hpp +296 -0
  19. data/ext/rudb/NuDB/include/nudb/detail/bucket.hpp +473 -0
  20. data/ext/rudb/NuDB/include/nudb/detail/buffer.hpp +86 -0
  21. data/ext/rudb/NuDB/include/nudb/detail/bulkio.hpp +196 -0
  22. data/ext/rudb/NuDB/include/nudb/detail/cache.hpp +236 -0
  23. data/ext/rudb/NuDB/include/nudb/detail/endian.hpp +93 -0
  24. data/ext/rudb/NuDB/include/nudb/detail/field.hpp +265 -0
  25. data/ext/rudb/NuDB/include/nudb/detail/format.hpp +630 -0
  26. data/ext/rudb/NuDB/include/nudb/detail/gentex.hpp +259 -0
  27. data/ext/rudb/NuDB/include/nudb/detail/mutex.hpp +26 -0
  28. data/ext/rudb/NuDB/include/nudb/detail/pool.hpp +243 -0
  29. data/ext/rudb/NuDB/include/nudb/detail/store_base.hpp +45 -0
  30. data/ext/rudb/NuDB/include/nudb/detail/stream.hpp +149 -0
  31. data/ext/rudb/NuDB/include/nudb/detail/xxhash.hpp +328 -0
  32. data/ext/rudb/NuDB/include/nudb/error.hpp +257 -0
  33. data/ext/rudb/NuDB/include/nudb/file.hpp +55 -0
  34. data/ext/rudb/NuDB/include/nudb/impl/basic_store.ipp +785 -0
  35. data/ext/rudb/NuDB/include/nudb/impl/context.ipp +241 -0
  36. data/ext/rudb/NuDB/include/nudb/impl/create.ipp +163 -0
  37. data/ext/rudb/NuDB/include/nudb/impl/error.ipp +175 -0
  38. data/ext/rudb/NuDB/include/nudb/impl/posix_file.ipp +248 -0
  39. data/ext/rudb/NuDB/include/nudb/impl/recover.ipp +209 -0
  40. data/ext/rudb/NuDB/include/nudb/impl/rekey.ipp +248 -0
  41. data/ext/rudb/NuDB/include/nudb/impl/verify.ipp +634 -0
  42. data/ext/rudb/NuDB/include/nudb/impl/visit.ipp +96 -0
  43. data/ext/rudb/NuDB/include/nudb/impl/win32_file.ipp +264 -0
  44. data/ext/rudb/NuDB/include/nudb/native_file.hpp +76 -0
  45. data/ext/rudb/NuDB/include/nudb/nudb.hpp +27 -0
  46. data/ext/rudb/NuDB/include/nudb/posix_file.hpp +228 -0
  47. data/ext/rudb/NuDB/include/nudb/progress.hpp +32 -0
  48. data/ext/rudb/NuDB/include/nudb/recover.hpp +73 -0
  49. data/ext/rudb/NuDB/include/nudb/rekey.hpp +110 -0
  50. data/ext/rudb/NuDB/include/nudb/store.hpp +27 -0
  51. data/ext/rudb/NuDB/include/nudb/type_traits.hpp +63 -0
  52. data/ext/rudb/NuDB/include/nudb/verify.hpp +200 -0
  53. data/ext/rudb/NuDB/include/nudb/version.hpp +21 -0
  54. data/ext/rudb/NuDB/include/nudb/visit.hpp +63 -0
  55. data/ext/rudb/NuDB/include/nudb/win32_file.hpp +246 -0
  56. data/ext/rudb/NuDB/include/nudb/xxhasher.hpp +45 -0
  57. data/ext/rudb/extconf.rb +12 -0
  58. data/ext/rudb/rudb.cpp +234 -0
  59. data/lib/rudb/version.rb +3 -0
  60. data/lib/rudb.rb +1 -0
  61. metadata +104 -0
@@ -0,0 +1,785 @@
1
+ //
2
+ // Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
3
+ //
4
+ // Distributed under the Boost Software License, Version 1.0. (See accompanying
5
+ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6
+ //
7
+
8
+ #ifndef NUDB_IMPL_BASIC_STORE_IPP
9
+ #define NUDB_IMPL_BASIC_STORE_IPP
10
+
11
+ #include <nudb/concepts.hpp>
12
+ #include <nudb/recover.hpp>
13
+ #include <boost/assert.hpp>
14
+ #include <cmath>
15
+ #include <memory>
16
+
17
+ #ifndef NUDB_DEBUG_LOG
18
+ #define NUDB_DEBUG_LOG 0
19
+ #endif
20
+ #if NUDB_DEBUG_LOG
21
+ #include <boost/beast/_experimental/unit_test/dstream.hpp>
22
+ #include <iostream>
23
+ #endif
24
+
25
+ namespace nudb {
26
+
27
+ template<class Hasher, class File>
28
+ basic_store<Hasher, File>::state::
29
+ state(File&& df_, File&& kf_, File&& lf_,
30
+ path_type const& dp_, path_type const& kp_,
31
+ path_type const& lp_,
32
+ detail::key_file_header const& kh_)
33
+ : df(std::move(df_))
34
+ , kf(std::move(kf_))
35
+ , lf(std::move(lf_))
36
+ , dp(dp_)
37
+ , kp(kp_)
38
+ , lp(lp_)
39
+ , hasher(kh_.salt)
40
+ , p0(kh_.key_size, "p0")
41
+ , p1(kh_.key_size, "p1")
42
+ , c1(kh_.key_size, kh_.block_size, "c1")
43
+ , kh(kh_)
44
+ {
45
+ static_assert(is_File<File>::value,
46
+ "File requirements not met");
47
+ }
48
+
49
+ //------------------------------------------------------------------------------
50
+
51
+ template<class Hasher, class File>
52
+ basic_store<Hasher, File>::
53
+ ~basic_store()
54
+ {
55
+ error_code ec;
56
+ // We call close here to make sure data is intact
57
+ // if an exception destroys the basic_store, but callers
58
+ // should always call close manually to receive the
59
+ // error code.
60
+ close(ec);
61
+ }
62
+
63
+ template<class Hasher, class File>
64
+ path_type const&
65
+ basic_store<Hasher, File>::
66
+ dat_path() const
67
+ {
68
+ BOOST_ASSERT(is_open());
69
+ return s_->dp;
70
+ }
71
+
72
+ template<class Hasher, class File>
73
+ path_type const&
74
+ basic_store<Hasher, File>::
75
+ key_path() const
76
+ {
77
+ BOOST_ASSERT(is_open());
78
+ return s_->kp;
79
+ }
80
+
81
+ template<class Hasher, class File>
82
+ path_type const&
83
+ basic_store<Hasher, File>::
84
+ log_path() const
85
+ {
86
+ BOOST_ASSERT(is_open());
87
+ return s_->lp;
88
+ }
89
+
90
+ template<class Hasher, class File>
91
+ std::uint64_t
92
+ basic_store<Hasher, File>::
93
+ appnum() const
94
+ {
95
+ BOOST_ASSERT(is_open());
96
+ return s_->kh.appnum;
97
+ }
98
+
99
+ template<class Hasher, class File>
100
+ std::size_t
101
+ basic_store<Hasher, File>::
102
+ key_size() const
103
+ {
104
+ BOOST_ASSERT(is_open());
105
+ return s_->kh.key_size;
106
+ }
107
+
108
+ template<class Hasher, class File>
109
+ std::size_t
110
+ basic_store<Hasher, File>::
111
+ block_size() const
112
+ {
113
+ BOOST_ASSERT(is_open());
114
+ return s_->kh.block_size;
115
+ }
116
+
117
+ template<class Hasher, class File>
118
+ template<class... Args>
119
+ void
120
+ basic_store<Hasher, File>::
121
+ open(
122
+ path_type const& dat_path,
123
+ path_type const& key_path,
124
+ path_type const& log_path,
125
+ error_code& ec,
126
+ Args&&... args)
127
+ {
128
+ static_assert(is_Hasher<Hasher>::value,
129
+ "Hasher requirements not met");
130
+ using namespace detail;
131
+ BOOST_ASSERT(! is_open());
132
+ ec_ = {};
133
+ ecb_.store(false);
134
+ recover<Hasher, File>(
135
+ dat_path, key_path, log_path, ec, args...);
136
+ if(ec)
137
+ return;
138
+ File df(args...);
139
+ File kf(args...);
140
+ File lf(args...);
141
+ df.open(file_mode::append, dat_path, ec);
142
+ if(ec)
143
+ return;
144
+ kf.open(file_mode::write, key_path, ec);
145
+ if(ec)
146
+ return;
147
+ lf.create(file_mode::append, log_path, ec);
148
+ if(ec)
149
+ return;
150
+ // VFALCO TODO Erase empty log file if this
151
+ // function subsequently fails.
152
+ dat_file_header dh;
153
+ read(df, dh, ec);
154
+ if(ec)
155
+ return;
156
+ verify(dh, ec);
157
+ if(ec)
158
+ return;
159
+ key_file_header kh;
160
+ read(kf, kh, ec);
161
+ if(ec)
162
+ return;
163
+ verify<Hasher>(kh, ec);
164
+ if(ec)
165
+ return;
166
+ verify<Hasher>(dh, kh, ec);
167
+ if(ec)
168
+ return;
169
+ boost::optional<state> s;
170
+ s.emplace(std::move(df), std::move(kf), std::move(lf),
171
+ dat_path, key_path, log_path, kh);
172
+ thresh_ = std::max<std::size_t>(65536UL,
173
+ kh.load_factor * kh.capacity);
174
+ frac_ = thresh_ / 2;
175
+ buckets_ = kh.buckets;
176
+ modulus_ = ceil_pow2(kh.buckets);
177
+ // VFALCO TODO This could be better
178
+ if(buckets_ < 1)
179
+ {
180
+ ec = error::short_key_file;
181
+ return;
182
+ }
183
+ dataWriteSize_ = 32 * nudb::block_size(dat_path);
184
+ logWriteSize_ = 32 * nudb::block_size(log_path);
185
+ s_.emplace(std::move(*s));
186
+ open_ = true;
187
+ ctx_->insert(*this);
188
+ }
189
+
190
+ template<class Hasher, class File>
191
+ void
192
+ basic_store<Hasher, File>::
193
+ close(error_code& ec)
194
+ {
195
+ if(open_)
196
+ {
197
+ open_ = false;
198
+ ctx_->erase(*this);
199
+ if(! s_->p1.empty())
200
+ {
201
+ std::size_t work;
202
+ detail::unique_lock_type m{m_};
203
+ commit(m, work, ec_);
204
+ if (ec_)
205
+ ecb_.store(true);
206
+ }
207
+ if(ecb_)
208
+ {
209
+ ec = ec_;
210
+ return;
211
+ }
212
+ s_->lf.close();
213
+ state s{std::move(*s_)};
214
+ File::erase(s.lp, ec_);
215
+ if(ec_)
216
+ ec = ec_;
217
+ }
218
+ }
219
+
220
+ template<class Hasher, class File>
221
+ template<class Callback>
222
+ void
223
+ basic_store<Hasher, File>::
224
+ fetch(
225
+ void const* key,
226
+ Callback && callback,
227
+ error_code& ec)
228
+ {
229
+ using namespace detail;
230
+ BOOST_ASSERT(is_open());
231
+ if(ecb_)
232
+ {
233
+ ec = ec_;
234
+ return;
235
+ }
236
+ auto const h =
237
+ hash(key, s_->kh.key_size, s_->hasher);
238
+ shared_lock_type m{m_};
239
+ {
240
+ auto iter = s_->p1.find(key);
241
+ if(iter == s_->p1.end())
242
+ {
243
+ iter = s_->p0.find(key);
244
+ if(iter == s_->p0.end())
245
+ goto cont;
246
+ }
247
+ callback(iter->first.data, iter->first.size);
248
+ return;
249
+ }
250
+ cont:
251
+ auto const n = bucket_index(h, buckets_, modulus_);
252
+ auto const iter = s_->c1.find(n);
253
+ if(iter != s_->c1.end())
254
+ return fetch(h, key, iter->second, callback, ec);
255
+ genlock<gentex> g{g_};
256
+ m.unlock();
257
+ buffer buf{s_->kh.block_size};
258
+ // b constructs from uninitialized buf
259
+ bucket b{s_->kh.block_size, buf.get()};
260
+ b.read(s_->kf, (n + 1) * b.block_size(), ec);
261
+ if(ec)
262
+ return;
263
+ fetch(h, key, b, callback, ec);
264
+ }
265
+
266
+ template<class Hasher, class File>
267
+ void
268
+ basic_store<Hasher, File>::
269
+ insert(
270
+ void const* key,
271
+ void const* data,
272
+ nsize_t size,
273
+ error_code& ec)
274
+ {
275
+ using namespace detail;
276
+ using namespace std::chrono;
277
+ BOOST_ASSERT(is_open());
278
+ if(ecb_)
279
+ {
280
+ ec = ec_;
281
+ return;
282
+ }
283
+ // Data Record
284
+ BOOST_ASSERT(size > 0); // zero disallowed
285
+ BOOST_ASSERT(size <= field<uint32_t>::max); // too large
286
+ auto const h =
287
+ hash(key, s_->kh.key_size, s_->hasher);
288
+ std::lock_guard<std::mutex> u{u_};
289
+ {
290
+ shared_lock_type m{m_};
291
+ if(s_->p1.find(key) != s_->p1.end() ||
292
+ s_->p0.find(key) != s_->p0.end())
293
+ {
294
+ ec = error::key_exists;
295
+ return;
296
+ }
297
+ auto const n = bucket_index(h, buckets_, modulus_);
298
+ auto const iter = s_->c1.find(n);
299
+ if(iter != s_->c1.end())
300
+ {
301
+ auto const found = exists(
302
+ h, key, &m, iter->second, ec);
303
+ if(ec)
304
+ return;
305
+ if(found)
306
+ {
307
+ ec = error::key_exists;
308
+ return;
309
+ }
310
+ // m is now unlocked
311
+ }
312
+ else
313
+ {
314
+ // VFALCO Audit for concurrency
315
+ genlock<gentex> g{g_};
316
+ m.unlock();
317
+ buffer buf;
318
+ buf.reserve(s_->kh.block_size);
319
+ bucket b{s_->kh.block_size, buf.get()};
320
+ b.read(s_->kf,
321
+ static_cast<noff_t>(n + 1) * s_->kh.block_size, ec);
322
+ if(ec)
323
+ return;
324
+ auto const found = exists(h, key, nullptr, b, ec);
325
+ if(ec)
326
+ return;
327
+ if(found)
328
+ {
329
+ ec = error::key_exists;
330
+ return;
331
+ }
332
+ }
333
+ }
334
+ // Perform insert
335
+ unique_lock_type m{m_};
336
+ s_->p1.insert(h, key, data, size);
337
+ auto const now = clock_type::now();
338
+ auto const elapsed = duration_cast<duration<float>>(
339
+ now > s_->when ? now - s_->when : clock_type::duration{1});
340
+ auto const work = s_->p1.data_size() +
341
+ 3 * s_->p1.size() * s_->kh.block_size;
342
+ auto const rate = static_cast<std::size_t>(
343
+ std::ceil(work / elapsed.count()));
344
+ auto const sleep =
345
+ s_->rate && rate > s_->rate;
346
+ m.unlock();
347
+ if(sleep)
348
+ std::this_thread::sleep_for(milliseconds{25});
349
+ }
350
+
351
+ // Fetch key in loaded bucket b or its spills.
352
+ //
353
+ template<class Hasher, class File>
354
+ template<class Callback>
355
+ void
356
+ basic_store<Hasher, File>::
357
+ fetch(
358
+ detail::nhash_t h,
359
+ void const* key,
360
+ detail::bucket b,
361
+ Callback&& callback,
362
+ error_code& ec)
363
+ {
364
+ using namespace detail;
365
+ buffer buf0;
366
+ buffer buf1;
367
+ for(;;)
368
+ {
369
+ for(auto i = b.lower_bound(h); i < b.size(); ++i)
370
+ {
371
+ auto const item = b[i];
372
+ if(item.hash != h)
373
+ break;
374
+ // Data Record
375
+ auto const len =
376
+ s_->kh.key_size + // Key
377
+ item.size; // Value
378
+ buf0.reserve(len);
379
+ s_->df.read(item.offset +
380
+ field<uint48_t>::size, // Size
381
+ buf0.get(), len, ec);
382
+ if(ec)
383
+ return;
384
+ if(std::memcmp(buf0.get(), key,
385
+ s_->kh.key_size) == 0)
386
+ {
387
+ callback(
388
+ buf0.get() + s_->kh.key_size, item.size);
389
+ return;
390
+ }
391
+ }
392
+ auto const spill = b.spill();
393
+ if(! spill)
394
+ break;
395
+ buf1.reserve(s_->kh.block_size);
396
+ b = bucket(s_->kh.block_size,
397
+ buf1.get());
398
+ b.read(s_->df, spill, ec);
399
+ if(ec)
400
+ return;
401
+ }
402
+ ec = error::key_not_found;
403
+ }
404
+
405
+ // Returns `true` if the key exists
406
+ // lock is unlocked after the first bucket processed
407
+ //
408
+ template<class Hasher, class File>
409
+ bool
410
+ basic_store<Hasher, File>::
411
+ exists(
412
+ detail::nhash_t h,
413
+ void const* key,
414
+ detail::shared_lock_type* lock,
415
+ detail::bucket b,
416
+ error_code& ec)
417
+ {
418
+ using namespace detail;
419
+ buffer buf{s_->kh.key_size + s_->kh.block_size};
420
+ void* pk = buf.get();
421
+ void* pb = buf.get() + s_->kh.key_size;
422
+ for(;;)
423
+ {
424
+ for(auto i = b.lower_bound(h); i < b.size(); ++i)
425
+ {
426
+ auto const item = b[i];
427
+ if(item.hash != h)
428
+ break;
429
+ // Data Record
430
+ s_->df.read(item.offset +
431
+ field<uint48_t>::size, // Size
432
+ pk, s_->kh.key_size, ec); // Key
433
+ if(ec)
434
+ return false;
435
+ if(std::memcmp(pk, key, s_->kh.key_size) == 0)
436
+ return true;
437
+ }
438
+ auto spill = b.spill();
439
+ if(lock && lock->owns_lock())
440
+ lock->unlock();
441
+ if(! spill)
442
+ break;
443
+ b = bucket(s_->kh.block_size, pb);
444
+ b.read(s_->df, spill, ec);
445
+ if(ec)
446
+ return false;
447
+ }
448
+ return false;
449
+ }
450
+
451
+ // Split the bucket in b1 to b2
452
+ // b1 must be loaded
453
+ // tmp is used as a temporary buffer
454
+ // splits are written but not the new buckets
455
+ //
456
+ template<class Hasher, class File>
457
+ void
458
+ basic_store<Hasher, File>::
459
+ split(
460
+ detail::bucket& b1,
461
+ detail::bucket& b2,
462
+ detail::bucket& tmp,
463
+ nbuck_t n1,
464
+ nbuck_t n2,
465
+ nbuck_t buckets,
466
+ nbuck_t modulus,
467
+ detail::bulk_writer<File>& w,
468
+ error_code& ec)
469
+ {
470
+ using namespace detail;
471
+ // Trivial case: split empty bucket
472
+ if(b1.empty())
473
+ return;
474
+ // Split
475
+ for(std::size_t i = 0; i < b1.size();)
476
+ {
477
+ auto const e = b1[i];
478
+ auto const n = bucket_index(e.hash, buckets, modulus);
479
+ (void)n1;
480
+ (void)n2;
481
+ BOOST_ASSERT(n==n1 || n==n2);
482
+ if(n == n2)
483
+ {
484
+ b2.insert(e.offset, e.size, e.hash);
485
+ b1.erase(i);
486
+ }
487
+ else
488
+ {
489
+ ++i;
490
+ }
491
+ }
492
+ noff_t spill = b1.spill();
493
+ if(spill)
494
+ {
495
+ b1.spill(0);
496
+ do
497
+ {
498
+ // If any part of the spill record is
499
+ // in the write buffer then flush first
500
+ if(spill + bucket_size(s_->kh.capacity) >
501
+ w.offset() - w.size())
502
+ {
503
+ w.flush(ec);
504
+ if(ec)
505
+ return;
506
+ }
507
+ tmp.read(s_->df, spill, ec);
508
+ if(ec)
509
+ return;
510
+ for(std::size_t i = 0; i < tmp.size(); ++i)
511
+ {
512
+ auto const e = tmp[i];
513
+ auto const n = bucket_index(
514
+ e.hash, buckets, modulus);
515
+ BOOST_ASSERT(n==n1 || n==n2);
516
+ if(n == n2)
517
+ {
518
+ maybe_spill(b2, w, ec);
519
+ if(ec)
520
+ return;
521
+ b2.insert(e.offset, e.size, e.hash);
522
+ }
523
+ else
524
+ {
525
+ maybe_spill(b1, w, ec);
526
+ if(ec)
527
+ return;
528
+ b1.insert(e.offset, e.size, e.hash);
529
+ }
530
+ }
531
+ spill = tmp.spill();
532
+ }
533
+ while(spill);
534
+ }
535
+ }
536
+
537
+ template<class Hasher, class File>
538
+ detail::bucket
539
+ basic_store<Hasher, File>::
540
+ load(
541
+ nbuck_t n,
542
+ detail::cache& c1,
543
+ detail::cache& c0,
544
+ void* buf,
545
+ error_code& ec)
546
+ {
547
+ using namespace detail;
548
+ auto iter = c1.find(n);
549
+ if(iter != c1.end())
550
+ return iter->second;
551
+ iter = c0.find(n);
552
+ if(iter != c0.end())
553
+ return c1.insert(n, iter->second)->second;
554
+ bucket tmp{s_->kh.block_size, buf};
555
+ tmp.read(s_->kf,
556
+ static_cast<noff_t>(n + 1) * s_->kh.block_size, ec);
557
+ if(ec)
558
+ return {};
559
+ c0.insert(n, tmp);
560
+ return c1.insert(n, tmp)->second;
561
+ }
562
+
563
+ template<class Hasher, class File>
564
+ void
565
+ basic_store<Hasher, File>::
566
+ commit(detail::unique_lock_type& m,
567
+ std::size_t& work, error_code& ec)
568
+ {
569
+ using namespace detail;
570
+ BOOST_ASSERT(m.owns_lock());
571
+ BOOST_ASSERT(! s_->p1.empty());
572
+ swap(s_->p0, s_->p1);
573
+ m.unlock();
574
+ work = s_->p0.data_size();
575
+ cache c0(s_->kh.key_size, s_->kh.block_size, "c0");
576
+ cache c1(s_->kh.key_size, s_->kh.block_size, "c1");
577
+ // 0.63212 ~= 1 - 1/e
578
+ {
579
+ auto const size = static_cast<std::size_t>(
580
+ std::ceil(0.63212 * s_->p0.size()));
581
+ c0.reserve(size);
582
+ c1.reserve(size);
583
+ }
584
+ buffer buf1{s_->kh.block_size};
585
+ buffer buf2{s_->kh.block_size};
586
+ bucket tmp{s_->kh.block_size, buf1.get()};
587
+ // Prepare rollback information
588
+ log_file_header lh;
589
+ lh.version = currentVersion; // Version
590
+ lh.uid = s_->kh.uid; // UID
591
+ lh.appnum = s_->kh.appnum; // Appnum
592
+ lh.key_size = s_->kh.key_size; // Key Size
593
+ lh.salt = s_->kh.salt; // Salt
594
+ lh.pepper = pepper<Hasher>(lh.salt); // Pepper
595
+ lh.block_size = s_->kh.block_size; // Block Size
596
+ lh.key_file_size = s_->kf.size(ec); // Key File Size
597
+ if(ec)
598
+ return;
599
+ lh.dat_file_size = s_->df.size(ec); // Data File Size
600
+ if(ec)
601
+ return;
602
+ write(s_->lf, lh, ec);
603
+ if(ec)
604
+ return;
605
+ // Checkpoint
606
+ s_->lf.sync(ec);
607
+ if(ec)
608
+ return;
609
+ // Append data and spills to data file
610
+ auto modulus = modulus_;
611
+ auto buckets = buckets_;
612
+ {
613
+ // Bulk write to avoid write amplification
614
+ auto const size = s_->df.size(ec);
615
+ if(ec)
616
+ return;
617
+ bulk_writer<File> w{s_->df, size, dataWriteSize_};
618
+ // Write inserted data to the data file
619
+ for(auto& e : s_->p0)
620
+ {
621
+ // VFALCO This could be UB since other
622
+ // threads are reading other data members
623
+ // of this object in memory
624
+ e.second = w.offset();
625
+ auto os = w.prepare(value_size(
626
+ e.first.size, s_->kh.key_size), ec);
627
+ if(ec)
628
+ return;
629
+ // Data Record
630
+ write<uint48_t>(os, e.first.size); // Size
631
+ write(os, e.first.key, s_->kh.key_size); // Key
632
+ write(os, e.first.data, e.first.size); // Data
633
+ }
634
+ // Do inserts, splits, and build view
635
+ // of original and modified buckets
636
+ for(auto const& e : s_->p0)
637
+ {
638
+ // VFALCO Should this be >= or > ?
639
+ if((frac_ += 65536) >= thresh_)
640
+ {
641
+ // split
642
+ frac_ -= thresh_;
643
+ if(buckets == modulus)
644
+ modulus *= 2;
645
+ auto const n1 = buckets - (modulus / 2);
646
+ auto const n2 = buckets++;
647
+ auto b1 = load(n1, c1, c0, buf2.get(), ec);
648
+ if(ec)
649
+ return;
650
+ auto b2 = c1.create(n2);
651
+ // If split spills, the writer is
652
+ // flushed which can amplify writes.
653
+ split(b1, b2, tmp, n1, n2,
654
+ buckets, modulus, w, ec);
655
+ if(ec)
656
+ return;
657
+ }
658
+ // Insert
659
+ auto const n = bucket_index(
660
+ e.first.hash, buckets, modulus);
661
+ auto b = load(n, c1, c0, buf2.get(), ec);
662
+ if(ec)
663
+ return;
664
+ // This can amplify writes if it spills.
665
+ maybe_spill(b, w, ec);
666
+ if(ec)
667
+ return;
668
+ b.insert(e.second, e.first.size, e.first.hash);
669
+ }
670
+ w.flush(ec);
671
+ if(ec)
672
+ return;
673
+ }
674
+ work += s_->kh.block_size * (2 * c0.size() + c1.size());
675
+ // Give readers a view of the new buckets.
676
+ // This might be slightly better than the old
677
+ // view since there could be fewer spills.
678
+ m.lock();
679
+ swap(c1, s_->c1);
680
+ s_->p0.clear();
681
+ buckets_ = buckets;
682
+ modulus_ = modulus;
683
+ g_.start();
684
+ m.unlock();
685
+ // Write clean buckets to log file
686
+ {
687
+ auto const size = s_->lf.size(ec);
688
+ if(ec)
689
+ return;
690
+ bulk_writer<File> w{s_->lf, size, logWriteSize_};
691
+ for(auto const e : c0)
692
+ {
693
+ // Log Record
694
+ auto os = w.prepare(
695
+ field<std::uint64_t>::size + // Index
696
+ e.second.actual_size(), ec); // Bucket
697
+ if(ec)
698
+ return;
699
+ // Log Record
700
+ write<std::uint64_t>(os, e.first); // Index
701
+ e.second.write(os); // Bucket
702
+ }
703
+ c0.clear();
704
+ w.flush(ec);
705
+ if(ec)
706
+ return;
707
+ s_->lf.sync(ec);
708
+ if(ec)
709
+ return;
710
+ }
711
+ g_.finish();
712
+ // Write new buckets to key file
713
+ for(auto const e : s_->c1)
714
+ {
715
+ e.second.write(s_->kf,
716
+ (e.first + 1) * s_->kh.block_size, ec);
717
+ if(ec)
718
+ return;
719
+ }
720
+ // Finalize the commit
721
+ s_->df.sync(ec);
722
+ if(ec)
723
+ return;
724
+ s_->kf.sync(ec);
725
+ if(ec)
726
+ return;
727
+ s_->lf.trunc(0, ec);
728
+ if(ec)
729
+ return;
730
+ s_->lf.sync(ec);
731
+ if(ec)
732
+ return;
733
+ // Cache is no longer needed, all fetches will go straight
734
+ // to disk again. Do this after the sync, otherwise readers
735
+ // might get blocked longer due to the extra I/O.
736
+ m.lock();
737
+ s_->c1.clear();
738
+ }
739
+
740
+ template<class Hasher, class File>
741
+ void
742
+ basic_store<Hasher, File>::
743
+ flush()
744
+ {
745
+ using namespace std::chrono;
746
+ using namespace detail;
747
+
748
+ #if NUDB_DEBUG_LOG
749
+ beast::unit_test::dstream dout{std::cout};
750
+ #endif
751
+ {
752
+ unique_lock_type m{m_};
753
+ if(! s_->p1.empty())
754
+ {
755
+ std::size_t work;
756
+ commit(m, work, ec_);
757
+ if(ec_)
758
+ {
759
+ ecb_.store(true);
760
+ return;
761
+ }
762
+ BOOST_ASSERT(m.owns_lock());
763
+ auto const now = clock_type::now();
764
+ auto const elapsed = duration_cast<duration<float>>(
765
+ now > s_->when ? now - s_->when : clock_type::duration{1});
766
+ s_->rate = static_cast<std::size_t>(
767
+ std::ceil(work / elapsed.count()));
768
+ #if NUDB_DEBUG_LOG
769
+ dout <<
770
+ "work=" << work <<
771
+ ", time=" << elapsed.count() <<
772
+ ", rate=" << s_->rate <<
773
+ "\n";
774
+ #endif
775
+ }
776
+ s_->p1.periodic_activity();
777
+ s_->when = clock_type::now();
778
+ }
779
+ if(ec_)
780
+ ecb_.store(true);
781
+ }
782
+
783
+ } // nudb
784
+
785
+ #endif