Skip to content

Commit ee929cd

Browse files
committed
Changed random eviction cache to accounts only
1 parent 6c8c90b commit ee929cd

15 files changed

+169
-55
lines changed

docs/stellar-core_example.cfg

+6-7
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,12 @@ 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_CACHED_PERCENT (Integer) default 10
239-
# Percentage of entries cached by BucketListDB when Bucket size is larger
240-
# than BUCKETLIST_DB_INDEX_CUTOFF. Note that this value does not impact
241-
# Buckets smaller than BUCKETLIST_DB_INDEX_CUTOFF, as they are always
242-
# completely held in memory. Roughly speaking, RAM usage for BucketList
243-
# cache == BucketListSize * (BUCKETLIST_DB_CACHED_PERCENT / 100).
244-
BUCKETLIST_DB_CACHED_PERCENT = 10
238+
# BUCKETLIST_DB_MEMORY_FOR_CACHING (Integer) default 3000
239+
# Memory used for caching entries by BucketListDB when Bucket size is
240+
# larger than BUCKETLIST_DB_INDEX_CUTOFF, in MB. Note that this value does
241+
# not impact Buckets smaller than BUCKETLIST_DB_INDEX_CUTOFF, as they are
242+
# always completely held in memory. If set to 0, caching is disabled.
243+
BUCKETLIST_DB_MEMORY_FOR_CACHING = 3000
245244

246245
# BUCKETLIST_DB_INDEX_CUTOFF (Integer) default 100
247246
# Size, in MB, determining whether a bucket should have an individual

src/bucket/BucketIndexUtils.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ namespace stellar
1919
std::streamoff
2020
getPageSizeFromConfig(Config const& cfg)
2121
{
22-
if (cfg.BUCKETLIST_DB_INDEX_PAGE_SIZE_EXPONENT == 0 ||
23-
cfg.BUCKETLIST_DB_CACHED_PERCENT == 100)
22+
if (cfg.BUCKETLIST_DB_INDEX_PAGE_SIZE_EXPONENT == 0)
2423
{
2524
return 0;
2625
}

src/bucket/BucketIndexUtils.h

+4
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,17 @@ std::streamoff getPageSizeFromConfig(Config const& cfg);
9898
// Builds index for given bucketfile. This is expensive (> 20 seconds
9999
// for the largest buckets) and should only be called once. Constructs a
100100
// DiskIndex or InMemoryIndex depending on config and Bucket size.
101+
// Note: Constructor does not initialize the cache for live bucket indexes,
102+
// as this must be done when the Bucket is being added to the BucketList
101103
template <class BucketT>
102104
std::unique_ptr<typename BucketT::IndexT const>
103105
createIndex(BucketManager& bm, std::filesystem::path const& filename,
104106
Hash const& hash, asio::io_context& ctx, SHA256* hasher);
105107

106108
// Loads index from given file. If file does not exist or if saved
107109
// index does not have expected version or pageSize, return null
110+
// Note: Constructor does not initialize the cache for live bucket indexes,
111+
// as this must be done when the Bucket is being added to the BucketList
108112
template <class BucketT>
109113
std::unique_ptr<typename BucketT::IndexT const>
110114
loadIndex(BucketManager const& bm, std::filesystem::path const& filename,

src/bucket/BucketManager.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -1291,6 +1291,9 @@ BucketManager::assumeState(HistoryArchiveState const& has,
12911291
mLiveBucketList->getLevel(i).setNext(nextFuture);
12921292
}
12931293

1294+
mLiveBucketList->maybeInitializeCaches(
1295+
mConfig.maxAccountsInBucketListCache());
1296+
12941297
if (restartMerges)
12951298
{
12961299
mLiveBucketList->restartMerges(mApp, maxProtocolVersion,

src/bucket/LiveBucket.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,15 @@ LiveBucket::getBucketVersion() const
431431
return it.getMetadata().ledgerVersion;
432432
}
433433

434+
void
435+
LiveBucket::maybeInitializeCache(size_t bucketListTotalAccounts,
436+
size_t maxBucketListAccountsToCache) const
437+
{
438+
releaseAssert(mIndex);
439+
mIndex->maybeInitializeCache(bucketListTotalAccounts,
440+
maxBucketListAccountsToCache);
441+
}
442+
434443
BucketEntryCounters const&
435444
LiveBucket::getBucketEntryCounters() const
436445
{

src/bucket/LiveBucket.h

+3
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ class LiveBucket : public BucketBase<LiveBucket, LiveBucketIndex>,
120120

121121
uint32_t getBucketVersion() const;
122122

123+
void maybeInitializeCache(size_t bucketListTotalAccounts,
124+
size_t maxBucketListAccountsToCache) const;
125+
123126
BucketEntryCounters const& getBucketEntryCounters() const;
124127

125128
friend class LiveBucketSnapshot;

src/bucket/LiveBucketIndex.cpp

+50-16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "bucket/LiveBucketIndex.h"
66
#include "bucket/BucketIndexUtils.h"
77
#include "bucket/BucketManager.h"
8+
#include "bucket/BucketUtils.h"
89
#include "bucket/DiskIndex.h"
910
#include "util/Fs.h"
1011
#include "util/GlobalChecks.h"
@@ -65,21 +66,6 @@ LiveBucketIndex::LiveBucketIndex(BucketManager& bm,
6566
pageSize, filename);
6667
mDiskIndex = std::make_unique<DiskIndex<LiveBucket>>(
6768
bm, filename, pageSize, hash, ctx, hasher);
68-
69-
auto percentCached = bm.getConfig().BUCKETLIST_DB_CACHED_PERCENT;
70-
if (percentCached > 0)
71-
{
72-
auto const& counters = mDiskIndex->getBucketEntryCounters();
73-
auto cacheSize = (counters.numEntries() * percentCached) / 100;
74-
75-
// Minimum cache size of 100 if we are going to cache a non-zero
76-
// number of entries
77-
// We don't want to reserve here, since caches only live as long as
78-
// the lifetime of the Bucket and fill relatively slowly
79-
mCache = std::make_unique<CacheT>(std::max<size_t>(cacheSize, 100),
80-
/*separatePRNG=*/false,
81-
/*reserve=*/false);
82-
}
8369
}
8470
}
8571

@@ -95,6 +81,44 @@ LiveBucketIndex::LiveBucketIndex(BucketManager const& bm, Archive& ar,
9581
releaseAssertOrThrow(pageSize != 0);
9682
}
9783

84+
void
85+
LiveBucketIndex::maybeInitializeCache(size_t bucketListTotalAccounts,
86+
size_t maxBucketListAccountsToCache) const
87+
{
88+
// Everything is already in memory, no need for a redundant cache, or we've
89+
// already initialized this cache
90+
if (mInMemoryIndex || mCache)
91+
{
92+
return;
93+
}
94+
95+
releaseAssert(mDiskIndex);
96+
97+
auto accountsInThisBucket =
98+
mDiskIndex->getBucketEntryCounters().entryTypeCounts.at(
99+
LedgerEntryTypeAndDurability::ACCOUNT);
100+
101+
// Nothing to cache
102+
if (accountsInThisBucket == 0)
103+
{
104+
return;
105+
}
106+
107+
if (bucketListTotalAccounts < maxBucketListAccountsToCache)
108+
{
109+
// We can cache the entire bucket
110+
mCache = std::make_unique<CacheT>(bucketListTotalAccounts);
111+
}
112+
else
113+
{
114+
double percentAccountsInBucket =
115+
static_cast<double>(accountsInThisBucket) / bucketListTotalAccounts;
116+
auto cacheSize = static_cast<size_t>(bucketListTotalAccounts *
117+
percentAccountsInBucket);
118+
mCache = std::make_unique<CacheT>(cacheSize);
119+
}
120+
}
121+
98122
LiveBucketIndex::IterT
99123
LiveBucketIndex::begin() const
100124
{
@@ -133,7 +157,7 @@ LiveBucketIndex::markBloomMiss() const
133157
std::shared_ptr<BucketEntry const>
134158
LiveBucketIndex::getCachedEntry(LedgerKey const& k) const
135159
{
136-
if (shouldUseCache())
160+
if (shouldUseCache() && isCachedType(k))
137161
{
138162
std::shared_lock<std::shared_mutex> lock(mCacheMutex);
139163
auto cachePtr = mCache->maybeGet(k);
@@ -262,6 +286,12 @@ LiveBucketIndex::getBucketEntryCounters() const
262286
return mInMemoryIndex->getBucketEntryCounters();
263287
}
264288

289+
bool
290+
LiveBucketIndex::isCachedType(LedgerKey const& lk)
291+
{
292+
return lk.type() == ACCOUNT;
293+
}
294+
265295
void
266296
LiveBucketIndex::maybeAddToCache(
267297
std::shared_ptr<BucketEntry const> const& entry) const
@@ -270,6 +300,10 @@ LiveBucketIndex::maybeAddToCache(
270300
{
271301
releaseAssertOrThrow(entry);
272302
auto k = getBucketLedgerKey(*entry);
303+
if (!isCachedType(k))
304+
{
305+
return;
306+
}
273307

274308
// If we are adding an entry to the cache, we must have missed it
275309
// earlier.

src/bucket/LiveBucketIndex.h

+18-3
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,13 @@ class LiveBucketIndex : public NonMovableOrCopyable
5959
RandomEvictionCache<LedgerKey, std::shared_ptr<BucketEntry const>>;
6060

6161
private:
62-
std::unique_ptr<DiskIndex<LiveBucket> const> mDiskIndex;
63-
std::unique_ptr<InMemoryIndex const> mInMemoryIndex;
64-
std::unique_ptr<CacheT> mCache;
62+
std::unique_ptr<DiskIndex<LiveBucket> const> mDiskIndex{};
63+
std::unique_ptr<InMemoryIndex const> mInMemoryIndex{};
6564

6665
// The indexes themselves are thread safe, as they are immutable after
6766
// construction. The cache is not, all accesses must first acquire this
6867
// mutex.
68+
mutable std::unique_ptr<CacheT> mCache{};
6969
mutable std::shared_mutex mCacheMutex;
7070

7171
medida::Meter& mCacheHitMeter;
@@ -89,6 +89,11 @@ class LiveBucketIndex : public NonMovableOrCopyable
8989
return mDiskIndex && mCache;
9090
}
9191

92+
// Because we already have an LTX level cache at apply time, we only need to
93+
// cache entry types that are used during TX validation and flooding.
94+
// Currently, that is only ACCOUNT.
95+
static bool isCachedType(LedgerKey const& lk);
96+
9297
// Returns nullptr if cache is not enabled or entry not found
9398
std::shared_ptr<BucketEntry const> getCachedEntry(LedgerKey const& k) const;
9499

@@ -97,14 +102,24 @@ class LiveBucketIndex : public NonMovableOrCopyable
97102
inline static const uint32_t BUCKET_INDEX_VERSION = 5;
98103

99104
// Constructor for creating new index from Bucketfile
105+
// Note: Constructor does not initialize the cache
100106
LiveBucketIndex(BucketManager& bm, std::filesystem::path const& filename,
101107
Hash const& hash, asio::io_context& ctx, SHA256* hasher);
102108

103109
// Constructor for loading pre-existing index from disk
110+
// Note: Constructor does not initialize the cache
104111
template <class Archive>
105112
LiveBucketIndex(BucketManager const& bm, Archive& ar,
106113
std::streamoff pageSize);
107114

115+
// Initializes the random eviction cache if it has not already been
116+
// initialized. The random eviction cache itself has an entry limit, but we
117+
// expose a memory limit in the validator config. To account for this, we
118+
// check what percentage of the total number of accounts are in this bucket
119+
// and allocate space accordingly.
120+
void maybeInitializeCache(size_t bucketListTotalAccounts,
121+
size_t maxBucketListAccountsToCache) const;
122+
108123
// Returns true if LedgerEntryType not supported by BucketListDB
109124
static bool typeNotSupported(LedgerEntryType t);
110125

src/bucket/LiveBucketList.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ LiveBucketList::addBatch(Application& app, uint32_t currLedger,
2121
ZoneScoped;
2222
addBatchInternal(app, currLedger, currLedgerProtocol, initEntries,
2323
liveEntries, deadEntries);
24+
25+
// Initialize caches for any new buckets we might have added
26+
maybeInitializeCaches(app.getConfig().maxAccountsInBucketListCache());
2427
}
2528

2629
BucketEntryCounters
@@ -41,6 +44,30 @@ LiveBucketList::sumBucketEntryCounters() const
4144
return counters;
4245
}
4346

47+
void
48+
LiveBucketList::maybeInitializeCaches(size_t maxBucketListAccountsToCache) const
49+
{
50+
auto blCounters = sumBucketEntryCounters();
51+
size_t totalAccounts =
52+
blCounters.entryTypeCounts.at(LedgerEntryTypeAndDurability::ACCOUNT);
53+
for (uint32_t i = 0; i < kNumLevels; ++i)
54+
{
55+
auto curr = mLevels[i].getCurr();
56+
if (!curr->isEmpty())
57+
{
58+
curr->maybeInitializeCache(totalAccounts,
59+
maxBucketListAccountsToCache);
60+
}
61+
62+
auto snap = mLevels[i].getSnap();
63+
if (!snap->isEmpty())
64+
{
65+
snap->maybeInitializeCache(totalAccounts,
66+
maxBucketListAccountsToCache);
67+
}
68+
}
69+
}
70+
4471
void
4572
LiveBucketList::updateStartingEvictionIterator(EvictionIterator& iter,
4673
uint32_t firstScanLevel,

src/bucket/LiveBucketList.h

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ class LiveBucketList : public BucketListBase<LiveBucket>
5050
std::vector<LedgerKey> const& deadEntries);
5151

5252
BucketEntryCounters sumBucketEntryCounters() const;
53+
54+
// Initializes any uninitialized caches in the BucketIndex. Should be called
55+
// after every time buckets may have changed in the LiveBucketList.
56+
void maybeInitializeCaches(size_t maxBucketListAccountsToCache) const;
5357
};
5458

5559
}

src/bucket/readme.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ lookup speed and memory overhead. The following configuration flags control thes
9797
on startup. Defaults to true, should only be set to false for testing purposes.
9898
Validators do not currently support persisted indexes. If NODE_IS_VALIDATOR=true,
9999
this value is ignored and indexes are never persisted.
100-
- `BUCKETLIST_DB_CACHED_PERCENT`
101-
- Percentage of entries cached by BucketListDB when Bucket size is larger
102-
than `BUCKETLIST_DB_INDEX_CUTOFF`. Note that this value does not impact
100+
- `BUCKETLIST_DB_MEMORY_FOR_CACHING`
101+
- Memory used for caching entries by BucketListDB when Bucket size is larger
102+
than `BUCKETLIST_DB_INDEX_CUTOFF`, in MB. Note that this value does not impact
103103
Buckets smaller than `BUCKETLIST_DB_INDEX_CUTOFF`, as they are always
104104
completely held in memory.

src/bucket/test/BucketIndexTests.cpp

+13-9
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,20 @@ class BucketIndexTest
6363
}
6464

6565
void
66-
buildBucketList(std::function<void(std::vector<LedgerEntry>&)> f)
66+
buildBucketList(std::function<void(std::vector<LedgerEntry>&)> f,
67+
bool accountOnly = false)
6768
{
6869
uint32_t ledger = 0;
6970
do
7071
{
7172
++ledger;
7273
std::vector<LedgerEntry> entries =
73-
LedgerTestUtils::generateValidLedgerEntriesWithExclusions(
74-
{CONFIG_SETTING}, 10);
74+
accountOnly
75+
? LedgerTestUtils::
76+
generateValidUniqueLedgerEntriesWithTypes({ACCOUNT},
77+
10)
78+
: LedgerTestUtils::generateValidLedgerEntriesWithExclusions(
79+
{CONFIG_SETTING}, 10);
7580
f(entries);
7681
closeLedger(*mApp);
7782
} while (!LiveBucketList::levelShouldSpill(ledger, mLevelsToBuild - 1));
@@ -92,7 +97,7 @@ class BucketIndexTest
9297
}
9398

9499
virtual void
95-
buildGeneralTest()
100+
buildGeneralTest(bool accountOnly = false)
96101
{
97102
auto f = [&](std::vector<LedgerEntry> const& entries) {
98103
// Sample ~4% of entries
@@ -109,7 +114,7 @@ class BucketIndexTest
109114
{}, entries, {});
110115
};
111116

112-
buildBucketList(f);
117+
buildBucketList(f, accountOnly);
113118
}
114119

115120
void
@@ -513,7 +518,7 @@ class BucketIndexPoolShareTest : public BucketIndexTest
513518
}
514519

515520
virtual void
516-
buildGeneralTest() override
521+
buildGeneralTest(bool accountOnly = false) override
517522
{
518523
buildTest(false);
519524
}
@@ -581,12 +586,11 @@ TEST_CASE("bl cache", "[bucket][bucketindex]")
581586
Config cfg(getTestConfig());
582587

583588
// Use disk index for all levels and cache all entries
584-
// Note: Setting this to 100% will actually switch to in-memory index
585-
cfg.BUCKETLIST_DB_CACHED_PERCENT = 99;
586589
cfg.BUCKETLIST_DB_INDEX_CUTOFF = 0;
590+
cfg.BUCKETLIST_DB_MEMORY_FOR_CACHING = 5'000;
587591

588592
auto test = BucketIndexTest(cfg);
589-
test.buildGeneralTest();
593+
test.buildGeneralTest(/*accountOnly=*/true);
590594
test.run(/*testCache=*/true);
591595
}
592596

0 commit comments

Comments
 (0)