rrudb 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/LICENSE.txt +22 -0
- data/README.md +26 -0
- data/examples/example.rb +39 -0
- data/ext/rudb/NuDB/include/nudb/CMakeLists.txt +104 -0
- data/ext/rudb/NuDB/include/nudb/_experimental/basic_seconds_clock.hpp +200 -0
- data/ext/rudb/NuDB/include/nudb/_experimental/chrono_util.hpp +58 -0
- data/ext/rudb/NuDB/include/nudb/_experimental/test/fail_file.hpp +343 -0
- data/ext/rudb/NuDB/include/nudb/_experimental/test/temp_dir.hpp +73 -0
- data/ext/rudb/NuDB/include/nudb/_experimental/test/test_store.hpp +451 -0
- data/ext/rudb/NuDB/include/nudb/_experimental/test/xor_shift_engine.hpp +105 -0
- data/ext/rudb/NuDB/include/nudb/_experimental/util.hpp +288 -0
- data/ext/rudb/NuDB/include/nudb/basic_store.hpp +461 -0
- data/ext/rudb/NuDB/include/nudb/concepts.hpp +205 -0
- data/ext/rudb/NuDB/include/nudb/context.hpp +144 -0
- data/ext/rudb/NuDB/include/nudb/create.hpp +117 -0
- data/ext/rudb/NuDB/include/nudb/detail/arena.hpp +296 -0
- data/ext/rudb/NuDB/include/nudb/detail/bucket.hpp +473 -0
- data/ext/rudb/NuDB/include/nudb/detail/buffer.hpp +86 -0
- data/ext/rudb/NuDB/include/nudb/detail/bulkio.hpp +196 -0
- data/ext/rudb/NuDB/include/nudb/detail/cache.hpp +236 -0
- data/ext/rudb/NuDB/include/nudb/detail/endian.hpp +93 -0
- data/ext/rudb/NuDB/include/nudb/detail/field.hpp +265 -0
- data/ext/rudb/NuDB/include/nudb/detail/format.hpp +630 -0
- data/ext/rudb/NuDB/include/nudb/detail/gentex.hpp +259 -0
- data/ext/rudb/NuDB/include/nudb/detail/mutex.hpp +26 -0
- data/ext/rudb/NuDB/include/nudb/detail/pool.hpp +243 -0
- data/ext/rudb/NuDB/include/nudb/detail/store_base.hpp +45 -0
- data/ext/rudb/NuDB/include/nudb/detail/stream.hpp +149 -0
- data/ext/rudb/NuDB/include/nudb/detail/xxhash.hpp +328 -0
- data/ext/rudb/NuDB/include/nudb/error.hpp +257 -0
- data/ext/rudb/NuDB/include/nudb/file.hpp +55 -0
- data/ext/rudb/NuDB/include/nudb/impl/basic_store.ipp +785 -0
- data/ext/rudb/NuDB/include/nudb/impl/context.ipp +241 -0
- data/ext/rudb/NuDB/include/nudb/impl/create.ipp +163 -0
- data/ext/rudb/NuDB/include/nudb/impl/error.ipp +175 -0
- data/ext/rudb/NuDB/include/nudb/impl/posix_file.ipp +248 -0
- data/ext/rudb/NuDB/include/nudb/impl/recover.ipp +209 -0
- data/ext/rudb/NuDB/include/nudb/impl/rekey.ipp +248 -0
- data/ext/rudb/NuDB/include/nudb/impl/verify.ipp +634 -0
- data/ext/rudb/NuDB/include/nudb/impl/visit.ipp +96 -0
- data/ext/rudb/NuDB/include/nudb/impl/win32_file.ipp +264 -0
- data/ext/rudb/NuDB/include/nudb/native_file.hpp +76 -0
- data/ext/rudb/NuDB/include/nudb/nudb.hpp +27 -0
- data/ext/rudb/NuDB/include/nudb/posix_file.hpp +228 -0
- data/ext/rudb/NuDB/include/nudb/progress.hpp +32 -0
- data/ext/rudb/NuDB/include/nudb/recover.hpp +73 -0
- data/ext/rudb/NuDB/include/nudb/rekey.hpp +110 -0
- data/ext/rudb/NuDB/include/nudb/store.hpp +27 -0
- data/ext/rudb/NuDB/include/nudb/type_traits.hpp +63 -0
- data/ext/rudb/NuDB/include/nudb/verify.hpp +200 -0
- data/ext/rudb/NuDB/include/nudb/version.hpp +21 -0
- data/ext/rudb/NuDB/include/nudb/visit.hpp +63 -0
- data/ext/rudb/NuDB/include/nudb/win32_file.hpp +246 -0
- data/ext/rudb/NuDB/include/nudb/xxhasher.hpp +45 -0
- data/ext/rudb/extconf.rb +12 -0
- data/ext/rudb/rudb.cpp +234 -0
- data/lib/rudb/version.rb +3 -0
- data/lib/rudb.rb +1 -0
- 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
|