refactor: Delegate to LevelDB for CDBBatch size estimation

Serialized batch size can be queried via the underlying LevelDB implementation calling the native `leveldb::WriteBatch::ApproximateSize()`.

The previous manual calculation was added in e66dbde6d1 as part of https://github.com/bitcoin/bitcoin/pull/10195. At that time (April 2017), the version of LevelDB used by Bitcoin Core (and even the latest source) lacked a native function for this. LevelDB added this capability in 69e2bd224b, merged later that year.

The old manual estimation method (`SizeEstimate()`) is kept temporarily in this commit, and assertions are added in `txdb.cpp` to verify its results against `ApproximateSize()` during batch writes. This ensures the native function behaves as expected before removing the manual calculation in the subsequent commit.
This commit is contained in:
Lőrinc 2025-04-01 14:06:10 +02:00
parent 751077c6e2
commit 8b5e19d8b5
3 changed files with 28 additions and 3 deletions

View file

@ -200,6 +200,11 @@ void CDBBatch::EraseImpl(std::span<const std::byte> key)
size_estimate += 2 + (slKey.size() > 127) + slKey.size(); size_estimate += 2 + (slKey.size() > 127) + slKey.size();
} }
size_t CDBBatch::ApproximateSize() const
{
return m_impl_batch->batch.ApproximateSize();
}
struct LevelDBContext { struct LevelDBContext {
//! custom environment this database is using (may be nullptr in case of default environment) //! custom environment this database is using (may be nullptr in case of default environment)
leveldb::Env* penv; leveldb::Env* penv;

View file

@ -119,7 +119,8 @@ public:
ssKey.clear(); ssKey.clear();
} }
size_t SizeEstimate() const { return size_estimate; } size_t ApproximateSize() const;
size_t SizeEstimate() const { return size_estimate; } // TODO replace with ApproximateSize
}; };
class CDBIterator class CDBIterator

View file

@ -113,24 +113,39 @@ bool CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashB
// transition from old_tip to hashBlock. // transition from old_tip to hashBlock.
// A vector is used for future extensibility, as we may want to support // A vector is used for future extensibility, as we may want to support
// interrupting after partial writes from multiple independent reorgs. // interrupting after partial writes from multiple independent reorgs.
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
batch.Erase(DB_BEST_BLOCK); batch.Erase(DB_BEST_BLOCK);
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip)); batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip));
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
for (auto it{cursor.Begin()}; it != cursor.End();) { for (auto it{cursor.Begin()}; it != cursor.End();) {
if (it->second.IsDirty()) { if (it->second.IsDirty()) {
CoinEntry entry(&it->first); CoinEntry entry(&it->first);
if (it->second.coin.IsSpent()) if (it->second.coin.IsSpent()) {
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
batch.Erase(entry); batch.Erase(entry);
else assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
} else {
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
batch.Write(entry, it->second.coin); batch.Write(entry, it->second.coin);
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
}
changed++; changed++;
} }
count++; count++;
it = cursor.NextAndMaybeErase(*it); it = cursor.NextAndMaybeErase(*it);
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
if (batch.SizeEstimate() > m_options.batch_write_bytes) { if (batch.SizeEstimate() > m_options.batch_write_bytes) {
LogDebug(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0)); LogDebug(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
m_db->WriteBatch(batch); m_db->WriteBatch(batch);
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
batch.Clear(); batch.Clear();
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
if (m_options.simulate_crash_ratio) { if (m_options.simulate_crash_ratio) {
static FastRandomContext rng; static FastRandomContext rng;
if (rng.randrange(m_options.simulate_crash_ratio) == 0) { if (rng.randrange(m_options.simulate_crash_ratio) == 0) {
@ -142,11 +157,15 @@ bool CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashB
} }
// In the last batch, mark the database as consistent with hashBlock again. // In the last batch, mark the database as consistent with hashBlock again.
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
batch.Erase(DB_HEAD_BLOCKS); batch.Erase(DB_HEAD_BLOCKS);
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
batch.Write(DB_BEST_BLOCK, hashBlock); batch.Write(DB_BEST_BLOCK, hashBlock);
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0)); LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
bool ret = m_db->WriteBatch(batch); bool ret = m_db->WriteBatch(batch);
assert(batch.ApproximateSize() == batch.SizeEstimate()); // TODO remove
LogDebug(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count); LogDebug(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
return ret; return ret;
} }