Skip to content

Commit 0bd5630

Browse files
asdacapbenaadamskamilchodola
authored
Perf/Non blocking memory pruning (#9096)
* Move lastseen to triestore * Slight cleanup * Non blocking memory pruning * Fix test * Fix another test * Fix pruning test * LastSeen to LastCommit * Add extra test * allow disabling by setting to 0 * Debugging pruning.... * Stabilize test * Fix build * Remove console log * BBetter name * Whitespace * Fix test * Fix verify trie --------- Co-authored-by: Ben {chmark} Adams <thundercat@illyriad.co.uk> Co-authored-by: Kamil Chodoła <43241881+kamilchodola@users.noreply.github.com>
1 parent f9d425a commit 0bd5630

20 files changed

Lines changed: 717 additions & 429 deletions

File tree

src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,19 @@ protected override async Task<TestBlockchain> Build(Action<ContainerBuilder>? co
7575
return chain;
7676
}
7777

78-
protected override ContainerBuilder ConfigureContainer(ContainerBuilder builder, IConfigProvider configProvider) =>
78+
protected override ContainerBuilder
79+
ConfigureContainer(ContainerBuilder builder, IConfigProvider configProvider) =>
7980
// Reenable rocksdb
8081
base.ConfigureContainer(builder, configProvider)
8182
.AddSingleton<IDbFactory, RocksDbFactory>()
8283
.Intercept<IInitConfig>((initConfig) =>
8384
{
8485
initConfig.BaseDbPath = TempDirectory.Path;
86+
})
87+
.Intercept<IPruningConfig>((pruningConfig) =>
88+
{
89+
// Make test faster otherwise it may potentially buffer 128 block.
90+
pruningConfig.MaxBufferedCommitCount = 1;
8591
});
8692

8793
public override void Dispose()

src/Nethermind/Nethermind.Blockchain/FullPruning/FullPruner.cs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -118,16 +118,13 @@ protected virtual async Task RunFullPruning(CancellationToken cancellationToken)
118118
{
119119
IPruningContext? pruningContext = null;
120120

121-
// we don't want to start pruning in the middle of block processing, lets wait for new head.
122-
await WaitForMainChainChange((e) =>
121+
using (_trieStore.PrepareStableState(cancellationToken))
123122
{
124123
if (_fullPruningDb.TryStartPruning(_pruningConfig.Mode.IsMemory(), out IPruningContext fromDbPruningContext))
125124
{
126125
pruningContext = fromDbPruningContext;
127126
}
128-
129-
return true;
130-
}, cancellationToken);
127+
}
131128

132129
if (pruningContext is null) return;
133130

@@ -147,8 +144,6 @@ await WaitForMainChainChange((e) =>
147144

148145
private async Task RunFullPruning(IPruningContext pruningContext, CancellationToken cancellationToken)
149146
{
150-
_trieStore.PersistCache(cancellationToken);
151-
152147
long blockToWaitFor = 0;
153148
await WaitForMainChainChange((e) =>
154149
{
@@ -168,7 +163,7 @@ await WaitForMainChainChange((e) =>
168163
}, cancellationToken);
169164

170165
long stateToCopy = _blockTree.BestPersistedState.Value;
171-
long blockToPruneAfter = stateToCopy + Reorganization.MaxDepth;
166+
long blockToPruneAfter = stateToCopy + _pruningConfig.PruningBoundary;
172167

173168
await WaitForMainChainChange((e) =>
174169
{
@@ -225,7 +220,7 @@ private void HandlePruningFinished(object? sender, PruningEventArgs e)
225220
}
226221
}
227222

228-
private async Task CopyTrie(IPruningContext pruning, Hash256 stateRoot, CancellationToken cancellationToken)
223+
private Task CopyTrie(IPruningContext pruning, Hash256 stateRoot, CancellationToken cancellationToken)
229224
{
230225
INodeStorage.KeyScheme originalKeyScheme = _nodeStorage.Scheme;
231226
ICopyTreeVisitor visitor = null;
@@ -277,16 +272,12 @@ private async Task CopyTrie(IPruningContext pruning, Hash256 stateRoot, Cancella
277272
{
278273
visitor.Finish();
279274

280-
_nodeStorage.Scheme = targetNodeStorage.Scheme;
281-
// Note: This does means that during full pruning some of the key copied will be of old key scheme.
282-
await WaitForMainChainChange((e) =>
275+
using (_trieStore.PrepareStableState(cancellationToken))
283276
{
284-
// The db swap happens here. We do it within the event handler of main chain change to block
285-
// so that it does not happen during block processing.
286277
pruning.Commit();
287-
return true;
288-
}, cancellationToken);
278+
}
289279

280+
_nodeStorage.Scheme = targetNodeStorage.Scheme;
290281
_lastPruning = DateTime.UtcNow;
291282
}
292283
}
@@ -300,6 +291,8 @@ await WaitForMainChainChange((e) =>
300291
{
301292
visitor?.Dispose();
302293
}
294+
295+
return Task.CompletedTask;
303296
}
304297

305298
private ICopyTreeVisitor CopyTree<TContext>(

src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ protected virtual async Task<TestBlockchain> Build(Action<ContainerBuilder>? con
249249

250250
Block? genesis = GetGenesisBlock(WorldStateManager.GlobalWorldState);
251251
BlockTree.SuggestBlock(genesis);
252-
await waitGenesis;
252+
waitGenesis.Wait(_cts.Token);
253253
}
254254

255255
if (testConfiguration.AddBlockOnStart)

src/Nethermind/Nethermind.Core.Test/Modules/PseudoNethermindRunner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ private async Task PrepareGenesis(CancellationToken cancellation)
6363

6464
Block genesis = genesisLoader.Load();
6565
blockTree.SuggestBlock(genesis);
66-
await newHeadTask;
66+
newHeadTask.Wait();
6767
}
6868

6969
public async Task StartNetwork(CancellationToken cancellationToken)

src/Nethermind/Nethermind.Core.Test/TestRawTrieStore.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ public bool HasRoot(Hash256 stateRoot)
6262
return nodeStorage.KeyExists(null, TreePath.Empty, stateRoot);
6363
}
6464

65+
public IDisposable BeginScope(BlockHeader? baseBlock)
66+
{
67+
return new Reactive.AnonymousDisposable(() => { });
68+
}
69+
6570
public IScopedTrieStore GetTrieStore(Hash256? address)
6671
{
6772
return new RawScopedTrieStore(nodeStorage, address);
@@ -86,4 +91,18 @@ public event EventHandler<ReorgBoundaryReached>? ReorgBoundaryReached
8691
}
8792

8893
public IReadOnlyKeyValueStore TrieNodeRlpStore => throw new Exception("Unsupported operatioon");
94+
95+
private Lock _scopeLock = new Lock();
96+
private Lock _pruneLock = new Lock();
97+
public TrieStore.StableLockScope PrepareStableState(CancellationToken cancellationToken)
98+
{
99+
var scopeLockScope = _scopeLock.EnterScope();
100+
var pruneLockScope = _pruneLock.EnterScope();
101+
102+
return new TrieStore.StableLockScope
103+
{
104+
scopeLockScope = scopeLockScope,
105+
pruneLockScope = pruneLockScope,
106+
};
107+
}
89108
}

src/Nethermind/Nethermind.Db/IPruningConfig.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,7 @@ The max number of parallel tasks that can be used by full pruning.
9797

9898
[ConfigItem(Description = "Minimum number of block worth of unpersisted state in memory. Prevent memory pruning too often due to insufficient dirty cache memory.", DefaultValue = "8")]
9999
long MinUnpersistedBlockCount { get; set; }
100+
101+
[ConfigItem(Description = "Maximum number of block in commit buffer before blocking.", DefaultValue = "128", HiddenFromDocs = true)]
102+
int MaxBufferedCommitCount { get; set; }
100103
}

src/Nethermind/Nethermind.Db/PruningConfig.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,6 @@ public int DirtyNodeShardBit
6262
public long PrunePersistedNodeMinimumTarget { get; set; } = 50.MiB();
6363
public long MaxUnpersistedBlockCount { get; set; } = 300; // About 1 hour on mainnet
6464
public long MinUnpersistedBlockCount { get; set; } = 8; // About slightly more than 1 minute
65+
public int MaxBufferedCommitCount { get; set; } = 128;
6566
}
6667
}

src/Nethermind/Nethermind.State.Test/StateReaderTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class StateReaderTests
3232
private static readonly ILogManager Logger = LimboLogs.Instance;
3333

3434
[Test]
35-
public async Task Can_ask_about_balance_in_parallel()
35+
public void Can_ask_about_balance_in_parallel()
3636
{
3737
IReleaseSpec spec = MainnetSpecProvider.Instance.GetSpec((ForkActivation)MainnetSpecProvider.ConstantinopleFixBlockNumber);
3838
IDbProvider dbProvider = TestMemDbProvider.Init();
@@ -71,11 +71,11 @@ public async Task Can_ask_about_balance_in_parallel()
7171
Task c = StartTask(reader, baseBlock2, 3);
7272
Task d = StartTask(reader, baseBlock3, 4);
7373

74-
await Task.WhenAll(a, b, c, d);
74+
Task.WhenAll(a, b, c, d).Wait();
7575
}
7676

7777
[Test]
78-
public async Task Can_ask_about_storage_in_parallel()
78+
public void Can_ask_about_storage_in_parallel()
7979
{
8080
StorageCell storageCell = new(_address1, UInt256.One);
8181
IReleaseSpec spec = MuirGlacier.Instance;
@@ -130,7 +130,7 @@ void CommitEverything()
130130
Task c = StartStorageTask(reader, baseBlock2, storageCell, new byte[] { 3 });
131131
Task d = StartStorageTask(reader, baseBlock3, storageCell, new byte[] { 4 });
132132

133-
await Task.WhenAll(a, b, c, d);
133+
Task.WhenAll(a, b, c, d).Wait();
134134
}
135135

136136
[Test]

src/Nethermind/Nethermind.State/BlockingVerifyTrie.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public BlockingVerifyTrie(
4545
public bool VerifyTrie(BlockHeader stateAtBlock, CancellationToken cancellationToken)
4646
{
4747
// This is to block processing as with halfpath old nodes will be removed
48-
using IBlockCommitter? _ = _trieStore.BeginBlockCommit(stateAtBlock.Number + 1);
48+
using IDisposable _ = _trieStore.BeginScope(stateAtBlock);
4949

5050
Hash256 rootNode = stateAtBlock.StateRoot;
5151
TrieStats stats = _stateReader.CollectStats(rootNode, _codeDb, _logManager, cancellationToken);

src/Nethermind/Nethermind.State/WorldState.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,13 @@ public IDisposable BeginScope(BlockHeader? baseBlock)
246246
if (_logger.IsTrace) _logger.Trace($"Beginning WorldState scope with baseblock {baseBlock?.ToString(BlockHeader.Format.Short) ?? "null"} with stateroot {baseBlock?.StateRoot?.ToString() ?? "null"}.");
247247

248248
StateRoot = baseBlock?.StateRoot ?? Keccak.EmptyTreeHash;
249+
IDisposable trieStoreCloser = _trieStore.BeginScope(baseBlock);
249250

250251
return new Reactive.AnonymousDisposable(() =>
251252
{
252253
Reset();
253254
StateRoot = Keccak.EmptyTreeHash;
255+
trieStoreCloser.Dispose();
254256
_isInScope = false;
255257
if (_logger.IsTrace) _logger.Trace($"WorldState scope for baseblock {baseBlock?.ToString(BlockHeader.Format.Short) ?? "null"} closed");
256258
});

0 commit comments

Comments
 (0)