Description
While a full pruning run is in progress, PruningTriggerPruningStrategy forces a dirty-cache snapshot on every single block. Its threshold is measured from the chain head rather than from the pruning boundary, and the persistence machinery guarantees that this distance is always at least the pruning boundary, so the condition is a tautology for the whole full-pruning window.
The visible effect is a memory-prune cycle per block instead of roughly one per pruning boundary, each one persisting a single block's state delta, followed by a "pruning cache is too low" warning because retention stays above the efficiency threshold.
Steps to Reproduce
- Run a HalfPath node with full pruning enabled (any node whose state DB is an
IFullPruningDb).
- Trigger or wait for a full pruning run.
- Watch the logs for the whole pruning window.
Actual behavior
Executed memory prune and Pruning cache is too low appear at roughly block cadence — on a mainnet node during a full-prune window that is on the order of 300 of each per hour. Dirty cache stays pinned and each forced cycle reclaims only one block's delta.
Expected behavior
Snapshots at the configured pruning cadence, roughly once per pruning boundary, as the non-full-pruning strategies already do. The 32-block threshold should mean 32 blocks of accumulation, and it should carry hysteresis.
Additional context
On master (b644740ec8); git log e6d3d2f5ab..b644740ec8 over the relevant files is empty, so 2.0.0-rc is identical and master does not fix it.
- Every block commit ends in
TrieStore.FinishBlockCommit -> Prune() (Nethermind.Trie/Pruning/TrieStore.cs:466-486), which consults ShouldPruneDirtyNode(state) (:554-561) and, when true, runs PersistAndPruneDirtyCache -> SaveSnapshot + PruneCache (:655-694).
- A snapshot can only persist commit sets at or below
min(LatestCommitted - PruningBoundary, finalized) (TrieStore.cs:783-784, _maxDepth = pruningConfig.PruningBoundary at :99), and afterwards LastPersistedBlockNumber = commitSet.BlockNumber (:1168). So immediately after any snapshot at head, LatestCommittedBlock - LastPersistedBlock == PruningBoundary. The factory forces that boundary to at least 64 (Nethermind.Init/PruningTrieStateFactory.cs:124-128), and to 128 when snap serving is on (:116-122; WorldStateDbDeciderModule.cs:32 defaults SnapServingEnabled ??= true).
PruningTriggerPruningStrategy.ShouldPruneDirtyNode (Nethermind.Blockchain/FullPruning/PruningTriggerPruningStrategy.cs:46) returns true when state.LatestCommittedBlock - state.LastPersistedBlock > 32. Given (2) that difference is always at least 64, so the expression is always true for the entire full-pruning window — a forced snapshot per block, with zero hysteresis by construction.
- It also bypasses the base chain's
MinBlockInCachePruneStrategy 8-block guard (Nethermind.Trie/Pruning/MinBlockInCachePruneStrategy.cs:14-16) which — like MaxBlockInCachePruneStrategy.cs:14-16 — correctly measures from the reorg boundary, LatestCommittedBlock.SaturatingSub(pruneBoundary), rather than from the head.
- Each forced cycle persists exactly the one commit set that crossed the boundary since the previous block, so dirty memory drops by a single block's delta and the warning at
TrieStore.cs:673-682 fires because retention exceeds the 0.9 PruningEfficiencyWarningThreshold (:31).
The wrapper is installed whenever the state DB is an IFullPruningDb (PruningTrieStateFactory.cs:153-156) and only changes behaviour between PruningStarted and PruningFinished (:27-29), which is why this is scoped to full-pruning windows. FlatDb nodes show zero such cycles.
The fix is to measure the same distance from the pruning boundary rather than from the head, matching the sibling strategies.
Desktop
- Operating System: Linux x64
- Version: 2.0.0-rc (
e6d3d2f5ab); also present on master (b644740ec8)
- Installation Method: Docker
Description
While a full pruning run is in progress,
PruningTriggerPruningStrategyforces a dirty-cache snapshot on every single block. Its threshold is measured from the chain head rather than from the pruning boundary, and the persistence machinery guarantees that this distance is always at least the pruning boundary, so the condition is a tautology for the whole full-pruning window.The visible effect is a memory-prune cycle per block instead of roughly one per pruning boundary, each one persisting a single block's state delta, followed by a "pruning cache is too low" warning because retention stays above the efficiency threshold.
Steps to Reproduce
IFullPruningDb).Actual behavior
Executed memory pruneandPruning cache is too lowappear at roughly block cadence — on a mainnet node during a full-prune window that is on the order of 300 of each per hour. Dirty cache stays pinned and each forced cycle reclaims only one block's delta.Expected behavior
Snapshots at the configured pruning cadence, roughly once per pruning boundary, as the non-full-pruning strategies already do. The 32-block threshold should mean 32 blocks of accumulation, and it should carry hysteresis.
Additional context
On
master(b644740ec8);git log e6d3d2f5ab..b644740ec8over the relevant files is empty, so2.0.0-rcis identical and master does not fix it.TrieStore.FinishBlockCommit->Prune()(Nethermind.Trie/Pruning/TrieStore.cs:466-486), which consultsShouldPruneDirtyNode(state)(:554-561) and, when true, runsPersistAndPruneDirtyCache->SaveSnapshot+PruneCache(:655-694).min(LatestCommitted - PruningBoundary, finalized)(TrieStore.cs:783-784,_maxDepth = pruningConfig.PruningBoundaryat:99), and afterwardsLastPersistedBlockNumber = commitSet.BlockNumber(:1168). So immediately after any snapshot at head,LatestCommittedBlock - LastPersistedBlock == PruningBoundary. The factory forces that boundary to at least 64 (Nethermind.Init/PruningTrieStateFactory.cs:124-128), and to 128 when snap serving is on (:116-122;WorldStateDbDeciderModule.cs:32defaultsSnapServingEnabled ??= true).PruningTriggerPruningStrategy.ShouldPruneDirtyNode(Nethermind.Blockchain/FullPruning/PruningTriggerPruningStrategy.cs:46) returns true whenstate.LatestCommittedBlock - state.LastPersistedBlock > 32. Given (2) that difference is always at least 64, so the expression is always true for the entire full-pruning window — a forced snapshot per block, with zero hysteresis by construction.MinBlockInCachePruneStrategy8-block guard (Nethermind.Trie/Pruning/MinBlockInCachePruneStrategy.cs:14-16) which — likeMaxBlockInCachePruneStrategy.cs:14-16— correctly measures from the reorg boundary,LatestCommittedBlock.SaturatingSub(pruneBoundary), rather than from the head.TrieStore.cs:673-682fires because retention exceeds the 0.9PruningEfficiencyWarningThreshold(:31).The wrapper is installed whenever the state DB is an
IFullPruningDb(PruningTrieStateFactory.cs:153-156) and only changes behaviour betweenPruningStartedandPruningFinished(:27-29), which is why this is scoped to full-pruning windows. FlatDb nodes show zero such cycles.The fix is to measure the same distance from the pruning boundary rather than from the head, matching the sibling strategies.
Desktop
e6d3d2f5ab); also present onmaster(b644740ec8)