Skip to content

Commit 47754c8

Browse files
committed
Optimize duplicate point detection during archival.
1 parent 3d2277b commit 47754c8

File tree

4 files changed

+115
-24
lines changed

4 files changed

+115
-24
lines changed

include/bitcoin/database/impl/query/archive_write.ipp

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ code CLASS::set_code(const tx_link& tx_fk, const transaction& tx) NOEXCEPT
105105
// ========================================================================
106106
const auto scope = store_.get_transactor();
107107

108+
// If dirty we must guard against duplicates.
109+
const auto dirty = store_.is_dirty();
110+
108111
// Allocate contiguously and store inputs.
109112
input_link in_fk{};
110113
if (!store_.input.put_link(in_fk, table::input::put_ref{ {}, tx }))
@@ -158,31 +161,43 @@ code CLASS::set_code(const tx_link& tx_fk, const transaction& tx) NOEXCEPT
158161
if (!store_.point.expand(ins_fk + inputs))
159162
return error::tx_point_allocate;
160163

161-
// Collect duplicates to store in duplicate table.
162-
std::vector<chain::point> twins{};
163-
auto ptr = store_.point.get_memory();
164-
165-
// This must be set after tx.set and before tx.commit, since searchable and
166-
// produces an association to tx.link, and is also an integral part of tx.
167-
for (const auto& in: *ins)
164+
// Must be set after tx.set and before tx.commit, since searchable and
165+
// produces association to tx.link, and is also an integral part of tx.
166+
if (dirty)
168167
{
169-
bool duplicate{};
170-
if (!store_.point.put(duplicate, ptr, ins_fk++, in->point(),
171-
table::point::record{}))
172-
return error::tx_point_put;
173-
174-
if (duplicate)
175-
twins.push_back(in->point());
168+
// Collect duplicates to store in duplicate table.
169+
std::vector<chain::point> twins{};
170+
auto ptr = store_.point.get_memory();
171+
for (const auto& in: *ins)
172+
{
173+
bool duplicate{};
174+
if (!store_.point.put(duplicate, ptr, ins_fk++, in->point(),
175+
table::point::record{}))
176+
return error::tx_point_put;
177+
178+
if (duplicate)
179+
twins.push_back(in->point());
180+
}
181+
182+
ptr.reset();
183+
184+
// As few duplicates are expected, duplicate domain is only 2^16.
185+
// Return of tx_duplicate_put implies link domain has overflowed.
186+
for (const auto& twin: twins)
187+
if (!store_.duplicate.exists(twin))
188+
if (!store_.duplicate.put(twin, table::duplicate::record{}))
189+
return error::tx_duplicate_put;
176190
}
191+
else
192+
{
193+
auto ptr = store_.point.get_memory();
194+
for (const auto& in: *ins)
195+
if (!store_.point.put(ptr, ins_fk++, in->point(),
196+
table::point::record{}))
197+
return error::tx_point_put;
177198

178-
ptr.reset();
179-
180-
// As few duplicates are expected, the duplicate domain is only 2^16.
181-
// Return of tx_duplicate_put implies that the link domain has overflowed.
182-
for (const auto& twin: twins)
183-
if (!store_.duplicate.exists(twin))
184-
if (!store_.duplicate.put(twin, table::duplicate::record{}))
185-
return error::tx_duplicate_put;
199+
ptr.reset();
200+
}
186201
}
187202

188203
// Commit address index records (hashmap).

include/bitcoin/database/impl/store.ipp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <bitcoin/database/boost.hpp>
2525
#include <bitcoin/database/define.hpp>
2626
#include <bitcoin/database/file/file.hpp>
27+
#include <bitcoin/database/tables/schema.hpp>
2728

2829
// TODO: evaluate performance benefits of concurrency.
2930

@@ -542,7 +543,7 @@ code CLASS::reload(const event_handler& handler) NOEXCEPT
542543
}
543544

544545
code ec{ error::success };
545-
const auto reload = [&handler](code& ec, auto& storage,
546+
const auto reload = [&handler, this](code& ec, auto& storage,
546547
table_t table) NOEXCEPT
547548
{
548549
if (!ec)
@@ -552,6 +553,7 @@ code CLASS::reload(const event_handler& handler) NOEXCEPT
552553
{
553554
handler(event_t::load_file, table);
554555
ec = storage.reload();
556+
this->dirty_ = true;
555557
}
556558
}
557559
};
@@ -763,6 +765,8 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT
763765
load(ec, filter_tx_head_, table_t::filter_tx_head);
764766
load(ec, filter_tx_body_, table_t::filter_tx_body);
765767

768+
// create, open, and restore each invoke open_load.
769+
dirty_ = header_body_.size() > schema::header::minrow;
766770
return ec;
767771
}
768772

@@ -1149,6 +1153,12 @@ const typename CLASS::transactor CLASS::get_transactor() NOEXCEPT
11491153
return transactor{ transactor_mutex_ };
11501154
}
11511155

1156+
TEMPLATE
1157+
bool CLASS::is_dirty() const NOEXCEPT
1158+
{
1159+
return dirty_;
1160+
}
1161+
11521162
TEMPLATE
11531163
code CLASS::get_fault() const NOEXCEPT
11541164
{

include/bitcoin/database/store.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ class store
9090
/// Get a transactor object.
9191
const transactor get_transactor() NOEXCEPT;
9292

93+
/// Determine if the store is non-empty/initialized.
94+
bool is_dirty() const NOEXCEPT;
95+
9396
/// Get first fault code or error::success.
9497
code get_fault() const NOEXCEPT;
9598

@@ -229,6 +232,7 @@ class store
229232
flush_lock flush_lock_;
230233
interprocess_lock process_lock_;
231234
std::shared_timed_mutex transactor_mutex_{};
235+
bool dirty_{ true };
232236

233237
private:
234238
using path = std::filesystem::path;

test/store.cpp

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
#include "mocks/blocks.hpp"
2121
#include "mocks/map_store.hpp"
2222

23-
// these are the slow tests (mmap)
23+
// these include the slow tests (mmap)
2424

2525
BOOST_FIXTURE_TEST_SUITE(store_tests, test::directory_setup_fixture)
2626

@@ -45,6 +45,68 @@ BOOST_AUTO_TEST_CASE(store__construct__default_configuration__referenced)
4545
BOOST_REQUIRE_EQUAL(&instance.configuration(), &configuration);
4646
}
4747

48+
BOOST_AUTO_TEST_CASE(store__is_dirty__uninitialized__true)
49+
{
50+
const settings configuration{};
51+
store<map> instance{ configuration };
52+
BOOST_REQUIRE(instance.is_dirty());
53+
}
54+
55+
BOOST_AUTO_TEST_CASE(store__is_dirty__initialized__true)
56+
{
57+
settings configuration{};
58+
configuration.path = TEST_DIRECTORY;
59+
store<map> instance{ configuration };
60+
query<store<map>> query_{ instance };
61+
BOOST_REQUIRE(!instance.create(events));
62+
BOOST_REQUIRE(query_.initialize(test::genesis));
63+
BOOST_REQUIRE(!instance.is_dirty());
64+
BOOST_REQUIRE(!instance.close(events));
65+
}
66+
67+
BOOST_AUTO_TEST_CASE(store__is_dirty__open__false)
68+
{
69+
settings configuration{};
70+
configuration.path = TEST_DIRECTORY;
71+
store<map> instance{ configuration };
72+
query<store<map>> query_{ instance };
73+
BOOST_REQUIRE(!instance.create(events));
74+
BOOST_REQUIRE(query_.initialize(test::genesis));
75+
BOOST_REQUIRE(!instance.is_dirty());
76+
BOOST_REQUIRE(!instance.close(events));
77+
}
78+
79+
BOOST_AUTO_TEST_CASE(store__is_dirty__open_add_header__false)
80+
{
81+
settings configuration{};
82+
configuration.path = TEST_DIRECTORY;
83+
store<map> instance{ configuration };
84+
query<store<map>> query_{ instance };
85+
BOOST_REQUIRE(!instance.create(events));
86+
BOOST_REQUIRE(query_.initialize(test::genesis));
87+
BOOST_REQUIRE(query_.set(system::chain::header{}, context{}, false));
88+
BOOST_REQUIRE(!instance.is_dirty());
89+
BOOST_REQUIRE(!instance.close(events));
90+
}
91+
92+
BOOST_AUTO_TEST_CASE(store__is_dirty__open_with_two_headers__true)
93+
{
94+
settings configuration{};
95+
configuration.path = TEST_DIRECTORY;
96+
97+
store<map> instance1{ configuration };
98+
query<store<map>> query1_{ instance1 };
99+
BOOST_REQUIRE(!instance1.create(events));
100+
BOOST_REQUIRE(query1_.initialize(test::genesis));
101+
BOOST_REQUIRE(query1_.set(system::chain::header{}, context{}, false));
102+
BOOST_REQUIRE(!instance1.close(events));
103+
104+
store<map> instance2{ configuration };
105+
BOOST_REQUIRE(!instance1.open(events));
106+
BOOST_REQUIRE(instance2.is_dirty());
107+
BOOST_REQUIRE(!instance1.close(events));
108+
}
109+
48110
BOOST_AUTO_TEST_CASE(store__paths__default_configuration__expected)
49111
{
50112
const settings configuration{};

0 commit comments

Comments
 (0)