Skip to content

Commit 96a822e

Browse files
authored
BucketListDB in-memory Buckets (#4630)
# Description Partially resolves #3696 This change refactors the `BucketIndex` parts of BucketListDB to be more friendly towards the Hot Archive BucketList. This includes cleanups to metrics, which previously only tracked metrics for the main thread access of the BucketList. Now, both BucketList types and background threads record metrics properly. Additionally, this change removes the `IndividualIndex` and instead caches small Buckets entirely in-memory so we never read from disk. `RangeIndex` is largely unchanged but has been renamed to `DiskIndex`. A follow up PR will add a random eviction cache to the `DiskIndex`. I tried to break this up as much as possible, but it was easiest to do the refactor + in-memory buckets at the same time so I didn't have to refactor `IndividualIndex`. The `BUCKETLIST_DB_INDEX_CUTOFF` config setting determines the maximum size at which we keep bucket in-memory. I've set this to 250 MB, which is approximately the first 4-5 levels of the BucketList. This increases total memory consumption of stellar-core from 2.2 GB to 3 GB. This seems reasonable, and we could probably go even higher, but I'm holding off for now as the random eviction cache will further increase memory requirements. # Checklist - [x] Reviewed the [contributing](https://github.com/stellar/stellar-core/blob/master/CONTRIBUTING.md#submitting-changes) document - [x] Rebased on top of master (no merge commits) - [x] Ran `clang-format` v8.0.0 (via `make format` or the Visual Studio extension) - [x] Compiles - [x] Ran all tests - [ ] If change impacts performance, include supporting evidence per the [performance document](https://github.com/stellar/stellar-core/blob/master/performance-eval/performance-eval.md)
2 parents 8d8266f + 3fc04c1 commit 96a822e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2320
-1555
lines changed

docs/metrics.md

+7-7
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ bucketlist-archive.size.bytes | counter | total size of the hot ar
3838
bucketlist.size.bytes | counter | total size of the BucketList in bytes
3939
bucketlist.entryCounts.-<X> | counter | number of entries of type <X> in the BucketList
4040
bucketlist.entrySizes.-<X> | counter | size of entries of type <X> in the BucketList
41-
bucketlistDB.bloom.lookups | meter | number of bloom filter lookups
42-
bucketlistDB.bloom.misses | meter | number of bloom filter false positives
43-
bucketlistDB.bulk.loads | meter | number of entries BucketListDB queried to prefetch
44-
bucketlistDB.bulk.inflationWinners | timer | time to load inflation winners
45-
bucketlistDB.bulk.poolshareTrustlines | timer | time to load poolshare trustlines by accountID and assetID
46-
bucketlistDB.bulk.prefetch | timer | time to prefetch
47-
bucketlistDB.point.<X> | timer | time to load single entry of type <X> (if no bloom miss occurred)
41+
bucketlistDB-<X>.bloom.lookups | meter | number of bloom filter lookups on BucketList <X> (live/hotArchive)
42+
bucketlistDB-<X>.bloom.misses | meter | number of bloom filter false positives on BucketList <X> (live/hotArchive)
43+
bucketlistDB-<X>.bulk.loads | meter | number of entries BucketListDB queried to prefetch on BucketList <X> (live/hot-archive)
44+
bucketlistDB-live.bulk.inflationWinners | timer | time to load inflation winners
45+
bucketlistDB-live.bulk.poolshareTrustlines | timer | time to load poolshare trustlines by accountID and assetID
46+
bucketlistDB-live.bulk.prefetch | timer | time to prefetch
47+
bucketlistDB-<X>.point.<y> | timer | time to load single entry of type <Y> on BucketList <X> (live/hotArchive)
4848
crypto.verify.hit | meter | number of signature cache hits
4949
crypto.verify.miss | meter | number of signature cache misses
5050
crypto.verify.total | meter | sum of both hits and misses

docs/stellar-core_example.cfg

+2-2
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,13 @@ MAX_DEX_TX_OPERATIONS_IN_TX_SET = 0
235235
# 0, indiviudal index is always used. Default page size 16 kb.
236236
BUCKETLIST_DB_INDEX_PAGE_SIZE_EXPONENT = 14
237237

238-
# BUCKETLIST_DB_INDEX_CUTOFF (Integer) default 20
238+
# BUCKETLIST_DB_INDEX_CUTOFF (Integer) default 250
239239
# Size, in MB, determining whether a bucket should have an individual
240240
# key index or a key range index. If bucket size is below this value, range
241241
# based index will be used. If set to 0, all buckets are range indexed. If
242242
# BUCKETLIST_DB_INDEX_PAGE_SIZE_EXPONENT == 0, value ingnored and all
243243
# buckets have individual key index.
244-
BUCKETLIST_DB_INDEX_CUTOFF = 20
244+
BUCKETLIST_DB_INDEX_CUTOFF = 250
245245

246246
# BUCKETLIST_DB_PERSIST_INDEX (bool) default true
247247
# Determines whether BucketListDB indexes are saved to disk for faster

src/bucket/BucketApplicator.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,15 @@ shouldApplyEntry(BucketEntry const& e)
8282
{
8383
if (e.type() == LIVEENTRY || e.type() == INITENTRY)
8484
{
85-
return BucketIndex::typeNotSupported(e.liveEntry().data.type());
85+
return LiveBucketIndex::typeNotSupported(e.liveEntry().data.type());
8686
}
8787

8888
if (e.type() != DEADENTRY)
8989
{
9090
throw std::runtime_error(
9191
"Malformed bucket: unexpected non-INIT/LIVE/DEAD entry.");
9292
}
93-
return BucketIndex::typeNotSupported(e.deadEntry().type());
93+
return LiveBucketIndex::typeNotSupported(e.deadEntry().type());
9494
}
9595

9696
size_t

src/bucket/BucketBase.cpp

+49-54
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
// else.
88
#include "util/asio.h" // IWYU pragma: keep
99
#include "bucket/BucketBase.h"
10-
#include "bucket/BucketIndex.h"
1110
#include "bucket/BucketInputIterator.h"
1211
#include "bucket/BucketManager.h"
1312
#include "bucket/BucketOutputIterator.h"
@@ -30,36 +29,35 @@
3029
namespace stellar
3130
{
3231

33-
BucketIndex const&
34-
BucketBase::getIndex() const
32+
template <class BucketT, class IndexT>
33+
IndexT const&
34+
BucketBase<BucketT, IndexT>::getIndex() const
3535
{
3636
ZoneScoped;
3737
releaseAssertOrThrow(!mFilename.empty());
3838
releaseAssertOrThrow(mIndex);
3939
return *mIndex;
4040
}
4141

42+
template <class BucketT, class IndexT>
4243
bool
43-
BucketBase::isIndexed() const
44+
BucketBase<BucketT, IndexT>::isIndexed() const
4445
{
4546
return static_cast<bool>(mIndex);
4647
}
4748

48-
std::optional<std::pair<std::streamoff, std::streamoff>>
49-
BucketBase::getOfferRange() const
50-
{
51-
return getIndex().getOfferRange();
52-
}
53-
49+
template <class BucketT, class IndexT>
5450
void
55-
BucketBase::setIndex(std::unique_ptr<BucketIndex const>&& index)
51+
BucketBase<BucketT, IndexT>::setIndex(std::unique_ptr<IndexT const>&& index)
5652
{
5753
releaseAssertOrThrow(!mIndex);
5854
mIndex = std::move(index);
5955
}
6056

61-
BucketBase::BucketBase(std::string const& filename, Hash const& hash,
62-
std::unique_ptr<BucketIndex const>&& index)
57+
template <class BucketT, class IndexT>
58+
BucketBase<BucketT, IndexT>::BucketBase(std::string const& filename,
59+
Hash const& hash,
60+
std::unique_ptr<IndexT const>&& index)
6361
: mFilename(filename), mHash(hash), mIndex(std::move(index))
6462
{
6563
releaseAssert(filename.empty() || fs::exists(filename));
@@ -71,30 +69,34 @@ BucketBase::BucketBase(std::string const& filename, Hash const& hash,
7169
}
7270
}
7371

74-
BucketBase::BucketBase()
72+
template <class BucketT, class IndexT> BucketBase<BucketT, IndexT>::BucketBase()
7573
{
7674
}
7775

76+
template <class BucketT, class IndexT>
7877
Hash const&
79-
BucketBase::getHash() const
78+
BucketBase<BucketT, IndexT>::getHash() const
8079
{
8180
return mHash;
8281
}
8382

83+
template <class BucketT, class IndexT>
8484
std::filesystem::path const&
85-
BucketBase::getFilename() const
85+
BucketBase<BucketT, IndexT>::getFilename() const
8686
{
8787
return mFilename;
8888
}
8989

90+
template <class BucketT, class IndexT>
9091
size_t
91-
BucketBase::getSize() const
92+
BucketBase<BucketT, IndexT>::getSize() const
9293
{
9394
return mSize;
9495
}
9596

97+
template <class BucketT, class IndexT>
9698
bool
97-
BucketBase::isEmpty() const
99+
BucketBase<BucketT, IndexT>::isEmpty() const
98100
{
99101
if (mFilename.empty() || isZero(mHash))
100102
{
@@ -105,14 +107,17 @@ BucketBase::isEmpty() const
105107
return false;
106108
}
107109

110+
template <class BucketT, class IndexT>
108111
void
109-
BucketBase::freeIndex()
112+
BucketBase<BucketT, IndexT>::freeIndex()
110113
{
111114
mIndex.reset(nullptr);
112115
}
113116

117+
template <class BucketT, class IndexT>
114118
std::string
115-
BucketBase::randomFileName(std::string const& tmpDir, std::string ext)
119+
BucketBase<BucketT, IndexT>::randomFileName(std::string const& tmpDir,
120+
std::string ext)
116121
{
117122
ZoneScoped;
118123
for (;;)
@@ -127,14 +132,16 @@ BucketBase::randomFileName(std::string const& tmpDir, std::string ext)
127132
}
128133
}
129134

135+
template <class BucketT, class IndexT>
130136
std::string
131-
BucketBase::randomBucketName(std::string const& tmpDir)
137+
BucketBase<BucketT, IndexT>::randomBucketName(std::string const& tmpDir)
132138
{
133139
return randomFileName(tmpDir, ".xdr");
134140
}
135141

142+
template <class BucketT, class IndexT>
136143
std::string
137-
BucketBase::randomBucketIndexName(std::string const& tmpDir)
144+
BucketBase<BucketT, IndexT>::randomBucketIndexName(std::string const& tmpDir)
138145
{
139146
return randomFileName(tmpDir, ".index");
140147
}
@@ -172,7 +179,7 @@ BucketBase::randomBucketIndexName(std::string const& tmpDir)
172179
// and shadowing protocol simultaneously, the moment the first new-protocol
173180
// bucket enters the youngest level. At least one new bucket is in every merge's
174181
// shadows from then on in, so they all upgrade (and preserve lifecycle events).
175-
template <class BucketT>
182+
template <class BucketT, class IndexT>
176183
static void
177184
calculateMergeProtocolVersion(
178185
MergeCounters& mc, uint32_t maxProtocolVersion,
@@ -253,7 +260,7 @@ calculateMergeProtocolVersion(
253260
// side, or entries that compare non-equal. In all these cases we just
254261
// take the lesser (or existing) entry and advance only one iterator,
255262
// not scrutinizing the entry type further.
256-
template <class BucketT>
263+
template <class BucketT, class IndexT>
257264
static bool
258265
mergeCasesWithDefaultAcceptance(
259266
BucketEntryIdCmp<BucketT> const& cmp, MergeCounters& mc,
@@ -299,14 +306,15 @@ mergeCasesWithDefaultAcceptance(
299306
return false;
300307
}
301308

302-
template <class BucketT>
309+
template <class BucketT, class IndexT>
303310
std::shared_ptr<BucketT>
304-
BucketBase::merge(BucketManager& bucketManager, uint32_t maxProtocolVersion,
305-
std::shared_ptr<BucketT> const& oldBucket,
306-
std::shared_ptr<BucketT> const& newBucket,
307-
std::vector<std::shared_ptr<BucketT>> const& shadows,
308-
bool keepTombstoneEntries, bool countMergeEvents,
309-
asio::io_context& ctx, bool doFsync)
311+
BucketBase<BucketT, IndexT>::merge(
312+
BucketManager& bucketManager, uint32_t maxProtocolVersion,
313+
std::shared_ptr<BucketT> const& oldBucket,
314+
std::shared_ptr<BucketT> const& newBucket,
315+
std::vector<std::shared_ptr<BucketT>> const& shadows,
316+
bool keepTombstoneEntries, bool countMergeEvents, asio::io_context& ctx,
317+
bool doFsync)
310318
{
311319
BUCKET_TYPE_ASSERT(BucketT);
312320

@@ -326,9 +334,9 @@ BucketBase::merge(BucketManager& bucketManager, uint32_t maxProtocolVersion,
326334

327335
uint32_t protocolVersion;
328336
bool keepShadowedLifecycleEntries;
329-
calculateMergeProtocolVersion<BucketT>(mc, maxProtocolVersion, oi, ni,
330-
shadowIterators, protocolVersion,
331-
keepShadowedLifecycleEntries);
337+
calculateMergeProtocolVersion<BucketT, IndexT>(
338+
mc, maxProtocolVersion, oi, ni, shadowIterators, protocolVersion,
339+
keepShadowedLifecycleEntries);
332340

333341
auto timer = bucketManager.getMergeTimer().TimeScope();
334342
BucketMetadata meta;
@@ -340,14 +348,14 @@ BucketBase::merge(BucketManager& bucketManager, uint32_t maxProtocolVersion,
340348
{
341349
releaseAssertOrThrow(protocolVersionStartsFrom(
342350
maxProtocolVersion,
343-
BucketBase::FIRST_PROTOCOL_SUPPORTING_PERSISTENT_EVICTION));
351+
BucketT::FIRST_PROTOCOL_SUPPORTING_PERSISTENT_EVICTION));
344352
meta.ext = ni.getMetadata().ext;
345353
}
346354
else if (oi.getMetadata().ext.v() == 1)
347355
{
348356
releaseAssertOrThrow(protocolVersionStartsFrom(
349357
maxProtocolVersion,
350-
BucketBase::FIRST_PROTOCOL_SUPPORTING_PERSISTENT_EVICTION));
358+
BucketT::FIRST_PROTOCOL_SUPPORTING_PERSISTENT_EVICTION));
351359
meta.ext = oi.getMetadata().ext;
352360
}
353361

@@ -374,9 +382,9 @@ BucketBase::merge(BucketManager& bucketManager, uint32_t maxProtocolVersion,
374382
}
375383
}
376384

377-
if (!mergeCasesWithDefaultAcceptance(cmp, mc, oi, ni, out,
378-
shadowIterators, protocolVersion,
379-
keepShadowedLifecycleEntries))
385+
if (!mergeCasesWithDefaultAcceptance<BucketT, IndexT>(
386+
cmp, mc, oi, ni, out, shadowIterators, protocolVersion,
387+
keepShadowedLifecycleEntries))
380388
{
381389
BucketT::mergeCasesWithEqualKeys(mc, oi, ni, out, shadowIterators,
382390
protocolVersion,
@@ -400,19 +408,6 @@ BucketBase::merge(BucketManager& bucketManager, uint32_t maxProtocolVersion,
400408
return out.getBucket(bucketManager, &mk);
401409
}
402410

403-
template std::shared_ptr<LiveBucket> BucketBase::merge<LiveBucket>(
404-
BucketManager& bucketManager, uint32_t maxProtocolVersion,
405-
std::shared_ptr<LiveBucket> const& oldBucket,
406-
std::shared_ptr<LiveBucket> const& newBucket,
407-
std::vector<std::shared_ptr<LiveBucket>> const& shadows,
408-
bool keepTombstoneEntries, bool countMergeEvents, asio::io_context& ctx,
409-
bool doFsync);
410-
411-
template std::shared_ptr<HotArchiveBucket> BucketBase::merge<HotArchiveBucket>(
412-
BucketManager& bucketManager, uint32_t maxProtocolVersion,
413-
std::shared_ptr<HotArchiveBucket> const& oldBucket,
414-
std::shared_ptr<HotArchiveBucket> const& newBucket,
415-
std::vector<std::shared_ptr<HotArchiveBucket>> const& shadows,
416-
bool keepTombstoneEntries, bool countMergeEvents, asio::io_context& ctx,
417-
bool doFsync);
411+
template class BucketBase<LiveBucket, LiveBucket::IndexT>;
412+
template class BucketBase<HotArchiveBucket, HotArchiveBucket::IndexT>;
418413
}

src/bucket/BucketBase.h

+27-15
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// under the apache license, version 2.0. see the copying file at the root
55
// of this distribution or at http://www.apache.org/licenses/license-2.0
66

7-
#include "bucket/BucketIndex.h"
7+
#include "bucket/BucketUtils.h"
88
#include "util/NonCopyable.h"
99
#include "util/ProtocolVersion.h"
1010
#include "xdr/Stellar-types.h"
@@ -47,17 +47,37 @@ enum class Loop
4747
INCOMPLETE
4848
};
4949

50+
class HotArchiveBucket;
51+
class LiveBucket;
52+
class LiveBucketIndex;
53+
class HotArchiveBucketIndex;
54+
55+
template <class BucketT, class IndexT>
5056
class BucketBase : public NonMovableOrCopyable
5157
{
58+
BUCKET_TYPE_ASSERT(BucketT);
59+
60+
// Because of the CRTP design with derived Bucket classes, this base class
61+
// does not have direct access to BucketT::IndexT, so we take two templates
62+
// and make this assert.
63+
static_assert(
64+
std::is_same_v<
65+
IndexT,
66+
std::conditional_t<
67+
std::is_same_v<BucketT, LiveBucket>, LiveBucketIndex,
68+
std::conditional_t<std::is_same_v<BucketT, HotArchiveBucket>,
69+
HotArchiveBucketIndex, void>>>,
70+
"IndexT must match BucketT::IndexT");
71+
5272
protected:
5373
std::filesystem::path const mFilename;
5474
Hash const mHash;
5575
size_t mSize{0};
5676

57-
std::unique_ptr<BucketIndex const> mIndex{};
77+
std::unique_ptr<IndexT const> mIndex{};
5878

5979
// Returns index, throws if index not yet initialized
60-
BucketIndex const& getIndex() const;
80+
IndexT const& getIndex() const;
6181

6282
static std::string randomFileName(std::string const& tmpDir,
6383
std::string ext);
@@ -74,7 +94,7 @@ class BucketBase : public NonMovableOrCopyable
7494
// exists, but does not check that the hash is the bucket's hash. Caller
7595
// needs to ensure that.
7696
BucketBase(std::string const& filename, Hash const& hash,
77-
std::unique_ptr<BucketIndex const>&& index);
97+
std::unique_ptr<IndexT const>&& index);
7898

7999
Hash const& getHash() const;
80100
std::filesystem::path const& getFilename() const;
@@ -88,13 +108,8 @@ class BucketBase : public NonMovableOrCopyable
88108
// Returns true if bucket is indexed, false otherwise
89109
bool isIndexed() const;
90110

91-
// Returns [lowerBound, upperBound) of file offsets for all offers in the
92-
// bucket, or std::nullopt if no offers exist
93-
std::optional<std::pair<std::streamoff, std::streamoff>>
94-
getOfferRange() const;
95-
96111
// Sets index, throws if index is already set
97-
void setIndex(std::unique_ptr<BucketIndex const>&& index);
112+
void setIndex(std::unique_ptr<IndexT const>&& index);
98113

99114
// Merge two buckets together, producing a fresh one. Entries in `oldBucket`
100115
// are overridden in the fresh bucket by keywise-equal entries in
@@ -107,7 +122,6 @@ class BucketBase : public NonMovableOrCopyable
107122
// `maxProtocolVersion` bounds this (for error checking) and should usually
108123
// be the protocol of the ledger header at which the merge is starting. An
109124
// exception will be thrown if any provided bucket versions exceed it.
110-
template <class BucketT>
111125
static std::shared_ptr<BucketT>
112126
merge(BucketManager& bucketManager, uint32_t maxProtocolVersion,
113127
std::shared_ptr<BucketT> const& oldBucket,
@@ -120,16 +134,14 @@ class BucketBase : public NonMovableOrCopyable
120134
static std::string randomBucketIndexName(std::string const& tmpDir);
121135

122136
#ifdef BUILD_TESTS
123-
BucketIndex const&
137+
IndexT const&
124138
getIndexForTesting() const
125139
{
126140
return getIndex();
127141
}
128142

129143
#endif // BUILD_TESTS
130144

131-
virtual uint32_t getBucketVersion() const = 0;
132-
133-
template <class BucketT> friend class BucketSnapshotBase;
145+
template <class T> friend class BucketSnapshotBase;
134146
};
135147
}

0 commit comments

Comments
 (0)