Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@
"preimage",
"preimages",
"preinstallation",
"prepopulate",
"prestate",
"prevop",
"prevrandao",
Expand Down
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ public void Compact()
public void Clear() { throw new NotSupportedException(); }

// Maybe it should be column specific metric?
public IDbMeta.DbMetric GatherMetric(bool includeSharedCache = false) => _mainDb.GatherMetric(includeSharedCache);
public IDbMeta.DbMetric GatherMetric() => _mainDb.GatherMetric();

public byte[]? FirstKey
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;

namespace Nethermind.Db.Rocks.Config;

public class AdjustedRocksdbConfig(
IRocksDbConfig baseConfig,
string additionalRocksDbOptions,
ulong writeBufferSize
ulong writeBufferSize,
IntPtr? blockCache = null
) : IRocksDbConfig
{
public ulong? WriteBufferSize => writeBufferSize;
Expand Down Expand Up @@ -37,4 +40,5 @@ ulong writeBufferSize
public double CompressibilityHint => baseConfig.CompressibilityHint;

public bool FlushOnExit => baseConfig.FlushOnExit;
public IntPtr? BlockCache => blockCache ?? baseConfig.BlockCache;
}
3 changes: 3 additions & 0 deletions src/Nethermind/Nethermind.Db.Rocks/Config/IRocksDbConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;

namespace Nethermind.Db.Rocks.Config;

public interface IRocksDbConfig
Expand All @@ -19,4 +21,5 @@ public interface IRocksDbConfig
bool EnableFileWarmer { get; }
double CompressibilityHint { get; }
bool FlushOnExit { get; }
IntPtr? BlockCache { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ private void EnsureConfigIsAvailable(string propertyName)
public bool EnableFileWarmer => ReadConfig<bool>(nameof(EnableFileWarmer));
public double CompressibilityHint => ReadConfig<double>(nameof(CompressibilityHint));
public bool FlushOnExit => ReadConfig<bool?>(nameof(FlushOnExit)) ?? true;
public IntPtr? BlockCache => null;

private T? ReadConfig<T>(string propertyName)
{
Expand Down
77 changes: 59 additions & 18 deletions src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public partial class DbOnTheRocks : IDb, ITunableDb, IReadOnlyNativeKeyValueStor
private ulong _writeBufferSize;
private int _maxWriteBufferNumber;
private readonly RocksDbReader _reader;
private bool _isUsingSharedBlockCache;

public DbOnTheRocks(
string basePath,
Expand Down Expand Up @@ -352,7 +353,7 @@ protected virtual long FetchTotalPropertyValue(string propertyName)
return value;
}

public IDbMeta.DbMetric GatherMetric(bool isUsingSharedCache = false)
public IDbMeta.DbMetric GatherMetric()
{
if (_isDisposed)
{
Expand All @@ -369,7 +370,7 @@ public IDbMeta.DbMetric GatherMetric(bool isUsingSharedCache = false)
return new IDbMeta.DbMetric()
{
Size = GetSize(),
CacheSize = GetCacheSize(isUsingSharedCache),
CacheSize = GetCacheSize(),
IndexSize = GetIndexSize(),
MemtableSize = GetMemtableSize(),
TotalReads = _totalReads,
Expand All @@ -394,11 +395,11 @@ private long GetSize()
return 0;
}

private long GetCacheSize(bool isUsingSharedCache = false)
private long GetCacheSize()
{
try
{
if (isUsingSharedCache)
if (_isUsingSharedBlockCache)
{
// returning 0 as we are using shared cache.
return 0;
Expand Down Expand Up @@ -460,6 +461,38 @@ public static IDictionary<string, string> ExtractOptions(string dbOptions)
return asDict;
}

private const string OptimizeFiltersForHitsOption = "optimize_filters_for_hits=";

/// <summary>
/// Normalizes a RocksDB options string by removing earlier occurrences of optimize_filters_for_hits,
/// keeping only the last one. This is needed because RocksDB does not allow overriding this option
/// when specified multiple times in the same options string.
/// </summary>
public static string NormalizeRocksDbOptions(string dbOptions)
{
if (string.IsNullOrEmpty(dbOptions)) return dbOptions ?? string.Empty;

int lastIndex = dbOptions.LastIndexOf(OptimizeFiltersForHitsOption, StringComparison.Ordinal);
if (lastIndex == -1) return dbOptions;

// Remove all earlier occurrences, keep only the last one
int searchStart = 0;
while (true)
{
int index = dbOptions.IndexOf(OptimizeFiltersForHitsOption, searchStart, StringComparison.Ordinal);
if (index == -1 || index == lastIndex) break;

// Find the end of this option (next semicolon)
int endIndex = dbOptions.IndexOf(';', index);
if (endIndex == -1) break;

dbOptions = dbOptions.Remove(index, endIndex - index + 1);
lastIndex = dbOptions.LastIndexOf(OptimizeFiltersForHitsOption, StringComparison.Ordinal);
}

return dbOptions;
}

protected virtual void BuildOptions<T>(IRocksDbConfig dbConfig, Options<T> options, IntPtr? sharedCache, IMergeOperator? mergeOperator) where T : Options<T>
{
// This section is about the table factory.. and block cache apparently.
Expand All @@ -475,27 +508,35 @@ protected virtual void BuildOptions<T>(IRocksDbConfig dbConfig, Options<T> optio
_writeBufferSize = ulong.Parse(optionsAsDict["write_buffer_size"]);
_maxWriteBufferNumber = int.Parse(optionsAsDict["max_write_buffer_number"]);

BlockBasedTableOptions tableOptions = new();
options.SetBlockBasedTableFactory(tableOptions);
IntPtr optsPtr = Marshal.StringToHGlobalAnsi(dbConfig.RocksDbOptions);
try
{
_rocksDbNative.rocksdb_get_options_from_string(options.Handle, optsPtr, options.Handle);
}
finally
{
Marshal.FreeHGlobal(optsPtr);
}

ulong blockCacheSize = 0;
if (optionsAsDict.TryGetValue("block_based_table_factory.block_cache", out string? blockCacheSizeStr))
{
blockCacheSize = ulong.Parse(blockCacheSizeStr);
}

if (sharedCache is not null && blockCacheSize == 0)
BlockBasedTableOptions? tableOptions = new();
if (dbConfig.BlockCache is not null)
{
tableOptions.SetBlockCache(dbConfig.BlockCache.Value);
}
else if (sharedCache is not null && blockCacheSize == 0)
{
tableOptions.SetBlockCache(sharedCache.Value);
_isUsingSharedBlockCache = true;
}

// Note: the ordering is important.
// changes to the table options must be applied before setting to set.
options.SetBlockBasedTableFactory(tableOptions);

IntPtr optsPtr = Marshal.StringToHGlobalAnsi(NormalizeRocksDbOptions(dbConfig.RocksDbOptions));
try
{
_rocksDbNative.rocksdb_get_options_from_string(options.Handle, optsPtr, options.Handle);
}
finally
{
Marshal.FreeHGlobal(optsPtr);
}

if (dbConfig.WriteBufferSize is not null)
Expand Down Expand Up @@ -579,7 +620,7 @@ protected virtual void BuildOptions<T>(IRocksDbConfig dbConfig, Options<T> optio

if (dbConfig.AdditionalRocksDbOptions is not null)
{
optsPtr = Marshal.StringToHGlobalAnsi(dbConfig.AdditionalRocksDbOptions);
optsPtr = Marshal.StringToHGlobalAnsi(NormalizeRocksDbOptions(dbConfig.AdditionalRocksDbOptions));
try
{
_rocksDbNative.rocksdb_get_options_from_string(options.Handle, optsPtr, options.Handle);
Expand Down
32 changes: 32 additions & 0 deletions src/Nethermind/Nethermind.Db.Rocks/HyperClockCacheWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Threading;
using Nethermind.Core.Extensions;
using RocksDbSharp;

namespace Nethermind.Db.Rocks;

public class HyperClockCacheWrapper : IDisposable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably should just derive from SafeHandleZeroOrMinusOneIsInvalid or SafeHandle: https://blog.benoitblanchon.fr/safehandle/

{
private readonly IntPtr _cacheHandle;

public HyperClockCacheWrapper(ulong capacity = 32_000_000)
{
_cacheHandle = RocksDbSharp.Native.Instance.rocksdb_cache_create_hyper_clock(new UIntPtr(capacity), 0);
}

private bool _isDisposed;
public IntPtr? Handle => _cacheHandle;
public void Dispose()
{
if (Interlocked.CompareExchange(ref _isDisposed, true, false)) return;
RocksDbSharp.Native.Instance.rocksdb_cache_destroy(_cacheHandle);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RocksDbSharp.Native.Instance.rocksdb_cache_destroy(_cacheHandle);
RocksDbSharp.Native.Instance.rocksdb_cache_destroy(_cacheHandle);
_cacheHandle = IntPtr.Zero;

}

public long GetUsage()
{
return (long)Native.Instance.rocksdb_cache_get_usage(_cacheHandle);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add disposed check?

}
}
20 changes: 8 additions & 12 deletions src/Nethermind/Nethermind.Db.Rocks/RocksDbFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,33 @@ public class RocksDbFactory : IDbFactory

private readonly string _basePath;

private readonly IntPtr _sharedCache;
private readonly HyperClockCacheWrapper _sharedCache;
private readonly IRocksDbConfigFactory _rocksDbConfigFactory;

public RocksDbFactory(IRocksDbConfigFactory rocksDbConfigFactory, IDbConfig dbConfig, IInitConfig initConfig, ILogManager logManager)
: this(rocksDbConfigFactory, dbConfig, logManager, initConfig.BaseDbPath)
public RocksDbFactory(IRocksDbConfigFactory rocksDbConfigFactory, IDbConfig dbConfig, IInitConfig initConfig, HyperClockCacheWrapper sharedCache, ILogManager logManager)
: this(rocksDbConfigFactory, dbConfig, sharedCache, logManager, initConfig.BaseDbPath)
{

}

public RocksDbFactory(IRocksDbConfigFactory rocksDbConfigFactory, IDbConfig dbConfig, ILogManager logManager, string basePath)
public RocksDbFactory(IRocksDbConfigFactory rocksDbConfigFactory, IDbConfig dbConfig, HyperClockCacheWrapper sharedCache, ILogManager logManager, string basePath)
{
_rocksDbConfigFactory = rocksDbConfigFactory;
_dbConfig = dbConfig;
_logManager = logManager;
_basePath = basePath;

ILogger logger = _logManager.GetClassLogger<RocksDbFactory>();
if (logger.IsDebug) logger.Debug($"Shared memory size is {dbConfig.SharedBlockCacheSize}");

if (logger.IsDebug)
{
logger.Debug($"Shared memory size is {dbConfig.SharedBlockCacheSize}");
}

_sharedCache = RocksDbSharp.Native.Instance.rocksdb_cache_create_lru(new UIntPtr(dbConfig.SharedBlockCacheSize));
_sharedCache = sharedCache;
}

public IDb CreateDb(DbSettings dbSettings) =>
new DbOnTheRocks(_basePath, dbSettings, _dbConfig, _rocksDbConfigFactory, _logManager, sharedCache: _sharedCache);
new DbOnTheRocks(_basePath, dbSettings, _dbConfig, _rocksDbConfigFactory, _logManager, sharedCache: _sharedCache.Handle);

public IColumnsDb<T> CreateColumnsDb<T>(DbSettings dbSettings) where T : struct, Enum =>
new ColumnsDb<T>(_basePath, dbSettings, _dbConfig, _rocksDbConfigFactory, _logManager, Array.Empty<T>(), sharedCache: _sharedCache);
new ColumnsDb<T>(_basePath, dbSettings, _dbConfig, _rocksDbConfigFactory, _logManager, Array.Empty<T>(), sharedCache: _sharedCache.Handle);

public string GetFullDbPath(DbSettings dbSettings) => DbOnTheRocks.GetFullDbPath(dbSettings.DbPath, _basePath);
}
Loading