diff --git a/src/coins.h b/src/coins.h index 9efd23c581d..8ff296059f8 100644 --- a/src/coins.h +++ b/src/coins.h @@ -293,6 +293,8 @@ struct CoinsViewCacheCursor } inline bool WillErase(CoinsCachePair& current) const noexcept { return m_will_erase || current.second.coin.IsSpent(); } + size_t GetDirtyCount() const noexcept { return m_dirty_count; } + size_t GetTotalCount() const noexcept { return m_map.size(); } private: size_t& m_dirty_count; CoinsCachePair& m_sentinel; diff --git a/src/txdb.cpp b/src/txdb.cpp index 21f6eb93867..2c39cf2767b 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,9 @@ static constexpr uint8_t DB_HEAD_BLOCKS{'H'}; // Keys used in previous version that might still be found in the DB: static constexpr uint8_t DB_COINS{'c'}; +// Threshold for warning when writing this many dirty cache entries to disk. +static constexpr size_t WARN_FLUSH_COINS_COUNT{10'000'000}; + bool CCoinsViewDB::NeedsUpgrade() { std::unique_ptr cursor{m_db->NewIterator()}; @@ -97,7 +101,7 @@ void CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashB { CDBBatch batch(*m_db); size_t count = 0; - size_t changed = 0; + const size_t dirty_count{cursor.GetDirtyCount()}; assert(!hashBlock.IsNull()); uint256 old_tip = GetBestBlock(); @@ -113,6 +117,10 @@ void CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashB } } + if (dirty_count > WARN_FLUSH_COINS_COUNT) LogWarning("Flushing large (%d entries) UTXO set to disk, it may take several minutes", dirty_count); + LOG_TIME_MILLIS_WITH_CATEGORY(strprintf("write coins cache to disk (%d out of %d cached coins)", + dirty_count, cursor.GetTotalCount()), BCLog::BENCH); + // In the first batch, mark the database as being in the middle of a // transition from old_tip to hashBlock. // A vector is used for future extensibility, as we may want to support @@ -128,8 +136,6 @@ void CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashB } else { batch.Write(entry, it->second.coin); } - - changed++; } count++; it = cursor.NextAndMaybeErase(*it); @@ -154,7 +160,7 @@ void CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashB LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.ApproximateSize() * (1.0 / 1048576.0)); m_db->WriteBatch(batch); - 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...", (unsigned int)dirty_count, (unsigned int)count); } size_t CCoinsViewDB::EstimateSize() const diff --git a/src/validation.cpp b/src/validation.cpp index c200c3d6cd8..0a7c57df25e 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -89,8 +89,6 @@ using node::CBlockIndexHeightOnlyComparator; using node::CBlockIndexWorkComparator; using node::SnapshotMetadata; -/** Size threshold for warning about slow UTXO set flush to disk. */ -static constexpr size_t WARN_FLUSH_COINS_SIZE = 1 << 30; // 1 GiB /** Time window to wait between writing blocks/block index and chainstate to disk. * Randomize writing time inside the window to prevent a situation where the * network over time settles into a few cohorts of synchronized writers. @@ -2708,8 +2706,8 @@ bool Chainstate::FlushStateToDisk( std::set setFilesToPrune; bool full_flush_completed = false; - const size_t coins_count = CoinsTip().GetCacheSize(); - const size_t coins_mem_usage = CoinsTip().DynamicMemoryUsage(); + [[maybe_unused]] const size_t coins_count{CoinsTip().GetCacheSize()}; + [[maybe_unused]] const size_t coins_mem_usage{CoinsTip().DynamicMemoryUsage()}; try { { @@ -2802,16 +2800,12 @@ bool Chainstate::FlushStateToDisk( } if (!CoinsTip().GetBestBlock().IsNull()) { - if (coins_mem_usage >= WARN_FLUSH_COINS_SIZE) LogWarning("Flushing large (%d GiB) UTXO set to disk, it may take several minutes", coins_mem_usage >> 30); - LOG_TIME_MILLIS_WITH_CATEGORY(strprintf("write coins cache to disk (%d coins, %.2fKiB)", - coins_count, coins_mem_usage >> 10), BCLog::BENCH); - // Typical Coin structures on disk are around 48 bytes in size. // Pushing a new one to the database can cause it to be written // twice (once in the log, and once in the tables). This is already // an overestimation, as most will delete an existing entry or // overwrite one. Still, use a conservative safety factor of 2. - if (!CheckDiskSpace(m_chainman.m_options.datadir, 48 * 2 * 2 * CoinsTip().GetCacheSize())) { + if (!CheckDiskSpace(m_chainman.m_options.datadir, 48 * 2 * 2 * CoinsTip().GetDirtyCount())) { return FatalError(m_chainman.GetNotifications(), state, _("Disk space is too low!")); } // Flush the chainstate (which may refer to block index entries).