Skip to content
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

Serialized storage cache #3669

Open
wants to merge 55 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
37217b6
Storage cache
shargon Jan 13, 2025
f0bfc3b
reduce
shargon Jan 13, 2025
038a7c4
clean
shargon Jan 13, 2025
ccce7a4
Fix discard
shargon Jan 13, 2025
4943659
Merge branch 'master' into storage-cache
shargon Jan 20, 2025
cca66ba
Clean
shargon Jan 20, 2025
54901ed
clean
shargon Jan 20, 2025
75f5a06
Merge branch 'master' into storage-cache
shargon Jan 21, 2025
ecdfe09
Merge branch 'master' into storage-cache
shargon Jan 21, 2025
83d13cc
Clean code
shargon Jan 21, 2025
5230d9c
fix cache
shargon Jan 21, 2025
df7ad8e
remove lock
shargon Jan 21, 2025
be5db33
Merge branch 'master' into storage-cache
shargon Jan 22, 2025
c12767a
Clean and Fix UT
shargon Jan 22, 2025
601c366
Clean
shargon Jan 22, 2025
6bdcc21
Anna's feedback
shargon Jan 23, 2025
9c45d94
Merge branch 'master' into storage-cache
shargon Jan 23, 2025
5fb4b62
Add fee values
shargon Jan 23, 2025
3d25c9f
Merge branch 'storage-cache' of https://github.com/neo-project/neo in…
shargon Jan 23, 2025
413d605
Merge branch 'master' into storage-cache
cschuchardt88 Jan 23, 2025
2dd2301
Merge branch 'master' into storage-cache
shargon Jan 26, 2025
336cbac
Merge branch 'master' into storage-cache
Jim8y Jan 28, 2025
9decaf0
```
shargon Feb 6, 2025
bb6ca9c
Merge branch 'master' into storage-cache
shargon Feb 6, 2025
a84c163
Fix conflicts
shargon Feb 6, 2025
3c6d292
Optimize
shargon Feb 6, 2025
bf97fab
Fix
shargon Feb 6, 2025
62ae410
Clean changes
shargon Feb 6, 2025
ac22a4d
Merge branch 'master' into storage-cache
shargon Feb 6, 2025
a21526f
add UT
Jim8y Feb 6, 2025
f0a9add
Merge branch 'storage-cache' of github.com:neo-project/neo into stora…
Jim8y Feb 6, 2025
a917235
Merge branch 'master' into storage-cache
shargon Feb 7, 2025
f4b5bcd
Merge branch 'master' into storage-cache
shargon Feb 7, 2025
6d1d8a0
Clean changes
shargon Feb 7, 2025
6bd4f42
Update IReadOnlyStore.cs
shargon Feb 7, 2025
aa3b705
Merge branch 'master' into storage-cache
shargon Feb 9, 2025
e4e8130
Update src/Neo/SmartContract/Native/NeoToken.cs
shargon Feb 9, 2025
b5f03e2
Merge branch 'master' into storage-cache
Jim8y Feb 11, 2025
d31d182
Merge branch 'master' into storage-cache
shargon Feb 11, 2025
c6a7c00
Clean Store and Snapshot
shargon Feb 11, 2025
e503094
Clean code
shargon Feb 11, 2025
13b3e50
Add ut
shargon Feb 11, 2025
a96bdbc
Merge branch 'master' into storage-cache
shargon Feb 11, 2025
3bf2240
Update src/Neo/Persistence/SerializedCache.cs
shargon Feb 11, 2025
53215b7
Update src/Neo/Persistence/SerializedCache.cs
shargon Feb 11, 2025
64b2796
Update benchmarks/Neo.Benchmarks/Benchmarks.Cache.cs
shargon Feb 11, 2025
898d3e6
Merge remote-tracking branch 'origin/master' into storage-cache
shargon Feb 13, 2025
1d971bf
Rename to Upsert
shargon Feb 13, 2025
894f979
Fix clone
shargon Feb 13, 2025
3a7bf7a
Fix
shargon Feb 13, 2025
1f5af5c
Merge branch 'master' into storage-cache
shargon Feb 13, 2025
5f80417
Merge branch 'master' into storage-cache
shargon Feb 15, 2025
fa7ec14
Update Program.cs
shargon Feb 15, 2025
ce5ec54
Merge branch 'master' into storage-cache
shargon Feb 18, 2025
f7ae9de
Merge branch 'master' into storage-cache
shargon Mar 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions benchmarks/Neo.Benchmarks/Benchmarks.Cache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// Benchmarks.Cache.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using BenchmarkDotNet.Attributes;
using Neo.Persistence;
using Neo.Persistence.Providers;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using System.Numerics;

namespace Neo.Benchmark
{
public class Benchmarks_Cache
{
readonly MemoryStore _store;
readonly StoreCache _snapshot;

public Benchmarks_Cache()
{
_store = new MemoryStore();
_snapshot = new(_store.GetSnapshot());

// Ledger.CurrentIndex

_snapshot.GetAndChange(new KeyBuilder(NativeContract.Ledger.Id, 12), () => new StorageItem(new HashIndexState() { Hash = UInt256.Zero, Index = 2 }));

// Gas Per block

_snapshot.GetAndChange(new KeyBuilder(NativeContract.NEO.Id, 29).AddBigEndian(0), () => new StorageItem(0));
_snapshot.GetAndChange(new KeyBuilder(NativeContract.NEO.Id, 29).AddBigEndian(1), () => new StorageItem(1));
_snapshot.GetAndChange(new KeyBuilder(NativeContract.NEO.Id, 29).AddBigEndian(2), () => new StorageItem(2));
}

[Benchmark]
public void WithCache()
{
for (var x = 0; x < 1_000; x++)
{
var ret = NativeContract.NEO.GetGasPerBlock(_snapshot);
if (ret != 2) throw new Exception("Test error");
}
}

[Benchmark]
public void WithoutCache()
{
for (var x = 0; x < 1_000; x++)
{
var ret = OldCode();
if (ret != 2) throw new Exception("Test error");
}
}

private BigInteger OldCode()
{
var end = NativeContract.Ledger.CurrentIndex(_snapshot) + 1;
var last = NativeContract.NEO.GetSortedGasRecords(_snapshot, end).First();
return last.GasPerBlock;
}
}
}
1 change: 1 addition & 0 deletions src/Neo/Neo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Neo.Benchmarks" />
<InternalsVisibleTo Include="Neo.SmartContract.Testing" />
<InternalsVisibleTo Include="Neo.SmartContract.TestEngine" />
<InternalsVisibleTo Include="Neo.Plugins.RpcServer.Tests" />
Expand Down
2 changes: 1 addition & 1 deletion src/Neo/Persistence/ClonedCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class ClonedCache : DataCache
{
private readonly DataCache _innerCache;

public ClonedCache(DataCache innerCache)
public ClonedCache(DataCache innerCache) : base(innerCache.SerializedCacheChanges)
{
_innerCache = innerCache;
}
Expand Down
81 changes: 80 additions & 1 deletion src/Neo/Persistence/DataCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace Neo.Persistence
/// <summary>
/// Represents a cache for the underlying storage of the NEO blockchain.
/// </summary>
public abstract class DataCache : IReadOnlyStore
public abstract class DataCache : ICacheableReadOnlyStore
{
/// <summary>
/// Represents an entry in the cache.
Expand All @@ -45,6 +45,25 @@ public class Trackable(StorageItem item, TrackState state)
private readonly Dictionary<StorageKey, Trackable> _dictionary = [];
private readonly HashSet<StorageKey> _changeSet = [];

/// <summary>
/// Serialized cache
/// </summary>
public SerializedCache SerializedCache { get; }

/// <summary>
/// This is where the cache changes are stored
/// </summary>
internal SerializedCache SerializedCacheChanges { get; } = new();

/// <summary>
/// Constructor
/// </summary>
/// <param name="serializedCache">Serialized cache</param>
protected DataCache(SerializedCache serializedCache)
{
SerializedCache = serializedCache;
}

/// <summary>
/// Reads a specified entry from the cache. If the entry is not in the cache, it will be automatically loaded from the underlying storage.
/// </summary>
Expand Down Expand Up @@ -101,6 +120,51 @@ public void Add(StorageKey key, StorageItem value)
}
}

/// <summary>
/// Adds a new entry to the cache.
/// </summary>
/// <param name="key">The key of the entry.</param>
/// <param name="value">The data of the entry.</param>
/// <exception cref="ArgumentException">The entry has already been cached.</exception>
/// <remarks>Note: This method does not read the internal storage to check whether the record already exists.</remarks>
public void Add<T>(StorageKey key, T value) where T : IStorageCacheEntry
{
lock (_dictionary)
{
Add(key, value.GetStorageItem());
AddToCache(value);
}
}

/// <summary>
/// Adds a new entry to the cache.
/// </summary>
/// <param name="value">The data of the entry.</param>
/// <exception cref="ArgumentException">The entry has already been cached.</exception>
/// <remarks>Note: This method does not read the internal storage to check whether the record already exists.</remarks>
public void AddToCache<T>(T? value = default) where T : IStorageCacheEntry
{
var type = typeof(T);
SerializedCacheChanges.Set(type, value);
}

/// <summary>
/// Tries to get the entry from cache.
/// </summary>
/// <typeparam name="T">Cache type</typeparam>
/// <returns>The entry if found, null otherwise.</returns>
public T? GetFromCache<T>() where T : IStorageCacheEntry
{
var value = SerializedCacheChanges.Get<T>();

if (value != null)
{
return value;
}

return SerializedCache.Get<T>();
}

/// <summary>
/// Adds a new entry to the underlying storage.
/// </summary>
Expand Down Expand Up @@ -134,6 +198,8 @@ public virtual void Commit()
break;
}
}
SerializedCache.CopyFrom(SerializedCacheChanges);
SerializedCacheChanges.Clear();
_changeSet.Clear();
}
}
Expand Down Expand Up @@ -365,6 +431,19 @@ public bool Contains(StorageKey key)
}
}

/// <summary>
/// Reads a specified entry from the cache, and mark it as <see cref="TrackState.Changed"/>.
/// If the entry is not in the cache, it will be automatically loaded from the underlying storage.
/// </summary>
/// <param name="key">The key of the entry.</param>
/// <param name="serializedCache">Serialized cache</param>
public void Upsert<T>(StorageKey key, T serializedCache) where T : IStorageCacheEntry
{
var ret = GetAndChange(key, serializedCache.GetStorageItem);
ret!.FromReplica(serializedCache.GetStorageItem());
AddToCache(serializedCache);
}

/// <summary>
/// Reads a specified entry from the cache.
/// If the entry is not in the cache, it will be automatically loaded from the underlying storage.
Expand Down
44 changes: 44 additions & 0 deletions src/Neo/Persistence/ICacheableReadOnlyStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// ICacheableReadOnlyStore.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

#nullable enable

using Neo.SmartContract;
using System;

namespace Neo.Persistence
{
/// <summary>
/// This interface provides methods to read from the database.
/// </summary>
public interface ICacheableReadOnlyStore : ICacheableReadOnlyStore<StorageKey, StorageItem>, IReadOnlyStore { }

/// <summary>
/// This interface provides methods to read from the database.
/// </summary>
public interface ICacheableReadOnlyStore<TKey, TValue> : IReadOnlyStore<TKey, TValue>
{
/// <summary>
/// Tries to get the entry from cache.
/// </summary>
/// <typeparam name="T">Cache type</typeparam>
/// <returns>The entry if found, null otherwise.</returns>
public T? GetFromCache<T>() where T : IStorageCacheEntry;

/// <summary>
/// Adds a new entry to the cache.
/// </summary>
/// <param name="value">The data of the entry.</param>
/// <exception cref="ArgumentException">The entry has already been cached.</exception>
/// <remarks>Note: This method does not read the internal storage to check whether the record already exists.</remarks>
public void AddToCache<T>(T? value = default) where T : IStorageCacheEntry;
}
}
20 changes: 20 additions & 0 deletions src/Neo/Persistence/IStorageCacheEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// IStorageCacheEntry.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.SmartContract;

namespace Neo.Persistence
{
public interface IStorageCacheEntry
{
public StorageItem GetStorageItem();
}
}
5 changes: 5 additions & 0 deletions src/Neo/Persistence/IStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ public interface IStore :
IWriteStore<byte[], byte[]>,
IDisposable
{
/// <summary>
/// Serialized cache
/// </summary>
SerializedCache SerializedCache { get; }

/// <summary>
/// Creates a snapshot of the database.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/Neo/Persistence/Providers/MemoryStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace Neo.Persistence.Providers
public class MemoryStore : IStore
{
private readonly ConcurrentDictionary<byte[], byte[]> _innerData = new(ByteArrayEqualityComparer.Default);
public SerializedCache SerializedCache { get; } = new();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Delete(byte[] key)
Expand Down
108 changes: 108 additions & 0 deletions src/Neo/Persistence/SerializedCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// SerializedCache.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

#nullable enable

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace Neo.Persistence
{
public class SerializedCache
{
private readonly Dictionary<Type, IStorageCacheEntry> _cache = [];

/// <summary>
/// Get cached entry
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <returns>The cached entry. If not cached, the default value will be returned</returns>
public T? Get<T>()
{
if (_cache.TryGetValue(typeof(T), out var ret))
{
return (T)ret;
}

return default;
}

/// <summary>
/// Set entry
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <param name="value">Value</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Set<T>(T? value) where T : IStorageCacheEntry
{
Set(typeof(T), value);
}

/// <summary>
/// Set entry
/// </summary>
/// <param name="type">Type</param>
/// <param name="value">Value</param>
public void Set(Type type, IStorageCacheEntry? value)
{
if (value == null)
{
Remove(type);
}
else
{
lock (_cache)
{
_cache[type] = value;
}
}
}

/// <summary>
/// Remove entry
/// </summary>
/// <param name="type">Type</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Remove(Type type)
{
lock (_cache)
{
_cache.Remove(type, out _);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
lock (_cache)
{
_cache.Clear();
}
}

/// <summary>
/// Copy from
/// </summary>
/// <param name="value">Value</param>
public void CopyFrom(SerializedCache value)
{
if (ReferenceEquals(this, value)) return;
lock (_cache) lock (value._cache)
{
foreach (var serialized in value._cache)
{
_cache[serialized.Key] = serialized.Value;
}
}
}
}
}
Loading
Loading