-
Notifications
You must be signed in to change notification settings - Fork 670
perf: add fast MVCC snapshots for MemDb in FlatDb tests #10792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
92c341e
feat: add MVCC snapshot support to MemDb for fast test snapshots
asdacap fe42d69
chore: migrate FlatDb tests to use SnapshotableMemColumnsDb
asdacap a3565f0
refactor: use primary constructors and optimize write batches
asdacap 4327138
perf: use ArrayPoolList in MemDbWriteBatch to reduce GC pressure
asdacap 5eeaeb2
refactor: Change SnapshottedMemDb from Dictionary<byte[], Version> to…
asdacap 0899bb1
fix: Add neverPrune option to SnapshotableMemDb to avoid pruning bug …
asdacap b7441ca
fix: Initialize neverPrune before creating column databases
asdacap d625346
fix: Critical thread-safety bugs in SnapshotableMemDb
asdacap dc76c44
fix: Remove Reverse() allocations and fix PruneVersionsOlderThan bug
asdacap 5a5322f
perf: Switch SnapshotableMemDb from SortedDictionary to SortedSet wit…
asdacap 1576d1e
perf: Simplify FindFirst/LastKeyAtVersion using GetValueAtVersion
asdacap 6f9e925
fix: Address PR review comments on SnapshotableMemDb
asdacap 7af1c37
fix: Address PR review - use Lock type and Dictionary instead of IDic…
asdacap File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
218 changes: 218 additions & 0 deletions
218
src/Nethermind/Nethermind.Db.Test/SnapshotableMemColumnsDbTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,218 @@ | ||
| // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
|
|
||
| using FluentAssertions; | ||
| using Nethermind.Core; | ||
| using Nethermind.Core.Test.Builders; | ||
| using NUnit.Framework; | ||
|
|
||
| namespace Nethermind.Db.Test | ||
| { | ||
| [TestFixture] | ||
| [Parallelizable(ParallelScope.All)] | ||
| public class SnapshotableMemColumnsDbTests | ||
| { | ||
| private enum TestColumns | ||
| { | ||
| Column1, | ||
| Column2, | ||
| Column3 | ||
| } | ||
|
|
||
| private readonly byte[] _sampleValue = { 1, 2, 3 }; | ||
| private readonly byte[] _sampleValue2 = { 4, 5, 6 }; | ||
|
|
||
| [Test] | ||
| public void Can_create_and_get_columns() | ||
| { | ||
| SnapshotableMemColumnsDb<TestColumns> columnsDb = new(); | ||
|
|
||
| IDb column1 = columnsDb.GetColumnDb(TestColumns.Column1); | ||
| IDb column2 = columnsDb.GetColumnDb(TestColumns.Column2); | ||
|
|
||
| column1.Should().NotBeNull(); | ||
| column2.Should().NotBeNull(); | ||
| column1.Should().NotBeSameAs(column2); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Can_write_and_read_from_columns() | ||
| { | ||
| SnapshotableMemColumnsDb<TestColumns> columnsDb = new(); | ||
|
|
||
| IDb column1 = columnsDb.GetColumnDb(TestColumns.Column1); | ||
| IDb column2 = columnsDb.GetColumnDb(TestColumns.Column2); | ||
|
|
||
| column1.Set(TestItem.KeccakA, _sampleValue); | ||
| column2.Set(TestItem.KeccakA, _sampleValue2); | ||
|
|
||
| column1.Get(TestItem.KeccakA).Should().BeEquivalentTo(_sampleValue); | ||
| column2.Get(TestItem.KeccakA).Should().BeEquivalentTo(_sampleValue2); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Can_create_snapshot() | ||
| { | ||
| SnapshotableMemColumnsDb<TestColumns> columnsDb = new(); | ||
|
|
||
| IDb column1 = columnsDb.GetColumnDb(TestColumns.Column1); | ||
| column1.Set(TestItem.KeccakA, _sampleValue); | ||
|
|
||
| IColumnDbSnapshot<TestColumns> snapshot = columnsDb.CreateSnapshot(); | ||
| snapshot.Should().NotBeNull(); | ||
|
|
||
| IReadOnlyKeyValueStore snapshotColumn = snapshot.GetColumn(TestColumns.Column1); | ||
| snapshotColumn.Get(TestItem.KeccakA).Should().BeEquivalentTo(_sampleValue); | ||
|
|
||
| snapshot.Dispose(); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Snapshot_is_isolated_from_subsequent_writes() | ||
| { | ||
| SnapshotableMemColumnsDb<TestColumns> columnsDb = new(); | ||
|
|
||
| IDb column1 = columnsDb.GetColumnDb(TestColumns.Column1); | ||
| column1.Set(TestItem.KeccakA, _sampleValue); | ||
|
|
||
| IColumnDbSnapshot<TestColumns> snapshot = columnsDb.CreateSnapshot(); | ||
|
|
||
| // Modify after snapshot | ||
| column1.Set(TestItem.KeccakA, _sampleValue2); | ||
| column1.Set(TestItem.KeccakB, _sampleValue2); | ||
|
|
||
| // Snapshot should see old values | ||
| IReadOnlyKeyValueStore snapshotColumn = snapshot.GetColumn(TestColumns.Column1); | ||
| snapshotColumn.Get(TestItem.KeccakA).Should().BeEquivalentTo(_sampleValue); | ||
| snapshotColumn.Get(TestItem.KeccakB).Should().BeNull(); | ||
|
|
||
| // Main db should see new values | ||
| column1.Get(TestItem.KeccakA).Should().BeEquivalentTo(_sampleValue2); | ||
| column1.Get(TestItem.KeccakB).Should().BeEquivalentTo(_sampleValue2); | ||
|
|
||
| snapshot.Dispose(); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Snapshot_captures_all_columns_atomically() | ||
| { | ||
| SnapshotableMemColumnsDb<TestColumns> columnsDb = new(); | ||
|
|
||
| IDb column1 = columnsDb.GetColumnDb(TestColumns.Column1); | ||
| IDb column2 = columnsDb.GetColumnDb(TestColumns.Column2); | ||
|
|
||
| column1.Set(TestItem.KeccakA, new byte[] { 1 }); | ||
| column2.Set(TestItem.KeccakA, new byte[] { 2 }); | ||
|
|
||
| IColumnDbSnapshot<TestColumns> snapshot = columnsDb.CreateSnapshot(); | ||
|
|
||
| // Modify both columns | ||
| column1.Set(TestItem.KeccakA, new byte[] { 10 }); | ||
| column2.Set(TestItem.KeccakA, new byte[] { 20 }); | ||
|
|
||
| // Snapshot should see old values in both columns | ||
| IReadOnlyKeyValueStore snapshotColumn1 = snapshot.GetColumn(TestColumns.Column1); | ||
| IReadOnlyKeyValueStore snapshotColumn2 = snapshot.GetColumn(TestColumns.Column2); | ||
|
|
||
| snapshotColumn1.Get(TestItem.KeccakA).Should().BeEquivalentTo(new byte[] { 1 }); | ||
| snapshotColumn2.Get(TestItem.KeccakA).Should().BeEquivalentTo(new byte[] { 2 }); | ||
|
|
||
| snapshot.Dispose(); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Multiple_snapshots_are_independent() | ||
| { | ||
| SnapshotableMemColumnsDb<TestColumns> columnsDb = new(); | ||
|
|
||
| IDb column1 = columnsDb.GetColumnDb(TestColumns.Column1); | ||
|
|
||
| column1.Set(TestItem.KeccakA, new byte[] { 1 }); | ||
| IColumnDbSnapshot<TestColumns> snapshot1 = columnsDb.CreateSnapshot(); | ||
|
|
||
| column1.Set(TestItem.KeccakA, new byte[] { 2 }); | ||
| IColumnDbSnapshot<TestColumns> snapshot2 = columnsDb.CreateSnapshot(); | ||
|
|
||
| column1.Set(TestItem.KeccakA, new byte[] { 3 }); | ||
|
|
||
| // Each snapshot sees its version | ||
| snapshot1.GetColumn(TestColumns.Column1).Get(TestItem.KeccakA).Should().BeEquivalentTo(new byte[] { 1 }); | ||
| snapshot2.GetColumn(TestColumns.Column1).Get(TestItem.KeccakA).Should().BeEquivalentTo(new byte[] { 2 }); | ||
|
|
||
| snapshot1.Dispose(); | ||
| snapshot2.Dispose(); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Can_use_write_batch() | ||
| { | ||
| SnapshotableMemColumnsDb<TestColumns> columnsDb = new(); | ||
|
|
||
| using (IColumnsWriteBatch<TestColumns> batch = columnsDb.StartWriteBatch()) | ||
| { | ||
| batch.GetColumnBatch(TestColumns.Column1).Set(TestItem.KeccakA, _sampleValue); | ||
| } | ||
|
|
||
| IDb column1 = columnsDb.GetColumnDb(TestColumns.Column1); | ||
| column1.Get(TestItem.KeccakA).Should().BeEquivalentTo(_sampleValue); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Flush_does_not_cause_trouble() | ||
| { | ||
| SnapshotableMemColumnsDb<TestColumns> columnsDb = new(); | ||
| columnsDb.Flush(); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Dispose_does_not_cause_trouble() | ||
| { | ||
| SnapshotableMemColumnsDb<TestColumns> columnsDb = new(); | ||
| columnsDb.Dispose(); | ||
| } | ||
|
|
||
| [Test] | ||
| public void ColumnKeys_returns_all_columns() | ||
| { | ||
| SnapshotableMemColumnsDb<TestColumns> columnsDb = new(); | ||
|
|
||
| columnsDb.GetColumnDb(TestColumns.Column1); | ||
| columnsDb.GetColumnDb(TestColumns.Column2); | ||
|
|
||
| columnsDb.ColumnKeys.Should().Contain(TestColumns.Column1); | ||
| columnsDb.ColumnKeys.Should().Contain(TestColumns.Column2); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Snapshot_column_supports_ISortedKeyValueStore() | ||
| { | ||
| SnapshotableMemColumnsDb<TestColumns> columnsDb = new(); | ||
| byte[] keyA = new byte[] { 0x01 }; | ||
| byte[] keyB = new byte[] { 0x02 }; | ||
| byte[] keyC = new byte[] { 0x03 }; | ||
|
|
||
| IDb column1 = columnsDb.GetColumnDb(TestColumns.Column1); | ||
| column1.Set(keyA, new byte[] { 1 }); | ||
| column1.Set(keyB, new byte[] { 2 }); | ||
| column1.Set(keyC, new byte[] { 3 }); | ||
|
|
||
| IColumnDbSnapshot<TestColumns> snapshot = columnsDb.CreateSnapshot(); | ||
| IReadOnlyKeyValueStore snapshotColumn = snapshot.GetColumn(TestColumns.Column1); | ||
|
|
||
| // Check if snapshot column is ISortedKeyValueStore | ||
| snapshotColumn.Should().BeAssignableTo<ISortedKeyValueStore>(); | ||
|
|
||
| ISortedKeyValueStore sortedColumn = (ISortedKeyValueStore)snapshotColumn; | ||
| byte[]? firstKey = sortedColumn.FirstKey; | ||
| byte[]? lastKey = sortedColumn.LastKey; | ||
|
|
||
| firstKey.Should().NotBeNull(); | ||
| lastKey.Should().NotBeNull(); | ||
|
|
||
| firstKey.Should().BeEquivalentTo(keyA); // 0x01 | ||
| lastKey.Should().BeEquivalentTo(keyC); // 0x03 | ||
|
|
||
| snapshot.Dispose(); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Registering SnapshotableMemColumnsDb with
neverPrune: truedisables version GC entirely. Given FlatDb creates many snapshots, this can cause unbounded memory growth during longer test runs. Once pruning is fixed, prefer enabling pruning (or at least adding a bounded GC strategy) so tests don’t risk OOMs.