Skip to main content

Embedded Kahuna Node

EmbeddedKahunaNode starts a single-node Kahuna engine inside the current .NET process. It is useful for integration tests, local tools, and applications that need Kahuna's transaction engine without running the ASP.NET server, Kestrel, REST, or external gRPC endpoints.

using System.Text;
using Kahuna;
using Kahuna.Shared.KeyValue;
using Kommander.Time;

await using var node = new EmbeddedKahunaNode(new()
{
Storage = "memory",
WalStorage = "memory",
InitialPartitions = 1
});

await node.StartAsync();
await node.WaitForLeaderForKeyAsync("tenant/table/key-a");

byte[] value = Encoding.UTF8.GetBytes("value-a");

await node.Kahuna.LocateAndTrySetKeyValue(
transactionId: HLCTimestamp.Zero,
key: "tenant/table/key-a",
value: value,
compareValue: null,
compareRevision: -1,
flags: KeyValueFlags.Set,
expiresMs: 0,
durability: KeyValueDurability.Persistent,
cancellationToken: CancellationToken.None
);

API

public sealed class EmbeddedKahunaNode : IAsyncDisposable
{
public IKahuna Kahuna { get; }
public IRaft Raft { get; }

public EmbeddedKahunaNode(
EmbeddedKahunaOptions options,
ILoggerFactory? loggerFactory = null
);

public Task StartAsync(CancellationToken cancellationToken = default);
public Task<string> WaitForLeaderForKeyAsync(string key, CancellationToken cancellationToken = default);
public ValueTask DisposeAsync();
}

Options

OptionDefaultDescription
NodeNameembedded-1Logical node name.
NodeId1Raft node identifier.
HostlocalhostMust be a concrete host, not *.
Port0Unused by in-memory communication, but still part of the Raft configuration.
InitialPartitions1Number of Raft partitions.
StoragememoryKey/value storage backend: memory, sqlite, or rocksdb.
StoragePathemptyStorage directory for persistent backends.
StorageRevisiongeneratedStorage revision name.
WalStoragememoryRaft WAL backend: memory, sqlite, or rocksdb.
WalPathemptyWAL directory for persistent backends.
WalRevisiongeneratedWAL revision name.
WalSyncWritestrueRequire synchronous durable writes for RocksDB or SQLite WAL storage.
RocksDbSharedMemoryEnabledfalseShare one RocksDB block cache and write-buffer manager between the key/value backend and Raft WAL when both use RocksDB.
RocksDbSharedMemoryBudgetMb320Total shared RocksDB block-cache budget in MiB. The memtable sub-budget is charged inside this total.
RocksDbSharedMemtableBudgetMb128Shared RocksDB memtable sub-budget in MiB. Must be less than or equal to RocksDbSharedMemoryBudgetMb.
LocksWorkersEnvironment.ProcessorCountLock worker count.
KeyValueWorkersEnvironment.ProcessorCountKey/value worker count.
BackgroundWriterWorkers1Background persistence worker count.
DefaultTransactionTimeout5000Default transaction timeout in milliseconds.
ScriptCacheExpiration1 minuteHow long parsed scripts stay cached.
RevisionsToKeepCached100Number of key revisions to keep cached in memory.
CacheEntryTtl5 minutesAge threshold used by lock cleanup and legacy cleanup paths. Key/value LRU eviction is budget-based.
CacheEntriesToRemove1000Maximum entries removed by cleanup paths that use this cap. Key/value collection uses CollectBatchMax.
CollectionInterval60 secondsInterval for cache collection and eviction checks.
MaxEntriesPerActor50000Maximum cached entries per actor before collection pressure applies.
MaxBytesPerActor268435456Approximate maximum cached bytes per actor before collection pressure applies.
CollectBatchMax1000Maximum number of entries evicted in one collection pass.
RevisionRetention16Number of revisions retained for in-memory revision history.
DirtyObjectsWriterDelay1000Delay between dirty object writer flush passes, in milliseconds. Longer values can increase batching but keep dirty persistent entries pinned in memory longer.
PersistentRevisionRetentionCount0Maximum persisted revisions retained per key. 0 keeps every revision.
PersistentRevisionRetentionAge0Maximum persisted revision age. TimeSpan.Zero disables age-based retention.
PersistentRevisionCleanupInterval5 minutesMinimum interval between full persistent-revision cleanup sweeps.
PersistentRevisionCleanupBatchSize1000Maximum revision records deleted per cleanup pass.
PersistentRevisionCleanupOnWritetrueQueue keys touched by writes for targeted revision cleanup.
PitrWindow1 hourRecoverable WAL history. Values are normalized to more than zero and at most 6 hours.
BaseSnapshotInterval30 minutesIntended interval between base checkpoints. It must be positive and no greater than PitrWindow. It also contributes to the protected WAL floor.
BackupDiremptyRoot directory for backup manifests and artifacts. Backup methods on node.Kahuna are disabled when empty.
RangeSplitThreshold1000Sampled key count that triggers count-based range splitting. 0 disables this trigger.
RangeSplitMinRangeSize10Minimum sampled keys required in each child range.
RangeSplitLoadThreshold0Replicated writes per second required for load-based splitting. 0 disables this trigger.
RangeSplitLoadMinQueueDepth8WAL backlog required alongside the load threshold.
RangeSplitLoadMinCommitWaitMs0Optional commit-wait gate in milliseconds. 0 disables it.
RangeSplitLoadWindow15 secondsTime the complete load predicate must remain satisfied.
RangeSplitLoadPollInterval5 secondsFrequency of load-based split checks.
RangeSplitLoadImbalanceMax0.8Maximum acceptable write fraction assigned to either child.
RangeSplitIndivisibleCooldown5 minutesDelay before reconsidering an indivisible range.
RangeSplitSettleWindow10 secondsPost-split delay before evaluating either child again.
EnableLeaderBalancerfalseEnable cross-node load reports and leader redistribution. Required with load splitting.
LeaderBalancerReportInterval5 secondsInterval between node load reports.
LeaderBalancerInterval30 secondsInterval between balancing passes.
LeaderBalancerReportTtl20 secondsMaximum accepted load-report age.
MinLeaderStability5 secondsMinimum leadership age before transfer.
LeaderBalancerOpsWeight1.0Operations-per-second weight in the balancer load score.
LeaderBalancerQueueWeight0.5Queue-depth weight in the balancer load score.
ReadIOThreads8Number of Raft read I/O threads.
WriteIOThreads8Number of Raft write I/O threads.
EnableSharedExecutorPooltrueShare a bounded worker pool across Raft partitions instead of using one OS thread per partition.
PartitionExecutorPoolSize0Shared Raft executor worker count. 0 auto-sizes to the processor count.
HttpSchemehttps://HTTP scheme used by Raft REST communication.
HttpAuthBearerTokenemptyBearer token sent with Raft REST communication.
HttpTimeout5Raft REST request timeout in seconds.
HttpVersion2.0HTTP protocol version used by Raft REST communication.
HeartbeatInterval500 msLeader heartbeat interval.
RecentHeartbeat100 msRecent-heartbeat window.
VotingTimeout1500 msVote wait timeout.
CheckLeaderInterval250 msLeader check interval.
TimerInitialDelay2500 msInitial delay before Raft timers start.
UpdateNodesInterval5000 msNode registry update interval.
StartElectionTimeout500Minimum election timeout in milliseconds.
EndElectionTimeout1500Maximum election timeout in milliseconds.
StartElectionTimeoutIncrement100Minimum election timeout increment in milliseconds.
EndElectionTimeoutIncrement200Maximum election timeout increment in milliseconds.
SlowRaftStateMachineLog50Slow state-machine operation log threshold in milliseconds.
SlowRaftWALMachineLog25Slow WAL state-machine operation log threshold in milliseconds.
CompactEveryOperations1000Number of committed operations between automatic Raft WAL compaction checks.
CompactNumberEntries50Number of Raft WAL entries removed per compaction batch.
MaxEntriesPerCompaction5000Maximum Raft WAL entries processed per compaction run.

Live snapshot holds clamp persistent revision cleanup. While a hold is active, the boundary revision needed by the held timestamp and every newer revision are kept even if PersistentRevisionRetentionCount or PersistentRevisionRetentionAge would otherwise remove them.

To bound RocksDB memory across both the embedded key/value backend and Raft WAL, enable shared RocksDB memory with both storage layers set to RocksDB:

EmbeddedKahunaOptions options = new()
{
Storage = "rocksdb",
WalStorage = "rocksdb",
RocksDbSharedMemoryEnabled = true,
RocksDbSharedMemoryBudgetMb = 512,
RocksDbSharedMemtableBudgetMb = 128
};

If either Storage or WalStorage is not rocksdb, these shared-memory options are ignored.

Code-Level Configuration

Two KahunaConfiguration options are not currently exposed by either Kahuna.Server command-line flags or EmbeddedKahunaOptions:

OptionDefaultDescription
ScriptCacheMaxEntries1000Maximum parsed scripts retained in the server-side script cache. New entries are dropped when the limit is reached.
RangeMergeMinSize10Adjacent key ranges smaller than this value can be considered for automatic merging. 0 disables automatic merge.

HttpsTrustedThumbprint also exists on KahunaConfiguration, but it is derived from HttpsCertificate by configuration validation rather than being an independent operator setting.

Notes

  • The embedded node uses in-memory Raft and inter-node communication.
  • StartAsync joins the single-node cluster and waits for leaders for the configured partitions.
  • WaitForLeaderForKeyAsync waits for the partition that owns a specific key.
  • Use distinct StoragePath and WalPath values when using sqlite or rocksdb.
  • Set BackupDir to enable backup, catalog, and offline restore methods through node.Kahuna. See Backups and Point-in-Time Recovery.
  • Load-based splitting requires a multi-node embedded deployment, key-range-routed spaces, and EnableLeaderBalancer = true. See Load-Based Range Splitting.
  • Always dispose the node with await using or DisposeAsync so Raft leaves the cluster and file-backed resources are released.