Merge bitcoin/bitcoin#30611: validation: write chainstate to disk every hour

e976bd3045 validation: add randomness to periodic write interval (Andrew Toth)
2e2f410681 refactor: replace m_last_write with m_next_write (Andrew Toth)
b557fa7a17 refactor: rename fDoFullFlush to should_write (Andrew Toth)
d73bd9fbe4 validation: write chainstate to disk every hour (Andrew Toth)
0ad7d7abdb test: chainstate write test for periodic chainstate flush (Andrew Toth)

Pull request description:

  Since #28233, periodically writing the chainstate to disk every 24 hours does not clear the dbcache. Since #28280, periodically writing the chainstate to disk is proportional only to the amount of dirty entries in the cache. Due to these changes, it is no longer beneficial to only write the chainstate to disk every 24 hours. The periodic flush interval was necessary because every write of the chainstate would clear the dbcache. Now, we can get rid of the periodic flush interval and simply write the chainstate along with blocks and block index at least every hour.

  Three benefits of doing this:
  1. For IBD or reindex-chainstate with a combination of large dbcache setting, slow CPU, slow internet speed/unreliable peers, it could be up to 24 hours until the chainstate is persisted to disk. A power outage or crash could potentially lose up to 24 hours of progress. If there is a very large amount of dirty cache entries, writing to disk when a flush finally does occur will take a very long time. Crashing during this window of writing can cause https://github.com/bitcoin/bitcoin/issues/11600. By syncing every hour in unison with the block index we avoid this problem. Only a maximum of one hour of progress can be lost, and the window for crashing during writing is much smaller. For IBD with lower dbcache settings, faster CPU, or better internet speed/reliable peers, chainstate writes are already triggered more often than every hour so this change will have no effect on IBD.
  2. Based on discussion in #28280, writing only once every 24 hours during long running operation of a node causes IO spikes. Writing smaller chainstate changes every hour like we do with blocks and block index will reduce IO spikes.
  3. Faster shutdown speeds. All dirty chainstate entries must be persisted to disk on shutdown. If we have a lot of dirty entries, such as when close to 24 hours or if we sync with a large dbcache, it can take a long time to shutdown. By keeping the chainstate clean we avoid this problem.

  Inspired by [this comment](https://github.com/bitcoin/bitcoin/pull/28280#issuecomment-2121088705).

  Resolves https://github.com/bitcoin/bitcoin/issues/11600

ACKs for top commit:
  achow101:
    ACK e976bd3045
  davidgumberg:
    utACK e976bd3045
  sipa:
    utACK e976bd3045
  l0rinc:
    ACK  e976bd3045

Tree-SHA512: 5bccd8f1dea47f9820a3fd32fe3bb6841c0167b3d6870cc8f3f7e2368f124af1a914bca6acb06889cd7183638a8dbdbace54d3237c3683f2b567eb7355e015ee
This commit is contained in:
Ava Chow
2025-05-01 12:11:55 -07:00
4 changed files with 91 additions and 50 deletions

View File

@@ -25,6 +25,7 @@ add_executable(test_bitcoin
blockmanager_tests.cpp
bloom_tests.cpp
bswap_tests.cpp
chainstate_write_tests.cpp
checkqueue_tests.cpp
cluster_linearize_tests.cpp
coins_tests.cpp

View File

@@ -0,0 +1,45 @@
// Copyright (c) The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <test/util/setup_common.h>
#include <validation.h>
#include <validationinterface.h>
#include <boost/test/unit_test.hpp>
BOOST_AUTO_TEST_SUITE(chainstate_write_tests)
BOOST_FIXTURE_TEST_CASE(chainstate_write_interval, TestingSetup)
{
struct TestSubscriber final : CValidationInterface {
bool m_did_flush{false};
void ChainStateFlushed(ChainstateRole, const CBlockLocator&) override
{
m_did_flush = true;
}
};
const auto sub{std::make_shared<TestSubscriber>()};
m_node.validation_signals->RegisterSharedValidationInterface(sub);
auto& chainstate{Assert(m_node.chainman)->ActiveChainstate()};
BlockValidationState state_dummy{};
// The first periodic flush sets m_next_write and does not flush
chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
m_node.validation_signals->SyncWithValidationInterfaceQueue();
BOOST_CHECK(!sub->m_did_flush);
// The periodic flush interval is between 50 and 70 minutes (inclusive)
SetMockTime(GetTime<std::chrono::minutes>() + 49min);
chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
m_node.validation_signals->SyncWithValidationInterfaceQueue();
BOOST_CHECK(!sub->m_did_flush);
SetMockTime(GetTime<std::chrono::minutes>() + 70min);
chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
m_node.validation_signals->SyncWithValidationInterfaceQueue();
BOOST_CHECK(sub->m_did_flush);
}
BOOST_AUTO_TEST_SUITE_END()