Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
89 changes: 87 additions & 2 deletions src/TidesDB/ColumnFamily.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,32 @@ public void Compact()
TidesDBException.ThrowIfError(result, "failed to compact column family");
}

/// <summary>
/// Synchronously compacts every SSTable whose key range overlaps [startKey, endKey).
/// Blocks the calling thread until the merge commits or fails - does not enqueue onto
/// the compaction thread pool. Pass <c>null</c> for either endpoint to indicate it is
/// unbounded on that side; both null is rejected with <c>InvalidArgs</c> (use
/// <see cref="Compact"/> for full-CF compaction).
/// </summary>
/// <param name="startKey">Inclusive lower bound, or null for unbounded.</param>
/// <param name="endKey">Exclusive upper bound, or null for unbounded.</param>
public void CompactRange(ReadOnlySpan<byte> startKey, ReadOnlySpan<byte> endKey)
{
int result;
unsafe
{
fixed (byte* startPtr = startKey)
fixed (byte* endPtr = endKey)
{
result = NativeMethods.tidesdb_compact_range(
Handle,
startKey.IsEmpty ? null : startPtr, (nuint)startKey.Length,
endKey.IsEmpty ? null : endPtr, (nuint)endKey.Length);
}
}
TidesDBException.ThrowIfError(result, "failed to compact range");
}

/// <summary>
/// Manually triggers memtable flush for this column family.
/// </summary>
Expand Down Expand Up @@ -242,14 +268,20 @@ public Stats GetStats()
UseBtree = nativeStats.UseBtree != 0,
BtreeTotalNodes = nativeStats.BtreeTotalNodes,
BtreeMaxHeight = nativeStats.BtreeMaxHeight,
BtreeAvgHeight = nativeStats.BtreeAvgHeight
BtreeAvgHeight = nativeStats.BtreeAvgHeight,
TotalTombstones = nativeStats.TotalTombstones,
TombstoneRatio = nativeStats.TombstoneRatio,
MaxSstDensity = nativeStats.MaxSstDensity,
MaxSstDensityLevel = nativeStats.MaxSstDensityLevel,
Config = ReadColumnFamilyConfig(nativeStats.Config),
};

if (nativeStats.NumLevels > 0)
{
var levelSizes = new ulong[nativeStats.NumLevels];
var levelNumSstables = new int[nativeStats.NumLevels];
var levelKeyCounts = new ulong[nativeStats.NumLevels];
var levelTombstoneCounts = new ulong[nativeStats.NumLevels];

if (nativeStats.LevelSizes != nint.Zero)
{
Expand All @@ -275,11 +307,20 @@ public Stats GetStats()
}
}

if (nativeStats.LevelTombstoneCounts != nint.Zero)
{
for (int i = 0; i < nativeStats.NumLevels; i++)
{
levelTombstoneCounts[i] = (ulong)Marshal.ReadInt64(nativeStats.LevelTombstoneCounts, i * sizeof(ulong));
}
}

return stats with
{
LevelSizes = levelSizes,
LevelNumSstables = levelNumSstables,
LevelKeyCounts = levelKeyCounts
LevelKeyCounts = levelKeyCounts,
LevelTombstoneCounts = levelTombstoneCounts,
};
}

Expand All @@ -290,4 +331,48 @@ public Stats GetStats()
NativeMethods.tidesdb_free_stats(statsPtr);
}
}

private static unsafe ColumnFamilyConfig? ReadColumnFamilyConfig(nint configPtr)
{
if (configPtr == nint.Zero) return null;

var n = Marshal.PtrToStructure<NativeColumnFamilyConfig>(configPtr);

string comparatorName = "";
var nameBytes = new byte[64];
Marshal.Copy(configPtr + (int)Marshal.OffsetOf<NativeColumnFamilyConfig>(nameof(NativeColumnFamilyConfig.ComparatorName)),
nameBytes, 0, nameBytes.Length);
int nameLen = Array.IndexOf<byte>(nameBytes, 0);
if (nameLen < 0) nameLen = nameBytes.Length;
if (nameLen > 0) comparatorName = System.Text.Encoding.UTF8.GetString(nameBytes, 0, nameLen);

return new ColumnFamilyConfig
{
WriteBufferSize = (ulong)n.WriteBufferSize,
LevelSizeRatio = (ulong)n.LevelSizeRatio,
MinLevels = n.MinLevels,
DividingLevelOffset = n.DividingLevelOffset,
KlogValueThreshold = (ulong)n.KlogValueThreshold,
CompressionAlgorithm = (CompressionAlgorithm)n.CompressionAlgo,
EnableBloomFilter = n.EnableBloomFilter != 0,
BloomFpr = n.BloomFpr,
EnableBlockIndexes = n.EnableBlockIndexes != 0,
IndexSampleRatio = n.IndexSampleRatio,
BlockIndexPrefixLen = n.BlockIndexPrefixLen,
SyncMode = (SyncMode)n.SyncMode,
SyncIntervalUs = n.SyncIntervalUs,
ComparatorName = comparatorName,
SkipListMaxLevel = n.SkipListMaxLevel,
SkipListProbability = n.SkipListProbability,
DefaultIsolationLevel = (IsolationLevel)n.DefaultIsolationLevel,
MinDiskSpace = n.MinDiskSpace,
L1FileCountTrigger = n.L1FileCountTrigger,
L0QueueStallThreshold = n.L0QueueStallThreshold,
TombstoneDensityTrigger = n.TombstoneDensityTrigger,
TombstoneDensityMinEntries = n.TombstoneDensityMinEntries,
UseBtree = n.UseBtree != 0,
ObjectLazyCompaction = n.ObjectLazyCompaction != 0,
ObjectPrefetchCompaction = n.ObjectPrefetchCompaction != 0,
};
}
}
65 changes: 62 additions & 3 deletions src/TidesDB/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using TidesDB.Native;

namespace TidesDB;

/// <summary>
Expand Down Expand Up @@ -102,8 +104,19 @@ public sealed class Config
/// </summary>
public ObjectStoreConfig? ObjectStoreConfig { get; init; }

private static readonly int s_defaultMaxConcurrentFlushes = NativeMethods.tidesdb_default_config().MaxConcurrentFlushes;

/// <summary>
/// Creates a default configuration with the specified database path.
/// Global semaphore on the number of in-flight memtable flushes across all column
/// families. Bounds peak transient memory and work-queue depth. 0 falls back to the
/// library default (TDB_DEFAULT_MAX_CONCURRENT_FLUSHES).
/// </summary>
public int MaxConcurrentFlushes { get; init; } = s_defaultMaxConcurrentFlushes;

/// <summary>
/// Creates a default configuration with the specified database path. Field defaults
/// are sourced from the C library (tidesdb_default_config) so the binding tracks
/// library defaults rather than duplicating constants.
/// </summary>
public static Config Default(string dbPath) => new()
{
Expand Down Expand Up @@ -216,6 +229,18 @@ public sealed class ColumnFamilyConfig
/// </summary>
public int L0QueueStallThreshold { get; init; } = 20;

/// <summary>
/// Per-SSTable tombstone density (tombstone_count / num_entries) above which compaction
/// priority escalates. Range [0.0, 1.0]. 0.0 disables the check (default).
/// </summary>
public double TombstoneDensityTrigger { get; init; }

/// <summary>
/// Minimum entry count for an SSTable to be considered by the tombstone density trigger.
/// SSTables with fewer entries are ignored to prevent tiny-sstable noise (default: 1024).
/// </summary>
public ulong TombstoneDensityMinEntries { get; init; } = 1024;

/// <summary>
/// Use B+tree format for klog (default: false = block-based).
/// </summary>
Expand All @@ -232,9 +257,43 @@ public sealed class ColumnFamilyConfig
public bool ObjectPrefetchCompaction { get; init; } = true;

/// <summary>
/// Creates a default column family configuration.
/// Creates a default column family configuration sourced from the C library defaults
/// (tidesdb_default_column_family_config), so the binding tracks library defaults
/// rather than duplicating constants.
/// </summary>
public static ColumnFamilyConfig Default => new();
public static ColumnFamilyConfig Default => FromNativeDefaults();

private static unsafe ColumnFamilyConfig FromNativeDefaults()
{
var n = NativeMethods.tidesdb_default_column_family_config();
return new ColumnFamilyConfig
{
WriteBufferSize = (ulong)n.WriteBufferSize,
LevelSizeRatio = (ulong)n.LevelSizeRatio,
MinLevels = n.MinLevels,
DividingLevelOffset = n.DividingLevelOffset,
KlogValueThreshold = (ulong)n.KlogValueThreshold,
CompressionAlgorithm = (CompressionAlgorithm)n.CompressionAlgo,
EnableBloomFilter = n.EnableBloomFilter != 0,
BloomFpr = n.BloomFpr,
EnableBlockIndexes = n.EnableBlockIndexes != 0,
IndexSampleRatio = n.IndexSampleRatio,
BlockIndexPrefixLen = n.BlockIndexPrefixLen,
SyncMode = (SyncMode)n.SyncMode,
SyncIntervalUs = n.SyncIntervalUs,
SkipListMaxLevel = n.SkipListMaxLevel,
SkipListProbability = n.SkipListProbability,
DefaultIsolationLevel = (IsolationLevel)n.DefaultIsolationLevel,
MinDiskSpace = n.MinDiskSpace,
L1FileCountTrigger = n.L1FileCountTrigger,
L0QueueStallThreshold = n.L0QueueStallThreshold,
TombstoneDensityTrigger = n.TombstoneDensityTrigger,
TombstoneDensityMinEntries = n.TombstoneDensityMinEntries,
UseBtree = n.UseBtree != 0,
ObjectLazyCompaction = n.ObjectLazyCompaction != 0,
ObjectPrefetchCompaction = n.ObjectPrefetchCompaction != 0,
};
}
}

/// <summary>
Expand Down
6 changes: 6 additions & 0 deletions src/TidesDB/Native/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ internal static partial class NativeMethods
[LibraryImport(LibraryName, EntryPoint = "tidesdb_compact")]
internal static partial int tidesdb_compact(nint cf);

[LibraryImport(LibraryName, EntryPoint = "tidesdb_compact_range")]
internal static unsafe partial int tidesdb_compact_range(nint cf, byte* startKey, nuint startKeySize, byte* endKey, nuint endKeySize);

[LibraryImport(LibraryName, EntryPoint = "tidesdb_flush_memtable")]
internal static partial int tidesdb_flush_memtable(nint cf);

Expand All @@ -167,6 +170,9 @@ internal static partial class NativeMethods
[LibraryImport(LibraryName, EntryPoint = "tidesdb_default_column_family_config")]
internal static partial NativeColumnFamilyConfig tidesdb_default_column_family_config();

[LibraryImport(LibraryName, EntryPoint = "tidesdb_default_config")]
internal static partial NativeConfig tidesdb_default_config();

// Comparator operations
[LibraryImport(LibraryName, EntryPoint = "tidesdb_register_comparator", StringMarshalling = StringMarshalling.Utf8)]
internal static partial int tidesdb_register_comparator(nint db, string name, nint fn, string? ctxStr, nint ctx);
Expand Down
8 changes: 8 additions & 0 deletions src/TidesDB/Native/NativeStructs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ internal struct NativeConfig
public ulong UnifiedMemtableSyncIntervalUs;
public nint ObjectStore;
public nint ObjectStoreConfig;
public int MaxConcurrentFlushes;
}

[StructLayout(LayoutKind.Sequential)]
Expand Down Expand Up @@ -88,6 +89,8 @@ internal unsafe struct NativeColumnFamilyConfig
public ulong MinDiskSpace;
public int L1FileCountTrigger;
public int L0QueueStallThreshold;
public double TombstoneDensityTrigger;
public ulong TombstoneDensityMinEntries;
public int UseBtree;
public nint CommitHookFn;
public nint CommitHookCtx;
Expand Down Expand Up @@ -127,6 +130,11 @@ internal struct NativeStats
public ulong BtreeTotalNodes;
public uint BtreeMaxHeight;
public double BtreeAvgHeight;
public ulong TotalTombstones;
public double TombstoneRatio;
public nint LevelTombstoneCounts;
public double MaxSstDensity;
public int MaxSstDensityLevel;
}

[StructLayout(LayoutKind.Sequential)]
Expand Down
25 changes: 25 additions & 0 deletions src/TidesDB/Stats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,31 @@ public sealed record Stats
/// Average tree height across all SSTables (only populated if UseBtree=true).
/// </summary>
public double BtreeAvgHeight { get; init; }

/// <summary>
/// Sum of tombstone counts across every SSTable in the column family.
/// </summary>
public ulong TotalTombstones { get; init; }

/// <summary>
/// Ratio of total tombstones to total keys (0.0 if no keys). Range [0.0, 1.0].
/// </summary>
public double TombstoneRatio { get; init; }

/// <summary>
/// Tombstone count per level (parallels <see cref="LevelKeyCounts"/>).
/// </summary>
public ulong[] LevelTombstoneCounts { get; init; } = [];

/// <summary>
/// Worst per-SSTable tombstone density observed in the column family. Range [0.0, 1.0].
/// </summary>
public double MaxSstDensity { get; init; }

/// <summary>
/// 1-based level index where <see cref="MaxSstDensity"/> was observed (0 if none).
/// </summary>
public int MaxSstDensityLevel { get; init; }
}

/// <summary>
Expand Down
5 changes: 4 additions & 1 deletion src/TidesDB/TidesDB.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ public static TidesDb Open(Config config)
UnifiedMemtableSyncMode = (int)config.UnifiedMemtableSyncMode,
UnifiedMemtableSyncIntervalUs = config.UnifiedMemtableSyncIntervalUs,
ObjectStore = objStorePtr,
ObjectStoreConfig = objStoreConfigPtr
ObjectStoreConfig = objStoreConfigPtr,
MaxConcurrentFlushes = config.MaxConcurrentFlushes
};

var result = NativeMethods.tidesdb_open(ref nativeConfig, out var dbHandle);
Expand Down Expand Up @@ -426,6 +427,8 @@ private static unsafe NativeColumnFamilyConfig CreateNativeColumnFamilyConfig(Co
MinDiskSpace = config.MinDiskSpace,
L1FileCountTrigger = config.L1FileCountTrigger,
L0QueueStallThreshold = config.L0QueueStallThreshold,
TombstoneDensityTrigger = config.TombstoneDensityTrigger,
TombstoneDensityMinEntries = config.TombstoneDensityMinEntries,
UseBtree = config.UseBtree ? 1 : 0,
CommitHookFn = nint.Zero,
CommitHookCtx = nint.Zero,
Expand Down
2 changes: 1 addition & 1 deletion src/TidesDB/TidesDB.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<!-- Package metadata -->
<PackageId>TidesDB</PackageId>
<Version>0.5.0</Version>
<Version>0.6.0</Version>
<Authors>TidesDB</Authors>
<Company>TidesDB</Company>
<Description>Official C# bindings for TidesDB, a high-performance embedded key-value storage engine. Wraps the native libtidesdb shared library, which must be installed on the target system.</Description>
Expand Down
Loading
Loading