rrudb 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|