Skip to content

Commit 321bbf5

Browse files
codablockUdjinM6
andcommitted
Fix excessive memory use when flushing chainstate and EvoDB (#3008)
* Specialize Serialize(CSizeComputer&) for BLS objects Speeds up size computation for BLS objects. * Track memory usage in CDBTransaction and CEvoDB * Take memory used by CEvoDB/CDBTransaction into account when flushing * Implement specialized SerReadWrite for immer maps This allows to use READWRITE and fixes compilation errors with CSizeComputer * Log EvoDB memory usage independently Co-Authored-By: UdjinM6 <UdjinM6@users.noreply.github.com>
1 parent 0410259 commit 321bbf5

File tree

5 files changed

+71
-12
lines changed

5 files changed

+71
-12
lines changed

src/bls/bls.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ class CBLSWrapper
170170
}
171171

172172
public:
173+
inline void Serialize(CSizeComputer& s) const
174+
{
175+
s.seek(SerSize);
176+
}
177+
173178
template <typename Stream>
174179
inline void Serialize(Stream& s) const
175180
{
@@ -356,6 +361,11 @@ class CBLSLazyWrapper
356361
return *this;
357362
}
358363

364+
inline void Serialize(CSizeComputer& s) const
365+
{
366+
s.seek(BLSObject::SerSize);
367+
}
368+
359369
template<typename Stream>
360370
inline void Serialize(Stream& s) const
361371
{

src/dbwrapper.h

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ class CDBTransaction {
551551
protected:
552552
Parent &parent;
553553
CommitTarget &commitTarget;
554+
ssize_t memoryUsage{0}; // signed, just in case we made an error in the calculations so that we don't get an overflow
554555

555556
struct DataStreamCmp {
556557
static bool less(const CDataStream& a, const CDataStream& b) {
@@ -564,14 +565,16 @@ class CDBTransaction {
564565
};
565566

566567
struct ValueHolder {
568+
size_t memoryUsage;
569+
ValueHolder(size_t _memoryUsage) : memoryUsage(_memoryUsage) {}
567570
virtual ~ValueHolder() = default;
568571
virtual void Write(const CDataStream& ssKey, CommitTarget &parent) = 0;
569572
};
570573
typedef std::unique_ptr<ValueHolder> ValueHolderPtr;
571574

572575
template <typename V>
573576
struct ValueHolderImpl : ValueHolder {
574-
ValueHolderImpl(const V &_value) : value(_value) { }
577+
ValueHolderImpl(const V &_value, size_t _memoryUsage) : ValueHolder(_memoryUsage), value(_value) {}
575578

576579
virtual void Write(const CDataStream& ssKey, CommitTarget &commitTarget) {
577580
// we're moving the value instead of copying it. This means that Write() can only be called once per
@@ -605,9 +608,18 @@ class CDBTransaction {
605608

606609
template <typename V>
607610
void Write(const CDataStream& ssKey, const V& v) {
608-
deletes.erase(ssKey);
611+
auto valueMemoryUsage = ::GetSerializeSize(v, SER_DISK, CLIENT_VERSION);
612+
613+
if (deletes.erase(ssKey)) {
614+
memoryUsage -= ssKey.size();
615+
}
609616
auto it = writes.emplace(ssKey, nullptr).first;
610-
it->second = std::make_unique<ValueHolderImpl<V>>(v);
617+
if (it->second) {
618+
memoryUsage -= ssKey.size() + it->second->memoryUsage;
619+
}
620+
it->second = std::make_unique<ValueHolderImpl<V>>(v, valueMemoryUsage);
621+
622+
memoryUsage += ssKey.size() + valueMemoryUsage;
611623
}
612624

613625
template <typename K, typename V>
@@ -657,13 +669,20 @@ class CDBTransaction {
657669
}
658670

659671
void Erase(const CDataStream& ssKey) {
660-
writes.erase(ssKey);
661-
deletes.emplace(ssKey);
672+
auto it = writes.find(ssKey);
673+
if (it != writes.end()) {
674+
memoryUsage -= ssKey.size() + it->second->memoryUsage;
675+
writes.erase(it);
676+
}
677+
if (deletes.emplace(ssKey).second) {
678+
memoryUsage += ssKey.size();
679+
}
662680
}
663681

664682
void Clear() {
665683
writes.clear();
666684
deletes.clear();
685+
memoryUsage = 0;
667686
}
668687

669688
void Commit() {
@@ -680,6 +699,19 @@ class CDBTransaction {
680699
return writes.empty() && deletes.empty();
681700
}
682701

702+
size_t GetMemoryUsage() const {
703+
if (memoryUsage < 0) {
704+
// something went wrong when we accounted/calculated used memory...
705+
static volatile bool didPrint = false;
706+
if (!didPrint) {
707+
LogPrintf("CDBTransaction::%s -- negative memoryUsage (%d)", __func__, memoryUsage);
708+
didPrint = true;
709+
}
710+
return 0;
711+
}
712+
return (size_t)memoryUsage;
713+
}
714+
683715
CDBTransactionIterator<CDBTransaction>* NewIterator() {
684716
return new CDBTransactionIterator<CDBTransaction>(*this);
685717
}

src/evo/deterministicmns.h

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,21 @@ void UnserializeImmerMap(Stream& is, immer::map<K, T, Hash, Equal>& m)
195195
}
196196
}
197197

198+
// For some reason the compiler is not able to choose the correct Serialize/Deserialize methods without a specialized
199+
// version of SerReadWrite. It otherwise always chooses the version that calls a.Serialize()
200+
template<typename Stream, typename K, typename T, typename Hash, typename Equal>
201+
inline void SerReadWrite(Stream& s, const immer::map<K, T, Hash, Equal>& m, CSerActionSerialize ser_action)
202+
{
203+
::SerializeImmerMap(s, m);
204+
}
205+
206+
template<typename Stream, typename K, typename T, typename Hash, typename Equal>
207+
inline void SerReadWrite(Stream& s, immer::map<K, T, Hash, Equal>& obj, CSerActionUnserialize ser_action)
208+
{
209+
::UnserializeImmerMap(s, obj);
210+
}
211+
212+
198213
class CDeterministicMNList
199214
{
200215
public:
@@ -226,13 +241,8 @@ class CDeterministicMNList
226241
{
227242
READWRITE(blockHash);
228243
READWRITE(nHeight);
229-
if (ser_action.ForRead()) {
230-
UnserializeImmerMap(s, mnMap);
231-
UnserializeImmerMap(s, mnUniquePropertyMap);
232-
} else {
233-
SerializeImmerMap(s, mnMap);
234-
SerializeImmerMap(s, mnUniquePropertyMap);
235-
}
244+
READWRITE(mnMap);
245+
READWRITE(mnUniquePropertyMap);
236246
}
237247

238248
public:

src/evo/evodb.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ class CEvoDB
7373
return db;
7474
}
7575

76+
size_t GetMemoryUsage()
77+
{
78+
return rootDBTransaction.GetMemoryUsage();
79+
}
80+
7681
bool CommitRootTransaction();
7782

7883
bool VerifyBestBlock(const uint256& hash);

src/validation.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2385,6 +2385,7 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode, int n
23852385
}
23862386
int64_t nMempoolSizeMax = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
23872387
int64_t cacheSize = pcoinsTip->DynamicMemoryUsage() * DB_PEAK_USAGE_FACTOR;
2388+
cacheSize += evoDb->GetMemoryUsage() * DB_PEAK_USAGE_FACTOR;
23882389
int64_t nTotalSpace = nCoinCacheUsage + std::max<int64_t>(nMempoolSizeMax - nMempoolUsage, 0);
23892390
// The cache is large and we're within 10% and 10 MiB of the limit, but we have time now (not in the middle of a block processing).
23902391
bool fCacheLarge = mode == FLUSH_STATE_PERIODIC && cacheSize > std::max((9 * nTotalSpace) / 10, nTotalSpace - MAX_BLOCK_COINSDB_USAGE * 1024 * 1024);
@@ -2522,6 +2523,7 @@ void static UpdateTip(CBlockIndex *pindexNew, const CChainParams& chainParams) {
25222523
log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx,
25232524
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),
25242525
GuessVerificationProgress(chainParams.TxData(), chainActive.Tip()), pcoinsTip->DynamicMemoryUsage() * (1.0 / (1<<20)), pcoinsTip->GetCacheSize());
2526+
strMessage += strprintf(" evodb_cache=%.1fMiB", evoDb->GetMemoryUsage() * (1.0 / (1<<20)));
25252527
if (!warningMessages.empty())
25262528
strMessage += strprintf(" warning='%s'", boost::algorithm::join(warningMessages, ", "));
25272529
LogPrintf("%s\n", strMessage);

0 commit comments

Comments
 (0)