diff --git a/Version.props b/Version.props index 20e5abf6635..45fcee605a6 100644 --- a/Version.props +++ b/Version.props @@ -1,6 +1,6 @@ - 1.0.83 + 1.0.84 diff --git a/benchmark/BDN.benchmark/Custom/CustomTxnSet.cs b/benchmark/BDN.benchmark/Custom/CustomTxnSet.cs index de849013dab..835e966ba6c 100644 --- a/benchmark/BDN.benchmark/Custom/CustomTxnSet.cs +++ b/benchmark/BDN.benchmark/Custom/CustomTxnSet.cs @@ -52,10 +52,10 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce valueC = GetNextArg(ref procInput, ref offset); valueD = GetNextArg(ref procInput, ref offset); - AddKey(setA, LockType.Exclusive, isObject: false); - AddKey(setB, LockType.Exclusive, isObject: false); - AddKey(setC, LockType.Exclusive, isObject: false); - AddKey(setD, LockType.Exclusive, isObject: false); + AddKey(setA, LockType.Exclusive, StoreType.Main); + AddKey(setB, LockType.Exclusive, StoreType.Main); + AddKey(setC, LockType.Exclusive, StoreType.Main); + AddKey(setD, LockType.Exclusive, StoreType.Main); return true; } diff --git a/libs/client/ClientSession/GarnetClientSessionClusterExtensions.cs b/libs/client/ClientSession/GarnetClientSessionClusterExtensions.cs deleted file mode 100644 index 0b64944783c..00000000000 --- a/libs/client/ClientSession/GarnetClientSessionClusterExtensions.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Garnet.common; -using Garnet.networking; - -namespace Garnet.client -{ - /// - /// Mono-threaded remote client session for Garnet (a session makes a single network connection, and - /// expects mono-threaded client access, i.e., no concurrent invocations of API by client) - /// - public sealed unsafe partial class GarnetClientSession : IServerHook, IMessageConsumer - { - static ReadOnlySpan GOSSIP => "GOSSIP"u8; - - /// - /// Send gossip message to corresponding node - /// - /// - /// - public Task ExecuteGossip(Memory byteArray) - { - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - tcsQueue.Enqueue(tcs); - byte* curr = offset; - byte* next = offset; - int arraySize = 3; - - while (!RespWriteUtils.TryWriteArrayLength(arraySize, ref curr, end)) - { - Flush(); - curr = offset; - } - offset = curr; - - //1 - while (!RespWriteUtils.TryWriteDirect(CLUSTER, ref curr, end)) - { - Flush(); - curr = offset; - } - offset = curr; - - //2 - while (!RespWriteUtils.TryWriteBulkString(GOSSIP, ref curr, end)) - { - Flush(); - curr = offset; - } - offset = curr; - - //3 - while (!RespWriteUtils.TryWriteBulkString(byteArray.Span, ref curr, end)) - { - Flush(); - curr = offset; - } - offset = curr; - - Flush(); - Interlocked.Increment(ref numCommands); - return tcs.Task; - } - } -} \ No newline at end of file diff --git a/libs/client/ClientSession/GarnetClientSessionReplicationExtensions.cs b/libs/client/ClientSession/GarnetClientSessionReplicationExtensions.cs index 2f9f060c415..3beac1a77ff 100644 --- a/libs/client/ClientSession/GarnetClientSessionReplicationExtensions.cs +++ b/libs/client/ClientSession/GarnetClientSessionReplicationExtensions.cs @@ -31,6 +31,7 @@ public sealed unsafe partial class GarnetClientSession : IServerHook, IMessageCo /// /// /// + /// public Task ExecuteReplicaSync(string nodeId, string primary_replid, byte[] checkpointEntryData, long aofBeginAddress, long aofTailAddress) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -112,6 +113,7 @@ public Task ExecuteReplicaSync(string nodeId, string primary_replid, byt /// /// /// + /// public Task ExecuteSendCkptMetadata(Memory fileTokenBytes, int fileType, Memory data) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -178,6 +180,7 @@ public Task ExecuteSendCkptMetadata(Memory fileTokenBytes, int fil /// /// /// + /// public Task ExecuteSendFileSegments(Memory fileTokenBytes, int fileType, long startAddress, Span data, int segmentId = -1) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -257,19 +260,19 @@ public Task ExecuteSendFileSegments(Memory fileTokenBytes, int fil /// Signal replica to recover /// /// - /// /// /// /// /// /// /// - public Task ExecuteBeginReplicaRecover(bool sendStoreCheckpoint, bool sendObjectStoreCheckpoint, bool replayAOF, string primary_replid, byte[] checkpointEntryData, long beginAddress, long tailAddress) + /// + public Task ExecuteBeginReplicaRecover(bool sendStoreCheckpoint, bool replayAOF, string primary_replid, byte[] checkpointEntryData, long beginAddress, long tailAddress) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); tcsQueue.Enqueue(tcs); byte* curr = offset; - int arraySize = 9; + int arraySize = 8; while (!RespWriteUtils.TryWriteArrayLength(arraySize, ref curr, end)) { @@ -303,14 +306,6 @@ public Task ExecuteBeginReplicaRecover(bool sendStoreCheckpoint, bool se offset = curr; //4 - while (!RespWriteUtils.TryWriteBulkString(sendObjectStoreCheckpoint ? "1"u8 : "0"u8, ref curr, end)) - { - Flush(); - curr = offset; - } - offset = curr; - - //5 while (!RespWriteUtils.TryWriteBulkString(replayAOF ? "1"u8 : "0"u8, ref curr, end)) { Flush(); @@ -318,7 +313,7 @@ public Task ExecuteBeginReplicaRecover(bool sendStoreCheckpoint, bool se } offset = curr; - //6 + //5 while (!RespWriteUtils.TryWriteAsciiBulkString(primary_replid, ref curr, end)) { Flush(); @@ -326,7 +321,7 @@ public Task ExecuteBeginReplicaRecover(bool sendStoreCheckpoint, bool se } offset = curr; - //7 + //6 while (!RespWriteUtils.TryWriteBulkString(checkpointEntryData, ref curr, end)) { Flush(); @@ -334,7 +329,7 @@ public Task ExecuteBeginReplicaRecover(bool sendStoreCheckpoint, bool se } offset = curr; - //8 + //7 while (!RespWriteUtils.TryWriteArrayItem(beginAddress, ref curr, end)) { Flush(); @@ -342,7 +337,7 @@ public Task ExecuteBeginReplicaRecover(bool sendStoreCheckpoint, bool se } offset = curr; - //9 + //8 while (!RespWriteUtils.TryWriteArrayItem(tailAddress, ref curr, end)) { Flush(); @@ -360,6 +355,7 @@ public Task ExecuteBeginReplicaRecover(bool sendStoreCheckpoint, bool se /// /// /// + /// public Task ExecuteAttachSync(byte[] syncMetadata) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -408,6 +404,7 @@ public Task ExecuteAttachSync(byte[] syncMetadata) /// /// /// + /// public void SetClusterSyncHeader(string sourceNodeId, bool isMainStore) { // Unlike Migration, where we don't know at the time of header initialization if we have a record or not, in Replication diff --git a/libs/cluster/Server/ClusterManagerSlotState.cs b/libs/cluster/Server/ClusterManagerSlotState.cs index 5041f656d9a..3f35e1c029f 100644 --- a/libs/cluster/Server/ClusterManagerSlotState.cs +++ b/libs/cluster/Server/ClusterManagerSlotState.cs @@ -13,10 +13,13 @@ namespace Garnet.cluster { using BasicGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, BasicContext, + ObjectAllocator>>, + BasicContext, ObjectAllocator>>>; /// @@ -482,7 +485,7 @@ public static unsafe void DeleteKeysInSlotsFromMainStore(BasicGarnetApi BasicGar var key = iter.Key; var s = HashSlotUtils.HashSlot(key); if (slots.Contains(s)) - _ = BasicGarnetApi.DELETE(PinnedSpanByte.FromPinnedSpan(key), StoreType.Main); + _ = BasicGarnetApi.DELETE(PinnedSpanByte.FromPinnedSpan(key)); } } @@ -499,7 +502,7 @@ public static unsafe void DeleteKeysInSlotsFromObjectStore(BasicGarnetApi BasicG var key = iterObject.Key; var s = HashSlotUtils.HashSlot(key); if (slots.Contains(s)) - _ = BasicGarnetApi.DELETE(PinnedSpanByte.FromPinnedSpan(key), StoreType.Object); + _ = BasicGarnetApi.DELETE(PinnedSpanByte.FromPinnedSpan(key)); } } } diff --git a/libs/cluster/Server/ClusterManagerWorkerState.cs b/libs/cluster/Server/ClusterManagerWorkerState.cs index 8d83c355ca5..15f4f308a94 100644 --- a/libs/cluster/Server/ClusterManagerWorkerState.cs +++ b/libs/cluster/Server/ClusterManagerWorkerState.cs @@ -100,6 +100,10 @@ public ReadOnlySpan TryReset(bool soft, int expirySeconds = 60) try { SuspendConfigMerge(); + + // Reset recovery operations before proceeding with reset + clusterProvider.replicationManager.ResetRecovery(); + var resp = CmdStrings.RESP_OK; while (true) { @@ -113,8 +117,9 @@ public ReadOnlySpan TryReset(bool soft, int expirySeconds = 60) this.clusterConnectionStore.CloseAll(); var newNodeId = soft ? current.LocalNodeId : Generator.CreateHexId(); - var address = current.LocalNodeIp; - var port = current.LocalNodePort; + var endpoint = clusterProvider.storeWrapper.GetClusterEndpoint(); + var address = endpoint.Address.ToString(); + var port = endpoint.Port; var configEpoch = soft ? current.LocalNodeConfigEpoch : 0; var expiry = DateTimeOffset.UtcNow.Ticks + TimeSpan.FromSeconds(expirySeconds).Ticks; diff --git a/libs/cluster/Server/ClusterProvider.cs b/libs/cluster/Server/ClusterProvider.cs index 3c060d35273..90bd30183b5 100644 --- a/libs/cluster/Server/ClusterProvider.cs +++ b/libs/cluster/Server/ClusterProvider.cs @@ -16,10 +16,13 @@ namespace Garnet.cluster { using BasicGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, BasicContext, + ObjectAllocator>>, + BasicContext, ObjectAllocator>>>; /// @@ -157,7 +160,6 @@ public void FlushConfig() public void FlushDB(bool unsafeTruncateLog = false) { storeWrapper.store.Log.ShiftBeginAddress(storeWrapper.store.Log.TailAddress, truncateLog: unsafeTruncateLog); - storeWrapper.objectStore?.Log.ShiftBeginAddress(storeWrapper.objectStore.Log.TailAddress, truncateLog: unsafeTruncateLog); } /// @@ -171,12 +173,6 @@ public void SafeTruncateAOF(bool full, long CheckpointCoveredAofAddress, Guid st entry.metadata.storeCheckpointCoveredAofAddress = CheckpointCoveredAofAddress; entry.metadata.storePrimaryReplId = replicationManager.PrimaryReplId; - entry.metadata.objectStoreVersion = serverOptions.DisableObjects ? -1 : storeWrapper.objectStore.CurrentVersion; - entry.metadata.objectStoreHlogToken = serverOptions.DisableObjects ? default : objectStoreCheckpointToken; - entry.metadata.objectStoreIndexToken = serverOptions.DisableObjects ? default : objectStoreCheckpointToken; - entry.metadata.objectCheckpointCoveredAofAddress = CheckpointCoveredAofAddress; - entry.metadata.objectStorePrimaryReplId = replicationManager.PrimaryReplId; - // Keep track of checkpoints for replica // Used to delete old checkpoints and cleanup and also cleanup during attachment to new primary replicationManager.AddCheckpointEntry(entry, full); @@ -241,8 +237,6 @@ public MetricsItem[] GetReplicationInfo() new("second_repl_offset", replication_offset2), new("store_current_safe_aof_address", clusterEnabled ? replicationManager.StoreCurrentSafeAofAddress.ToString() : "N/A"), new("store_recovered_safe_aof_address", clusterEnabled ? replicationManager.StoreRecoveredSafeAofTailAddress.ToString() : "N/A"), - new("object_store_current_safe_aof_address", clusterEnabled && !serverOptions.DisableObjects ? replicationManager.ObjectStoreCurrentSafeAofAddress.ToString() : "N/A"), - new("object_store_recovered_safe_aof_address", clusterEnabled && !serverOptions.DisableObjects ? replicationManager.ObjectStoreRecoveredSafeAofTailAddress.ToString() : "N/A"), new("recover_status", replicationManager.currentRecoveryStatus.ToString()), new("last_failover_state", !clusterEnabled ? FailoverUtils.GetFailoverStatus(FailoverStatus.NO_FAILOVER) : failoverManager.GetLastFailoverStatus()) }; @@ -427,15 +421,10 @@ public void ExtractKeySpecs(RespCommandsInfo commandInfo, RespCommand cmd, ref S public void ClusterPublish(RespCommand cmd, ref Span channel, ref Span message) => clusterManager.TryClusterPublish(cmd, ref channel, ref message); - internal GarnetClusterCheckpointManager GetReplicationLogCheckpointManager(StoreType storeType) + internal GarnetClusterCheckpointManager GetReplicationLogCheckpointManager() { Debug.Assert(serverOptions.EnableCluster); - return storeType switch - { - StoreType.Main => (GarnetClusterCheckpointManager)storeWrapper.store.CheckpointManager, - StoreType.Object => (GarnetClusterCheckpointManager)storeWrapper.objectStore?.CheckpointManager, - _ => throw new Exception($"GetCkptManager: unexpected state {storeType}") - }; + return (GarnetClusterCheckpointManager)storeWrapper.store.CheckpointManager; } /// diff --git a/libs/cluster/Server/Migration/MigrateSessionKeys.cs b/libs/cluster/Server/Migration/MigrateSessionKeys.cs index 687f5dd6367..629f95c8f95 100644 --- a/libs/cluster/Server/Migration/MigrateSessionKeys.cs +++ b/libs/cluster/Server/Migration/MigrateSessionKeys.cs @@ -104,11 +104,8 @@ public bool MigrateKeys() return false; // Migrate object store keys - if (!clusterProvider.serverOptions.DisableObjects) - { - if (!MigrateKeysFromObjectStore()) - return false; - } + if (!MigrateKeysFromObjectStore()) + return false; } catch (Exception ex) { diff --git a/libs/cluster/Server/Migration/MigrateSessionSlots.cs b/libs/cluster/Server/Migration/MigrateSessionSlots.cs index cc139281a99..5406c79141d 100644 --- a/libs/cluster/Server/Migration/MigrateSessionSlots.cs +++ b/libs/cluster/Server/Migration/MigrateSessionSlots.cs @@ -33,17 +33,6 @@ public async Task MigrateSlotsDriverInline() var success = await CreateAndRunMigrateTasks(StoreType.Main, storeBeginAddress, storeTailAddress, mainStorePageSize); if (!success) return false; - // Send object store - if (!clusterProvider.serverOptions.DisableObjects) - { - var objectStoreBeginAddress = clusterProvider.storeWrapper.objectStore.Log.BeginAddress; - var objectStoreTailAddress = clusterProvider.storeWrapper.objectStore.Log.TailAddress; - var objectStorePageSize = 1 << clusterProvider.serverOptions.ObjectStorePageSizeBits(); - logger?.LogWarning("Object Store migrate scan range [{objectStoreBeginAddress}, {objectStoreTailAddress}]", objectStoreBeginAddress, objectStoreTailAddress); - success = await CreateAndRunMigrateTasks(StoreType.Object, objectStoreBeginAddress, objectStoreTailAddress, objectStorePageSize); - if (!success) return false; - } - return true; async Task CreateAndRunMigrateTasks(StoreType storeType, long beginAddress, long tailAddress, int pageSize) diff --git a/libs/cluster/Server/Replication/CheckpointEntry.cs b/libs/cluster/Server/Replication/CheckpointEntry.cs index a6ba395f633..9c57eb7c2af 100644 --- a/libs/cluster/Server/Replication/CheckpointEntry.cs +++ b/libs/cluster/Server/Replication/CheckpointEntry.cs @@ -22,22 +22,13 @@ public static void LogCheckpointEntry(this ILogger logger, LogLevel logLevel, st "storeHlogToken: {storeHlogToken}\n" + "storeIndexToken: {storeIndexToken}\n" + "storeCheckpointCoveredAofAddress: {storeCheckpointCoveredAofAddress}\n" + - "------------------------------------------------------------------------\n" + - "objectStoreVersion:{objectStoreVersion}\n" + - "objectStoreHlogToken:{objectStoreHlogToken}\n" + - "objectStoreIndexToken:{objectStoreIndexToken}\n" + - "objectCheckpointCoveredAofAddress:{objectCheckpointCoveredAofAddress}\n" + "------------------------------------------------------------------------\n", msg, entry._lock, entry.metadata.storeVersion, entry.metadata.storeHlogToken, entry.metadata.storeIndexToken, - entry.metadata.storeCheckpointCoveredAofAddress, - entry.metadata.objectStoreVersion, - entry.metadata.objectStoreHlogToken, - entry.metadata.objectStoreIndexToken, - entry.metadata.objectCheckpointCoveredAofAddress); + entry.metadata.storeCheckpointCoveredAofAddress); } } @@ -55,7 +46,7 @@ public CheckpointEntry() } public long GetMinAofCoveredAddress() - => Math.Max(Math.Min(metadata.storeCheckpointCoveredAofAddress, metadata.objectCheckpointCoveredAofAddress), LogAddress.FirstValidAddress); + => Math.Max(metadata.storeCheckpointCoveredAofAddress, LogAddress.FirstValidAddress); /// /// Indicate addition of new reader by trying to increment reader counter @@ -90,8 +81,6 @@ public bool ContainsSharedToken(CheckpointEntry entry, CheckpointFileType fileTy { CheckpointFileType.STORE_HLOG => metadata.storeHlogToken.Equals(entry.metadata.storeHlogToken), CheckpointFileType.STORE_INDEX => metadata.storeIndexToken.Equals(entry.metadata.storeIndexToken), - CheckpointFileType.OBJ_STORE_HLOG => metadata.objectStoreHlogToken.Equals(entry.metadata.objectStoreHlogToken), - CheckpointFileType.OBJ_STORE_INDEX => metadata.objectStoreIndexToken.Equals(entry.metadata.objectStoreIndexToken), _ => throw new Exception($"Option {fileType} not supported") }; } @@ -118,18 +107,6 @@ public byte[] ToByteArray() writer.Write(metadata.storePrimaryReplId == null ? 0 : 1); if (metadata.storePrimaryReplId != null) writer.Write(metadata.storePrimaryReplId); - // Write checkpoint entry data for object store - writer.Write(metadata.objectStoreVersion); - byteBuffer = metadata.objectStoreHlogToken.ToByteArray(); - writer.Write(byteBuffer.Length); - writer.Write(byteBuffer); - byteBuffer = metadata.objectStoreIndexToken.ToByteArray(); - writer.Write(byteBuffer.Length); - writer.Write(byteBuffer); - writer.Write(metadata.objectCheckpointCoveredAofAddress); - writer.Write(metadata.objectStorePrimaryReplId == null ? 0 : 1); - if (metadata.objectStorePrimaryReplId != null) writer.Write(metadata.objectStorePrimaryReplId); - var byteArray = ms.ToArray(); writer.Dispose(); ms.Dispose(); @@ -154,13 +131,7 @@ public static CheckpointEntry FromByteArray(byte[] serialized) storeHlogToken = new Guid(reader.ReadBytes(reader.ReadInt32())), storeIndexToken = new Guid(reader.ReadBytes(reader.ReadInt32())), storeCheckpointCoveredAofAddress = reader.ReadInt64(), - storePrimaryReplId = reader.ReadInt32() > 0 ? reader.ReadString() : default, - - objectStoreVersion = reader.ReadInt64(), - objectStoreHlogToken = new Guid(reader.ReadBytes(reader.ReadInt32())), - objectStoreIndexToken = new Guid(reader.ReadBytes(reader.ReadInt32())), - objectCheckpointCoveredAofAddress = reader.ReadInt64(), - objectStorePrimaryReplId = reader.ReadInt32() > 0 ? reader.ReadString() : default + storePrimaryReplId = reader.ReadInt32() > 0 ? reader.ReadString() : default } }; diff --git a/libs/cluster/Server/Replication/CheckpointFileType.cs b/libs/cluster/Server/Replication/CheckpointFileType.cs index da2ac9690ba..e20bb1e28fa 100644 --- a/libs/cluster/Server/Replication/CheckpointFileType.cs +++ b/libs/cluster/Server/Replication/CheckpointFileType.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System; -using Garnet.server; - namespace Garnet.cluster { /// @@ -31,49 +28,5 @@ enum CheckpointFileType : byte /// Store Snapshot /// STORE_SNAPSHOT, - /// - /// Object Store Hybrid Log - Main - /// - OBJ_STORE_HLOG, - /// - /// Object Store Hybrid Log - Object - /// - OBJ_STORE_HLOG_OBJ, - /// - /// Object Store Delta Log - /// - OBJ_STORE_DLOG, - /// - /// Object Store Index - /// - OBJ_STORE_INDEX, - /// - /// Object Store Snapshot - Main - /// - OBJ_STORE_SNAPSHOT, - /// - /// Object Store Snapshot - Object - /// - OBJ_STORE_SNAPSHOT_OBJ, - } - - static class CheckpointFileTypeExtensions - { - public static StoreType ToStoreType(this CheckpointFileType type) - { - return type switch - { - CheckpointFileType.STORE_HLOG or - CheckpointFileType.STORE_DLOG or - CheckpointFileType.STORE_INDEX or - CheckpointFileType.STORE_SNAPSHOT => StoreType.Main, - CheckpointFileType.OBJ_STORE_HLOG or - CheckpointFileType.OBJ_STORE_DLOG or - CheckpointFileType.OBJ_STORE_INDEX or - CheckpointFileType.OBJ_STORE_SNAPSHOT or - CheckpointFileType.OBJ_STORE_SNAPSHOT_OBJ => StoreType.Object, - _ => throw new Exception($"ToStoreType: unexpected state {type}") - }; - } } } \ No newline at end of file diff --git a/libs/cluster/Server/Replication/CheckpointStore.cs b/libs/cluster/Server/Replication/CheckpointStore.cs index e3d8cc44045..8616f5e5be5 100644 --- a/libs/cluster/Server/Replication/CheckpointStore.cs +++ b/libs/cluster/Server/Replication/CheckpointStore.cs @@ -40,7 +40,7 @@ public void Initialize() { head = tail = GetLatestCheckpointEntryFromDisk(); - if (tail.metadata.storeVersion == -1 && tail.metadata.objectStoreVersion == -1) + if (tail.metadata.storeVersion == -1) { head = tail = null; } @@ -48,11 +48,6 @@ public void Initialize() { clusterProvider.storeWrapper.StoreCheckpointManager.RecoveredSafeAofAddress = tail.metadata.storeCheckpointCoveredAofAddress; clusterProvider.storeWrapper.StoreCheckpointManager.RecoveredHistoryId = tail.metadata.storePrimaryReplId; - if (!storeWrapper.serverOptions.DisableObjects) - { - clusterProvider.storeWrapper.ObjectStoreCheckpointManager.RecoveredSafeAofAddress = tail.metadata.storeCheckpointCoveredAofAddress; - clusterProvider.storeWrapper.ObjectStoreCheckpointManager.RecoveredHistoryId = tail.metadata.storePrimaryReplId; - } } // This purge does not check for active readers @@ -85,13 +80,11 @@ public void PurgeAllCheckpointsExceptEntry(CheckpointEntry entry = null) entry ??= GetLatestCheckpointEntryFromDisk(); if (entry == null) return; logger?.LogCheckpointEntry(LogLevel.Trace, nameof(PurgeAllCheckpointsExceptEntry), entry); - PurgeAllCheckpointsExceptTokens(StoreType.Main, entry.metadata.storeHlogToken, entry.metadata.storeIndexToken); - if (!clusterProvider.serverOptions.DisableObjects) - PurgeAllCheckpointsExceptTokens(StoreType.Object, entry.metadata.objectStoreHlogToken, entry.metadata.objectStoreIndexToken); + PurgeAllCheckpointsExceptTokens(entry.metadata.storeHlogToken, entry.metadata.storeIndexToken); - void PurgeAllCheckpointsExceptTokens(StoreType storeType, Guid logToken, Guid indexToken) + void PurgeAllCheckpointsExceptTokens(Guid logToken, Guid indexToken) { - var ckptManager = clusterProvider.GetReplicationLogCheckpointManager(storeType); + var ckptManager = clusterProvider.GetReplicationLogCheckpointManager(); // Delete log checkpoints foreach (var toDeletelogToken in ckptManager.GetLogCheckpointTokens()) @@ -129,7 +122,6 @@ public void AddCheckpointEntry(CheckpointEntry entry, bool fullCheckpoint = fals { var lastEntry = tail ?? throw new GarnetException($"Checkpoint history unavailable, need full checkpoint for {entry}"); entry.metadata.storeIndexToken = lastEntry.metadata.storeIndexToken; - entry.metadata.objectStoreIndexToken = lastEntry.metadata.objectStoreIndexToken; } _ = ValidateCheckpointEntry(entry); @@ -156,9 +148,6 @@ bool ValidateCheckpointEntry(CheckpointEntry entry) if (!clusterProvider.replicationManager.TryAcquireSettledMetadataForMainStore(entry, out _, out _)) throw new GarnetException("Failed to validate main store metadata at insertion"); - if (!clusterProvider.serverOptions.DisableObjects && !clusterProvider.replicationManager.TryAcquireSettledMetadataForObjectStore(entry, out _, out _)) - throw new GarnetException("Failed to validate object store metadata at insertion"); - return true; } catch (Exception ex) @@ -191,22 +180,11 @@ private void DeleteOutdatedCheckpoints() // Below check each checkpoint token separately if it is eligible for deletion if (!CanDeleteToken(curr, CheckpointFileType.STORE_HLOG)) break; - clusterProvider.GetReplicationLogCheckpointManager(StoreType.Main).DeleteLogCheckpoint(curr.metadata.storeHlogToken); + clusterProvider.GetReplicationLogCheckpointManager().DeleteLogCheckpoint(curr.metadata.storeHlogToken); if (!CanDeleteToken(curr, CheckpointFileType.STORE_INDEX)) break; - clusterProvider.GetReplicationLogCheckpointManager(StoreType.Main).DeleteIndexCheckpoint(curr.metadata.storeIndexToken); - - if (!clusterProvider.serverOptions.DisableObjects) - { - if (!CanDeleteToken(curr, CheckpointFileType.OBJ_STORE_HLOG)) - break; - clusterProvider.GetReplicationLogCheckpointManager(StoreType.Object).DeleteLogCheckpoint(curr.metadata.objectStoreHlogToken); - - if (!CanDeleteToken(curr, CheckpointFileType.OBJ_STORE_INDEX)) - break; - clusterProvider.GetReplicationLogCheckpointManager(StoreType.Object).DeleteIndexCheckpoint(curr.metadata.objectStoreIndexToken); - } + clusterProvider.GetReplicationLogCheckpointManager().DeleteIndexCheckpoint(curr.metadata.storeIndexToken); logger?.LogCheckpointEntry(LogLevel.Warning, "Deleting outdated checkpoint", curr); @@ -262,7 +240,6 @@ public bool TryGetLatestCheckpointEntryFromMemory(out CheckpointEntry cEntry) metadata = new() { storeCheckpointCoveredAofAddress = 0, - objectCheckpointCoveredAofAddress = clusterProvider.serverOptions.DisableObjects ? long.MaxValue : 0 } }; _ = cEntry.TryAddReader(); @@ -282,13 +259,8 @@ public bool TryGetLatestCheckpointEntryFromMemory(out CheckpointEntry cEntry) /// public CheckpointEntry GetLatestCheckpointEntryFromDisk() { - Guid objectStoreHLogToken = default; - Guid objectStoreIndexToken = default; - var objectStoreVersion = -1L; storeWrapper.store.GetLatestCheckpointTokens(out var storeHLogToken, out var storeIndexToken, out var storeVersion); - storeWrapper.objectStore?.GetLatestCheckpointTokens(out objectStoreHLogToken, out objectStoreIndexToken, out objectStoreVersion); - var (storeCheckpointCoveredAofAddress, storePrimaryReplId) = GetCheckpointCookieMetadata(StoreType.Main, storeHLogToken); - var (objectCheckpointCoveredAofAddress, objectStorePrimaryReplId) = objectStoreHLogToken == default ? (long.MaxValue, null) : GetCheckpointCookieMetadata(StoreType.Object, objectStoreHLogToken); + var (storeCheckpointCoveredAofAddress, storePrimaryReplId) = GetCheckpointCookieMetadata(storeHLogToken); CheckpointEntry entry = new() { @@ -299,21 +271,15 @@ public CheckpointEntry GetLatestCheckpointEntryFromDisk() storeIndexToken = storeIndexToken, storeCheckpointCoveredAofAddress = storeCheckpointCoveredAofAddress, storePrimaryReplId = storePrimaryReplId, - - objectStoreVersion = objectStoreVersion, - objectStoreHlogToken = objectStoreHLogToken, - objectStoreIndexToken = objectStoreIndexToken, - objectCheckpointCoveredAofAddress = objectCheckpointCoveredAofAddress, - objectStorePrimaryReplId = objectStorePrimaryReplId, } }; return entry; - (long RecoveredSafeAofAddress, string RecoveredReplicationId) GetCheckpointCookieMetadata(StoreType storeType, Guid fileToken) + (long RecoveredSafeAofAddress, string RecoveredReplicationId) GetCheckpointCookieMetadata(Guid fileToken) { if (fileToken == default) return (0, null); - var ckptManager = clusterProvider.GetReplicationLogCheckpointManager(storeType); - var pageSizeBits = storeType == StoreType.Main ? clusterProvider.serverOptions.PageSizeBits() : clusterProvider.serverOptions.ObjectStorePageSizeBits(); + var ckptManager = clusterProvider.GetReplicationLogCheckpointManager(); + var pageSizeBits = clusterProvider.serverOptions.PageSizeBits(); using (var deltaFileDevice = ckptManager.GetDeltaLogDevice(fileToken)) { if (deltaFileDevice is not null) diff --git a/libs/cluster/Server/Replication/GarnetClusterCheckpointManager.cs b/libs/cluster/Server/Replication/GarnetClusterCheckpointManager.cs index 3bf7ec5b5fe..36829e9abb1 100644 --- a/libs/cluster/Server/Replication/GarnetClusterCheckpointManager.cs +++ b/libs/cluster/Server/Replication/GarnetClusterCheckpointManager.cs @@ -58,10 +58,6 @@ public IDevice GetDevice(CheckpointFileType retStateType, Guid fileToken) CheckpointFileType.STORE_DLOG => GetDeltaLogDevice(fileToken), CheckpointFileType.STORE_INDEX => GetIndexDevice(fileToken), CheckpointFileType.STORE_SNAPSHOT => GetSnapshotLogDevice(fileToken), - CheckpointFileType.OBJ_STORE_DLOG => GetDeltaLogDevice(fileToken), - CheckpointFileType.OBJ_STORE_INDEX => GetIndexDevice(fileToken), - CheckpointFileType.OBJ_STORE_SNAPSHOT => GetSnapshotLogDevice(fileToken), - CheckpointFileType.OBJ_STORE_SNAPSHOT_OBJ => GetSnapshotObjectLogDevice(fileToken), _ => throw new Exception($"RetrieveCheckpointFile: unexpected state{retStateType}") }; return device; diff --git a/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicaSyncSession.cs b/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicaSyncSession.cs index c31f1b4b2bd..69df96fffca 100644 --- a/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicaSyncSession.cs +++ b/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicaSyncSession.cs @@ -30,8 +30,6 @@ internal sealed partial class ReplicaSyncSession public long currentStoreVersion; - public long currentObjectStoreVersion; - /// /// Pessimistic checkpoint covered AOF address /// @@ -192,7 +190,6 @@ public bool NeedToFullSync() var localPrimaryReplId = clusterProvider.replicationManager.PrimaryReplId; var sameHistory = localPrimaryReplId.Equals(replicaSyncMetadata.currentPrimaryReplId, StringComparison.Ordinal); var sendMainStore = !sameHistory || replicaSyncMetadata.currentStoreVersion != currentStoreVersion; - var sendObjectStore = !sameHistory || replicaSyncMetadata.currentObjectStoreVersion != currentObjectStoreVersion; var aofBeginAddress = clusterProvider.storeWrapper.appendOnlyFile.BeginAddress; var aofTailAddress = clusterProvider.storeWrapper.appendOnlyFile.TailAddress; @@ -202,11 +199,10 @@ public bool NeedToFullSync() // We need to stream checkpoint if any of the following conditions are met: // 1. Replica has different history than primary - // 2. Replica has different main store version than primary - // 3. Replica has different object store version than primary - // 4. Replica has truncated AOF - // 5. The AOF to be replayed in case of a partial sync is larger than the specified threshold - fullSync = sendMainStore || sendObjectStore || outOfRangeAof || aofTooLarge; + // 2. Replica has different store version than primary + // 3. Replica has truncated AOF + // 4. The AOF to be replayed in case of a partial sync is larger than the specified threshold + fullSync = sendMainStore || outOfRangeAof || aofTooLarge; return fullSync; } @@ -227,7 +223,6 @@ public async Task BeginAofSync() originNodeId: clusterProvider.clusterManager.CurrentConfig.LocalNodeId, currentPrimaryReplId: clusterProvider.replicationManager.PrimaryReplId, currentStoreVersion: currentStoreVersion, - currentObjectStoreVersion: currentObjectStoreVersion, currentAofBeginAddress: currentAofBeginAddress, currentAofTailAddress: currentAofTailAddress, currentReplicationOffset: clusterProvider.replicationManager.ReplicationOffset, diff --git a/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicationSnapshotIterator.cs b/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicationSnapshotIterator.cs index fa063530b5e..41f0e712887 100644 --- a/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicationSnapshotIterator.cs +++ b/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicationSnapshotIterator.cs @@ -13,12 +13,10 @@ namespace Garnet.cluster internal sealed unsafe class SnapshotIteratorManager { public readonly ReplicationSyncManager replicationSyncManager; - public readonly TimeSpan timeout; public readonly CancellationToken cancellationToken; public readonly ILogger logger; - public MainStoreSnapshotIterator mainStoreSnapshotIterator; - public ObjectStoreSnapshotIterator objectStoreSnapshotIterator; + public StoreSnapshotIterator StoreSnapshotIterator; // For serialization from LogRecord to DiskLogRecord SpanByteAndMemory serializationOutput; @@ -50,9 +48,7 @@ public SnapshotIteratorManager(ReplicationSyncManager replicationSyncManager, Ca sessions[i].checkpointCoveredAofAddress = CheckpointCoveredAddress; } - mainStoreSnapshotIterator = new MainStoreSnapshotIterator(this); - if (!replicationSyncManager.ClusterProvider.serverOptions.DisableObjects) - objectStoreSnapshotIterator = new ObjectStoreSnapshotIterator(this); + StoreSnapshotIterator = new StoreSnapshotIterator(this); memoryPool = MemoryPool.Shared; valueObjectSerializer = new(customCommandManager: default); @@ -76,7 +72,7 @@ public bool IsProgressing() } } - public bool OnStart(Guid checkpointToken, long currentVersion, long targetVersion, bool isMainStore) + public bool OnStart(Guid checkpointToken, long currentVersion, long targetVersion) { if (cancellationToken.IsCancellationRequested) { @@ -92,14 +88,11 @@ public bool OnStart(Guid checkpointToken, long currentVersion, long targetVersio if (!replicationSyncManager.IsActive(i)) continue; sessions[i].InitializeIterationBuffer(); - if (isMainStore) - sessions[i].currentStoreVersion = targetVersion; - else - sessions[i].currentObjectStoreVersion = targetVersion; + sessions[i].currentStoreVersion = targetVersion; } - logger?.LogTrace("{OnStart} {store} {token} {currentVersion} {targetVersion}", - nameof(OnStart), isMainStore ? "MAIN STORE" : "OBJECT STORE", checkpointToken, currentVersion, targetVersion); + logger?.LogTrace("{OnStart} {token} {currentVersion} {targetVersion}", + nameof(OnStart), checkpointToken, currentVersion, targetVersion); return true; } @@ -229,16 +222,15 @@ public void OnStop(bool completed, long numberOfRecords, bool isMainStore, long } } - internal sealed unsafe class MainStoreSnapshotIterator(SnapshotIteratorManager snapshotIteratorManager) : + internal sealed unsafe class StoreSnapshotIterator(SnapshotIteratorManager snapshotIteratorManager) : IStreamingSnapshotIteratorFunctions { - readonly SnapshotIteratorManager snapshotIteratorManager = snapshotIteratorManager; long targetVersion; public bool OnStart(Guid checkpointToken, long currentVersion, long targetVersion) { this.targetVersion = targetVersion; - return snapshotIteratorManager.OnStart(checkpointToken, currentVersion, targetVersion, isMainStore: true); + return snapshotIteratorManager.OnStart(checkpointToken, currentVersion, targetVersion); } public bool Reader(in TSourceLogRecord srcLogRecord, RecordMetadata recordMetadata, long numberOfRecords) @@ -246,32 +238,9 @@ public bool Reader(in TSourceLogRecord srcLogRecord, RecordMet => snapshotIteratorManager.StringReader(in srcLogRecord, recordMetadata, numberOfRecords); public void OnException(Exception exception, long numberOfRecords) - => snapshotIteratorManager.logger?.LogError(exception, $"{nameof(MainStoreSnapshotIterator)}"); + => snapshotIteratorManager.logger?.LogError(exception, $"{nameof(StoreSnapshotIterator)}"); public void OnStop(bool completed, long numberOfRecords) => snapshotIteratorManager.OnStop(completed, numberOfRecords, isMainStore: true, targetVersion); } - - internal sealed unsafe class ObjectStoreSnapshotIterator(SnapshotIteratorManager snapshotIteratorManager) : - IStreamingSnapshotIteratorFunctions - { - readonly SnapshotIteratorManager snapshotIteratorManager = snapshotIteratorManager; - long targetVersion; - - public bool OnStart(Guid checkpointToken, long currentVersion, long targetVersion) - { - this.targetVersion = targetVersion; - return snapshotIteratorManager.OnStart(checkpointToken, currentVersion, targetVersion, isMainStore: false); - } - - public bool Reader(in TSourceLogRecord srcLogRecord, RecordMetadata recordMetadata, long numberOfRecords) - where TSourceLogRecord : ISourceLogRecord - => snapshotIteratorManager.ObjectReader(in srcLogRecord, recordMetadata, numberOfRecords); - - public void OnException(Exception exception, long numberOfRecords) - => snapshotIteratorManager.logger?.LogError(exception, $"{nameof(ObjectStoreSnapshotIterator)}"); - - public void OnStop(bool completed, long numberOfRecords) - => snapshotIteratorManager.OnStop(completed, numberOfRecords, isMainStore: false, targetVersion); - } } \ No newline at end of file diff --git a/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicationSyncManager.cs b/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicationSyncManager.cs index e57e0f37b33..f04db715eaa 100644 --- a/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicationSyncManager.cs +++ b/libs/cluster/Server/Replication/PrimaryOps/DisklessReplication/ReplicationSyncManager.cs @@ -155,8 +155,6 @@ public async Task ReplicationSyncDriver(ReplicaSyncSession repli async Task MainStreamingSnapshotDriver() { // Parameters for sync operation - var disableObjects = ClusterProvider.serverOptions.DisableObjects; - try { // Lock to avoid the addition of new replica sync sessions while sync is in progress @@ -242,7 +240,6 @@ async Task PrepareForSync() // Set store version to operate on Sessions[i].currentStoreVersion = ClusterProvider.storeWrapper.store.CurrentVersion; - Sessions[i].currentObjectStoreVersion = disableObjects ? -1 : ClusterProvider.storeWrapper.objectStore.CurrentVersion; // If checkpoint is not needed mark this sync session as complete // to avoid waiting for other replicas which may need to receive the latest checkpoint @@ -280,47 +277,50 @@ async Task TakeStreamingCheckpoint() // Iterate through main store var mainStoreCheckpointTask = ClusterProvider.storeWrapper.store. - TakeFullCheckpointAsync(CheckpointType.StreamingSnapshot, streamingSnapshotIteratorFunctions: manager.mainStoreSnapshotIterator); + TakeFullCheckpointAsync(CheckpointType.StreamingSnapshot, cancellationToken: cts.Token, streamingSnapshotIteratorFunctions: manager.StoreSnapshotIterator); var result = await WaitOrDie(checkpointTask: mainStoreCheckpointTask, iteratorManager: manager); if (!result.success) - throw new InvalidOperationException("Main store checkpoint stream failed!"); - - if (!ClusterProvider.serverOptions.DisableObjects) - { - // Iterate through object store - var objectStoreCheckpointTask = ClusterProvider.storeWrapper.objectStore. - TakeFullCheckpointAsync(CheckpointType.StreamingSnapshot, streamingSnapshotIteratorFunctions: manager.objectStoreSnapshotIterator); - result = await WaitOrDie(checkpointTask: objectStoreCheckpointTask, iteratorManager: manager); - if (!result.success) - throw new InvalidOperationException("Object store checkpoint stream failed!"); - } + throw new GarnetException("Main store checkpoint stream failed!"); // Note: We do not truncate the AOF here as this was just a "virtual" checkpoint - + // WaitOrDie is needed here to check if streaming checkpoint is making progress. + // We cannot use a timeout on the cancellationToken because we don't know in total how long the streaming checkpoint will take async ValueTask<(bool success, Guid token)> WaitOrDie(ValueTask<(bool success, Guid token)> checkpointTask, SnapshotIteratorManager iteratorManager) { - var timeout = replicaSyncTimeout; - var delay = TimeSpan.FromSeconds(1); - while (true) + try { - // Check if cancellation requested - cts.Token.ThrowIfCancellationRequested(); + var timeout = replicaSyncTimeout; + var delay = TimeSpan.FromSeconds(1); + while (true) + { + // Check if cancellation requested + cts.Token.ThrowIfCancellationRequested(); - // Wait for stream sync to make some progress - await Task.Delay(delay); + // Wait for stream sync to make some progress + await Task.Delay(delay); - // Check if checkpoint has completed - if (checkpointTask.IsCompleted) - return await checkpointTask; + // Check if checkpoint has completed + if (checkpointTask.IsCompleted) + return await checkpointTask; - // Check if we made some progress - timeout = !manager.IsProgressing() ? timeout.Subtract(delay) : replicaSyncTimeout; + // Check if we made some progress + timeout = !manager.IsProgressing() ? timeout.Subtract(delay) : replicaSyncTimeout; - // Throw timeout equals to zero - if (timeout.TotalSeconds <= 0) - throw new TimeoutException("Streaming snapshot checkpoint timed out"); + // Throw timeout equals to zero + if (timeout.TotalSeconds <= 0) + { + cts.Cancel(); + throw new TimeoutException("Streaming snapshot checkpoint timed out"); + } + } + } + catch (Exception ex) + { + logger?.LogError(ex, "{method} faulted", nameof(WaitOrDie)); + cts.Cancel(); } + return (false, default); } } } diff --git a/libs/cluster/Server/Replication/PrimaryOps/ReplicaSyncSession.cs b/libs/cluster/Server/Replication/PrimaryOps/ReplicaSyncSession.cs index 54ecd50a934..20b64d1e447 100644 --- a/libs/cluster/Server/Replication/PrimaryOps/ReplicaSyncSession.cs +++ b/libs/cluster/Server/Replication/PrimaryOps/ReplicaSyncSession.cs @@ -61,37 +61,25 @@ public bool ValidateMetadata( CheckpointEntry localEntry, out long index_size, out LogFileInfo hlog_size, - out long obj_index_size, - out LogFileInfo obj_hlog_size, - out bool skipLocalMainStoreCheckpoint, - out bool skipLocalObjectStoreCheckpoint) + out bool skipLocalMainStoreCheckpoint) { hlog_size = default; - obj_hlog_size = default; index_size = -1L; - obj_index_size = -1L; // Local and remote checkpoints are of same history if both of the following hold // 1. There is a checkpoint available at remote node // 2. Remote and local checkpoints contain the same PrimaryReplId var sameMainStoreCheckpointHistory = !string.IsNullOrEmpty(replicaCheckpointEntry.metadata.storePrimaryReplId) && replicaCheckpointEntry.metadata.storePrimaryReplId.Equals(localEntry.metadata.storePrimaryReplId); - var sameObjectStoreCheckpointHistory = !string.IsNullOrEmpty(replicaCheckpointEntry.metadata.objectStorePrimaryReplId) && replicaCheckpointEntry.metadata.objectStorePrimaryReplId.Equals(localEntry.metadata.objectStorePrimaryReplId); // We will not send the latest local checkpoint if any of the following hold // 1. Local node does not have any checkpoints // 2. Local checkpoint is of same version and history as the remote checkpoint skipLocalMainStoreCheckpoint = localEntry.metadata.storeHlogToken == default || (sameMainStoreCheckpointHistory && localEntry.metadata.storeVersion == replicaCheckpointEntry.metadata.storeVersion); - skipLocalObjectStoreCheckpoint = clusterProvider.serverOptions.DisableObjects || localEntry.metadata.objectStoreHlogToken == default || (sameObjectStoreCheckpointHistory && localEntry.metadata.objectStoreVersion == replicaCheckpointEntry.metadata.objectStoreVersion); // Acquire metadata for main store // If failed then this checkpoint is not usable because it is corrupted if (!skipLocalMainStoreCheckpoint && !clusterProvider.replicationManager.TryAcquireSettledMetadataForMainStore(localEntry, out hlog_size, out index_size)) return false; - // Acquire metadata for object store - // If failed then this checkpoint is not usable because it is corrupted - if (!skipLocalObjectStoreCheckpoint && !clusterProvider.replicationManager.TryAcquireSettledMetadataForObjectStore(localEntry, out obj_hlog_size, out obj_index_size)) - return false; - return true; } @@ -101,8 +89,7 @@ public bool ValidateMetadata( public async Task SendCheckpoint() { errorMsg = default; - var storeCkptManager = clusterProvider.GetReplicationLogCheckpointManager(StoreType.Main); - var objectStoreCkptManager = clusterProvider.GetReplicationLogCheckpointManager(StoreType.Object); + var storeCkptManager = clusterProvider.GetReplicationLogCheckpointManager(); var current = clusterProvider.clusterManager.CurrentConfig; var (address, port) = current.GetWorkerAddressFromNodeId(replicaNodeId); @@ -126,8 +113,8 @@ public async Task SendCheckpoint() try { - logger?.LogInformation("Replica replicaId:{replicaId} requesting checkpoint replicaStoreVersion:{replicaStoreVersion} replicaObjectStoreVersion:{replicaObjectStoreVersion}", - replicaNodeId, replicaCheckpointEntry.metadata.storeVersion, replicaCheckpointEntry.metadata.objectStoreVersion); + logger?.LogInformation("Replica replicaId:{replicaId} requesting checkpoint replicaStoreVersion:{replicaStoreVersion}", + replicaNodeId, replicaCheckpointEntry.metadata.storeVersion); logger?.LogInformation("Attempting to acquire checkpoint"); (localEntry, aofSyncTaskInfo) = await AcquireCheckpointEntry(); @@ -136,13 +123,10 @@ public async Task SendCheckpoint() gcs.Connect((int)storeWrapper.serverOptions.ReplicaSyncTimeout.TotalMilliseconds); long index_size = -1; - long obj_index_size = -1; var hlog_size = default(LogFileInfo); - var obj_hlog_size = default(LogFileInfo); var skipLocalMainStoreCheckpoint = false; - var skipLocalObjectStoreCheckpoint = false; var retryCount = validateMetadataMaxRetryCount; - while (!ValidateMetadata(localEntry, out index_size, out hlog_size, out obj_index_size, out obj_hlog_size, out skipLocalMainStoreCheckpoint, out skipLocalObjectStoreCheckpoint)) + while (!ValidateMetadata(localEntry, out index_size, out hlog_size, out skipLocalMainStoreCheckpoint)) { logger?.LogError("Failed to validate metadata. Retrying...."); await Task.Yield(); @@ -177,50 +161,10 @@ public async Task SendCheckpoint() await SendCheckpointMetadata(gcs, storeCkptManager, CheckpointFileType.STORE_SNAPSHOT, localEntry.metadata.storeHlogToken); } - if (!skipLocalObjectStoreCheckpoint) - { - logger?.LogInformation("Sending object store checkpoint {version} {objectStoreHlogToken} {objectStoreIndexToken} to replica", localEntry.metadata.objectStoreVersion, localEntry.metadata.objectStoreHlogToken, localEntry.metadata.objectStoreIndexToken); - - // 1. send hlog file segments - if (clusterProvider.serverOptions.EnableStorageTier && obj_hlog_size.hybridLogFileEndAddress > 24) - { - //send object hlog file segments - await SendFileSegments(gcs, localEntry.metadata.objectStoreHlogToken, CheckpointFileType.OBJ_STORE_HLOG, obj_hlog_size.hybridLogFileStartAddress, obj_hlog_size.hybridLogFileEndAddress); - - var hlogSegmentCount = ((obj_hlog_size.hybridLogFileEndAddress - obj_hlog_size.hybridLogFileStartAddress) >> clusterProvider.serverOptions.ObjectStoreSegmentSizeBits()) + 1; - await SendObjectFiles(gcs, localEntry.metadata.objectStoreHlogToken, CheckpointFileType.OBJ_STORE_HLOG_OBJ, (int)hlogSegmentCount); - } - - // 2. Send object store snapshot files - if (obj_hlog_size.snapshotFileEndAddress > 24) - { - //send snapshot file segments - await SendFileSegments(gcs, localEntry.metadata.objectStoreHlogToken, CheckpointFileType.OBJ_STORE_SNAPSHOT, 0, obj_hlog_size.snapshotFileEndAddress); - - //send snapshot.obj file segments - var snapshotSegmentCount = (obj_hlog_size.snapshotFileEndAddress >> clusterProvider.serverOptions.ObjectStoreSegmentSizeBits()) + 1; - await SendObjectFiles(gcs, localEntry.metadata.objectStoreHlogToken, CheckpointFileType.OBJ_STORE_SNAPSHOT_OBJ, (int)snapshotSegmentCount); - } - - // 3. Send object store index file segments - if (obj_index_size > 0) - await SendFileSegments(gcs, localEntry.metadata.objectStoreIndexToken, CheckpointFileType.OBJ_STORE_INDEX, 0, obj_index_size); - - // 4. Send object store delta file segments - var obj_dlog_size = obj_hlog_size.deltaLogTailAddress; - if (obj_dlog_size > 0) - await SendFileSegments(gcs, localEntry.metadata.objectStoreHlogToken, CheckpointFileType.OBJ_STORE_DLOG, 0, obj_dlog_size); - - // 5. Send object store index metadata - await SendCheckpointMetadata(gcs, objectStoreCkptManager, CheckpointFileType.OBJ_STORE_INDEX, localEntry.metadata.objectStoreIndexToken); - - // 6. Send object store snapshot metadata - await SendCheckpointMetadata(gcs, objectStoreCkptManager, CheckpointFileType.OBJ_STORE_SNAPSHOT, localEntry.metadata.objectStoreHlogToken); - } #endregion #region startAofSync - var recoverFromRemote = !skipLocalMainStoreCheckpoint || !skipLocalObjectStoreCheckpoint; + var recoverFromRemote = !skipLocalMainStoreCheckpoint; var replayAOF = false; var checkpointAofBeginAddress = localEntry.GetMinAofCoveredAddress(); var beginAddress = checkpointAofBeginAddress; @@ -266,8 +210,7 @@ public async Task SendCheckpoint() } var sameMainStoreCheckpointHistory = !string.IsNullOrEmpty(replicaCheckpointEntry.metadata.storePrimaryReplId) && replicaCheckpointEntry.metadata.storePrimaryReplId.Equals(localEntry.metadata.storePrimaryReplId); - var sameObjectStoreCheckpointHistory = !string.IsNullOrEmpty(replicaCheckpointEntry.metadata.objectStorePrimaryReplId) && replicaCheckpointEntry.metadata.objectStorePrimaryReplId.Equals(localEntry.metadata.objectStorePrimaryReplId); - if (!sameMainStoreCheckpointHistory || !sameObjectStoreCheckpointHistory) + if (!sameMainStoreCheckpointHistory) { // If we are not in the same checkpoint history, we need to stream the AOF from the primary's beginning address checkpointAofBeginAddress = beginAddress; @@ -281,7 +224,6 @@ public async Task SendCheckpoint() // Make replica replayAOF if needed and replay from provided beginAddress to RecoveredReplication Address var resp = await gcs.ExecuteBeginReplicaRecover( !skipLocalMainStoreCheckpoint, - !skipLocalObjectStoreCheckpoint, replayAOF, clusterProvider.replicationManager.PrimaryReplId, localEntry.ToByteArray(), @@ -395,7 +337,7 @@ public async Task SendCheckpoint() // If there is possible AOF data loss and we need to take an on-demand checkpoint, // then we should take the checkpoint before we register the sync task, because // TryAddReplicationTask is guaranteed to return true in this scenario. - var validMetadata = ValidateMetadata(cEntry, out _, out _, out _, out _, out _, out _); + var validMetadata = ValidateMetadata(cEntry, out _, out _, out _); if (clusterProvider.serverOptions.OnDemandCheckpoint && (startAofAddress < clusterProvider.replicationManager.AofTruncatedUntil || !validMetadata)) { @@ -443,11 +385,9 @@ private async Task SendCheckpointMetadata(GarnetClientSession gcs, GarnetCluster switch (fileType) { case CheckpointFileType.STORE_SNAPSHOT: - case CheckpointFileType.OBJ_STORE_SNAPSHOT: checkpointMetadata = ckptManager.GetLogCheckpointMetadata(fileToken, null, true, -1); break; case CheckpointFileType.STORE_INDEX: - case CheckpointFileType.OBJ_STORE_INDEX: checkpointMetadata = ckptManager.GetIndexCheckpointMetadata(fileToken); break; } diff --git a/libs/cluster/Server/Replication/ReplicaOps/ReplicaDisklessSync.cs b/libs/cluster/Server/Replication/ReplicaOps/ReplicaDisklessSync.cs index 01b577e5027..b5c50462e6e 100644 --- a/libs/cluster/Server/Replication/ReplicaOps/ReplicaDisklessSync.cs +++ b/libs/cluster/Server/Replication/ReplicaOps/ReplicaDisklessSync.cs @@ -4,9 +4,11 @@ using System; using System.Net; using System.Text; +using System.Threading; using System.Threading.Tasks; using Garnet.client; using Garnet.cluster.Server.Replication; +using Garnet.common; using Microsoft.Extensions.Logging; namespace Garnet.cluster @@ -56,8 +58,8 @@ public bool TryReplicateDisklessSync( async Task TryBeginReplicaSync(bool downgradeLock) { var disklessSync = clusterProvider.serverOptions.ReplicaDisklessSync; - var disableObjects = clusterProvider.serverOptions.DisableObjects; GarnetClientSession gcs = null; + resetHandler ??= new CancellationTokenSource(); try { if (!clusterProvider.serverOptions.EnableFastCommit) @@ -113,13 +115,17 @@ async Task TryBeginReplicaSync(bool downgradeLock) originNodeId: current.LocalNodeId, currentPrimaryReplId: PrimaryReplId, currentStoreVersion: storeWrapper.store.CurrentVersion, - currentObjectStoreVersion: disableObjects ? -1 : storeWrapper.objectStore.CurrentVersion, currentAofBeginAddress: storeWrapper.appendOnlyFile.BeginAddress, currentAofTailAddress: storeWrapper.appendOnlyFile.TailAddress, currentReplicationOffset: ReplicationOffset, checkpointEntry: checkpointEntry); - var resp = await gcs.ExecuteAttachSync(syncMetadata.ToByteArray()).ConfigureAwait(false); + using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(ctsRepManager.Token, resetHandler.Token); + + // Exception injection point for testing cluster reset during diskless replication + await ExceptionInjectionHelper.WaitOnSet(ExceptionInjectionType.Replication_InProgress_During_Diskless_Replica_Attach_Sync).WaitAsync(storeWrapper.serverOptions.ReplicaAttachTimeout, linkedCts.Token).ConfigureAwait(false); + + var resp = await gcs.ExecuteAttachSync(syncMetadata.ToByteArray()).WaitAsync(storeWrapper.serverOptions.ReplicaAttachTimeout, linkedCts.Token).ConfigureAwait(false); } catch (Exception ex) { @@ -144,6 +150,11 @@ async Task TryBeginReplicaSync(bool downgradeLock) } gcs?.Dispose(); recvCheckpointHandler?.Dispose(); + if (!resetHandler.TryReset()) + { + resetHandler.Dispose(); + resetHandler = new CancellationTokenSource(); + } } return null; } @@ -171,8 +182,6 @@ public long ReplicaRecoverDiskless(SyncMetadata primarySyncMetadata, out ReadOnl // Set DB version storeWrapper.store.SetVersion(primarySyncMetadata.currentStoreVersion); - if (!clusterProvider.serverOptions.DisableObjects) - storeWrapper.objectStore.SetVersion(primarySyncMetadata.currentObjectStoreVersion); // Update replicationId to mark any subsequent checkpoints as part of this history logger?.LogInformation("Updating ReplicationId"); diff --git a/libs/cluster/Server/Replication/ReplicaOps/ReplicaReceiveCheckpoint.cs b/libs/cluster/Server/Replication/ReplicaOps/ReplicaReceiveCheckpoint.cs index 36ef01d4520..3bb0d31dd72 100644 --- a/libs/cluster/Server/Replication/ReplicaOps/ReplicaReceiveCheckpoint.cs +++ b/libs/cluster/Server/Replication/ReplicaOps/ReplicaReceiveCheckpoint.cs @@ -6,10 +6,11 @@ using System.IO; using System.Net; using System.Text; +using System.Threading; using System.Threading.Tasks; using Garnet.client; using Garnet.cluster.Server.Replication; -using Garnet.server; +using Garnet.common; using Microsoft.Extensions.Logging; using Tsavorite.core; @@ -72,6 +73,7 @@ async Task ReplicaSyncAttachTask(bool downgradeLock) { Debug.Assert(IsRecovering); GarnetClientSession gcs = null; + resetHandler ??= new CancellationTokenSource(); try { // Immediately try to connect to a primary, so we FAIL @@ -139,12 +141,16 @@ async Task ReplicaSyncAttachTask(bool downgradeLock) // 4. Replica responds with aofStartAddress sync // 5. Primary will initiate aof sync task // 6. Primary releases checkpoint + using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(ctsRepManager.Token, resetHandler.Token); + + // Exception injection point for testing cluster reset during disk-based replication + await ExceptionInjectionHelper.WaitOnSet(ExceptionInjectionType.Replication_InProgress_During_DiskBased_Replica_Attach_Sync).WaitAsync(storeWrapper.serverOptions.ReplicaAttachTimeout, linkedCts.Token).ConfigureAwait(false); var resp = await gcs.ExecuteReplicaSync( nodeId, PrimaryReplId, cEntry.ToByteArray(), storeWrapper.appendOnlyFile.BeginAddress, - storeWrapper.appendOnlyFile.TailAddress).WaitAsync(storeWrapper.serverOptions.ReplicaAttachTimeout, ctsRepManager.Token).ConfigureAwait(false); + storeWrapper.appendOnlyFile.TailAddress).WaitAsync(storeWrapper.serverOptions.ReplicaAttachTimeout, linkedCts.Token).ConfigureAwait(false); } catch (Exception ex) { @@ -167,6 +173,11 @@ async Task ReplicaSyncAttachTask(bool downgradeLock) } recvCheckpointHandler?.Dispose(); gcs?.Dispose(); + if (!resetHandler.TryReset()) + { + resetHandler.Dispose(); + resetHandler = new CancellationTokenSource(); + } } return null; } @@ -185,20 +196,16 @@ public void ProcessCheckpointMetadata(Guid fileToken, CheckpointFileType fileTyp var ckptManager = fileType switch { CheckpointFileType.STORE_SNAPSHOT or - CheckpointFileType.STORE_INDEX => clusterProvider.GetReplicationLogCheckpointManager(StoreType.Main), - CheckpointFileType.OBJ_STORE_SNAPSHOT or - CheckpointFileType.OBJ_STORE_INDEX => clusterProvider.GetReplicationLogCheckpointManager(StoreType.Object), + CheckpointFileType.STORE_INDEX => clusterProvider.GetReplicationLogCheckpointManager(), _ => throw new Exception($"Invalid checkpoint filetype {fileType}"), }; switch (fileType) { case CheckpointFileType.STORE_SNAPSHOT: - case CheckpointFileType.OBJ_STORE_SNAPSHOT: ckptManager.CommitLogCheckpointSendFromPrimary(fileToken, checkpointMetadata); break; case CheckpointFileType.STORE_INDEX: - case CheckpointFileType.OBJ_STORE_INDEX: ckptManager.CommitIndexCheckpoint(fileToken, checkpointMetadata); break; default: @@ -217,9 +224,7 @@ public static bool ShouldInitialize(CheckpointFileType type) return type switch { CheckpointFileType.STORE_HLOG or - CheckpointFileType.STORE_SNAPSHOT or - CheckpointFileType.OBJ_STORE_HLOG or - CheckpointFileType.OBJ_STORE_SNAPSHOT + CheckpointFileType.STORE_SNAPSHOT => true, _ => false, }; @@ -236,9 +241,7 @@ public IDevice GetInitializedSegmentFileDevice(Guid token, CheckpointFileType ty var device = type switch { CheckpointFileType.STORE_HLOG => GetStoreHLogDevice(), - CheckpointFileType.OBJ_STORE_HLOG => GetObjectStoreHLogDevice(false),//TODO: return device for object store hlog - CheckpointFileType.OBJ_STORE_HLOG_OBJ => GetObjectStoreHLogDevice(true), - _ => clusterProvider.GetReplicationLogCheckpointManager(type.ToStoreType()).GetDevice(type, token), + _ => clusterProvider.GetReplicationLogCheckpointManager().GetDevice(type, token), }; if (ShouldInitialize(type)) @@ -257,35 +260,21 @@ IDevice GetStoreHLogDevice() } return null; } - - IDevice GetObjectStoreHLogDevice(bool obj) - { - var opts = clusterProvider.serverOptions; - if (opts.EnableStorageTier) - { - var LogDir = opts.LogDir; - if (LogDir is null or "") LogDir = Directory.GetCurrentDirectory(); - var logFactory = opts.GetInitializedDeviceFactory(LogDir); - return obj ? logFactory.Get(new FileDescriptor("ObjectStore", "hlog.obj")) : logFactory.Get(new FileDescriptor("ObjectStore", "hlog")); - } - return null; - } } /// /// Process request from primary to start recovery process from the retrieved checkpoint. /// /// - /// /// /// /// /// /// + /// /// public long BeginReplicaRecover( bool recoverMainStoreFromToken, - bool recoverObjectStoreFromToken, bool replayAOF, string primaryReplicationId, CheckpointEntry remoteCheckpoint, @@ -298,19 +287,14 @@ public long BeginReplicaRecover( errorMessage = []; UpdateLastPrimarySyncTime(); - logger?.LogInformation("Replica Recover MainStore: {storeVersion}>[{sIndexToken} {sHlogToken}]" + - "\nObjectStore: {objectStoreVersion}>[{oIndexToken} {oHlogToken}]", + logger?.LogInformation("Replica Recover Store: {storeVersion}>[{sIndexToken} {sHlogToken}]", remoteCheckpoint.metadata.storeVersion, remoteCheckpoint.metadata.storeIndexToken, - remoteCheckpoint.metadata.storeHlogToken, - remoteCheckpoint.metadata.objectStoreVersion, - remoteCheckpoint.metadata.objectStoreIndexToken, - remoteCheckpoint.metadata.objectStoreHlogToken); + remoteCheckpoint.metadata.storeHlogToken); storeWrapper.RecoverCheckpoint( replicaRecover: true, recoverMainStoreFromToken, - recoverObjectStoreFromToken, remoteCheckpoint.metadata); if (replayAOF) @@ -333,12 +317,6 @@ public long BeginReplicaRecover( cEntry.metadata.storeHlogToken = remoteCheckpoint.metadata.storeHlogToken; } - // If checkpoint for object store was send add its token here in preparation for purge later on - if (recoverObjectStoreFromToken) - { - cEntry.metadata.objectStoreIndexToken = remoteCheckpoint.metadata.objectStoreIndexToken; - cEntry.metadata.objectStoreHlogToken = remoteCheckpoint.metadata.objectStoreHlogToken; - } checkpointStore.PurgeAllCheckpointsExceptEntry(cEntry); // Initialize in-memory checkpoint store and delete outdated checkpoint entries diff --git a/libs/cluster/Server/Replication/ReplicationCheckpointManagement.cs b/libs/cluster/Server/Replication/ReplicationCheckpointManagement.cs index e22db3ad2b1..1bfceec27ef 100644 --- a/libs/cluster/Server/Replication/ReplicationCheckpointManagement.cs +++ b/libs/cluster/Server/Replication/ReplicationCheckpointManagement.cs @@ -45,29 +45,6 @@ public bool TryAcquireSettledMetadataForMainStore(CheckpointEntry entry, out Log } } - /// - /// Keep trying to acquire object store metadata until it settles - /// - /// CheckpointEntry to retrieve metadata for - /// LogFileInfo to return - /// Index size in bytes to return - public bool TryAcquireSettledMetadataForObjectStore(CheckpointEntry entry, out LogFileInfo hlog_size, out long index_size) - { - hlog_size = default; - index_size = -1; - try - { - hlog_size = storeWrapper.objectStore.GetLogFileSize(entry.metadata.objectStoreHlogToken); - index_size = storeWrapper.objectStore.GetIndexFileSize(entry.metadata.objectStoreIndexToken); - return true; - } - catch (Exception ex) - { - logger?.LogError(ex, "Waiting for object store metadata to settle"); - return false; - } - } - /// /// Add new checkpoint entry to the in-memory store /// @@ -90,10 +67,8 @@ public string GetLatestCheckpointFromDiskInfo() #endregion public long StoreCurrentSafeAofAddress => clusterProvider.storeWrapper.StoreCheckpointManager.CurrentSafeAofAddress; - public long ObjectStoreCurrentSafeAofAddress => clusterProvider.serverOptions.DisableObjects ? -1 : clusterProvider.storeWrapper.ObjectStoreCheckpointManager.CurrentSafeAofAddress; public long StoreRecoveredSafeAofTailAddress => clusterProvider.storeWrapper.StoreCheckpointManager.RecoveredSafeAofAddress; - public long ObjectStoreRecoveredSafeAofTailAddress => clusterProvider.serverOptions.DisableObjects ? -1 : clusterProvider.storeWrapper.ObjectStoreCheckpointManager.RecoveredSafeAofAddress; /// /// Update current aof address for pending commit. @@ -103,8 +78,6 @@ public string GetLatestCheckpointFromDiskInfo() public void UpdateCommitSafeAofAddress(long safeAofTailAddress) { clusterProvider.storeWrapper.StoreCheckpointManager.CurrentSafeAofAddress = safeAofTailAddress; - if (!clusterProvider.serverOptions.DisableObjects) - clusterProvider.storeWrapper.ObjectStoreCheckpointManager.CurrentSafeAofAddress = safeAofTailAddress; } /// @@ -114,8 +87,6 @@ public void UpdateCommitSafeAofAddress(long safeAofTailAddress) public void SetPrimaryReplicationId() { clusterProvider.storeWrapper.StoreCheckpointManager.CurrentHistoryId = PrimaryReplId; - if (!clusterProvider.serverOptions.DisableObjects) - clusterProvider.storeWrapper.ObjectStoreCheckpointManager.CurrentHistoryId = PrimaryReplId; } } } \ No newline at end of file diff --git a/libs/cluster/Server/Replication/ReplicationManager.cs b/libs/cluster/Server/Replication/ReplicationManager.cs index 7eecb80878e..a840ce00a8e 100644 --- a/libs/cluster/Server/Replication/ReplicationManager.cs +++ b/libs/cluster/Server/Replication/ReplicationManager.cs @@ -25,6 +25,7 @@ internal sealed partial class ReplicationManager : IDisposable readonly CheckpointStore checkpointStore; readonly ReplicationSyncManager replicationSyncManager; readonly CancellationTokenSource ctsRepManager = new(); + CancellationTokenSource resetHandler = new(); readonly int pageSizeBits; @@ -86,29 +87,14 @@ public long ReplicationOffset2 public RecoveryStatus currentRecoveryStatus; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public GarnetClusterCheckpointManager GetCkptManager(StoreType storeType) - { - return storeType switch - { - StoreType.Main => (GarnetClusterCheckpointManager)storeWrapper.store.CheckpointManager, - StoreType.Object => (GarnetClusterCheckpointManager)storeWrapper.objectStore?.CheckpointManager, - _ => throw new Exception($"GetCkptManager: unexpected state {storeType}") - }; - } + public GarnetClusterCheckpointManager GetCkptManager() + => (GarnetClusterCheckpointManager)storeWrapper.store.CheckpointManager; public long GetRecoveredSafeAofAddress() - { - var storeAofAddress = clusterProvider.replicationManager.GetCkptManager(StoreType.Main).RecoveredSafeAofAddress; - var objectStoreAofAddress = clusterProvider.serverOptions.DisableObjects ? long.MaxValue : clusterProvider.replicationManager.GetCkptManager(StoreType.Object).RecoveredSafeAofAddress; - return Math.Min(storeAofAddress, objectStoreAofAddress); - } + => clusterProvider.replicationManager.GetCkptManager().RecoveredSafeAofAddress; public long GetCurrentSafeAofAddress() - { - var storeAofAddress = clusterProvider.replicationManager.GetCkptManager(StoreType.Main).CurrentSafeAofAddress; - var objectStoreAofAddress = clusterProvider.serverOptions.DisableObjects ? long.MaxValue : clusterProvider.replicationManager.GetCkptManager(StoreType.Object).CurrentSafeAofAddress; - return Math.Min(storeAofAddress, objectStoreAofAddress); - } + => clusterProvider.replicationManager.GetCkptManager().CurrentSafeAofAddress; public ReplicationManager(ClusterProvider clusterProvider, ILogger logger = null) { @@ -129,13 +115,8 @@ public ReplicationManager(ClusterProvider clusterProvider, ILogger logger = null ReplicationOffset = 0; // Set the appendOnlyFile field for all stores - clusterProvider.GetReplicationLogCheckpointManager(StoreType.Main).checkpointVersionShiftStart = CheckpointVersionShiftStart; - clusterProvider.GetReplicationLogCheckpointManager(StoreType.Main).checkpointVersionShiftEnd = CheckpointVersionShiftEnd; - if (storeWrapper.objectStore != null) - { - clusterProvider.GetReplicationLogCheckpointManager(StoreType.Object).checkpointVersionShiftStart = CheckpointVersionShiftStart; - clusterProvider.GetReplicationLogCheckpointManager(StoreType.Object).checkpointVersionShiftEnd = CheckpointVersionShiftEnd; - } + clusterProvider.GetReplicationLogCheckpointManager().checkpointVersionShiftStart = CheckpointVersionShiftStart; + clusterProvider.GetReplicationLogCheckpointManager().checkpointVersionShiftEnd = CheckpointVersionShiftEnd; // If this node starts as replica, it cannot serve requests until it is connected to primary if (clusterProvider.clusterManager.CurrentConfig.LocalNodeRole == NodeRole.REPLICA && clusterProvider.serverOptions.Recover && !BeginRecovery(RecoveryStatus.InitializeRecover, upgradeLock: false)) @@ -454,6 +435,20 @@ public void EndRecovery(RecoveryStatus nextRecoveryStatus, bool downgradeLock) } } + public void ResetRecovery() + { + switch (currentRecoveryStatus) + { + case RecoveryStatus.ClusterReplicate: + case RecoveryStatus.ClusterFailover: + case RecoveryStatus.ReplicaOfNoOne: + case RecoveryStatus.CheckpointRecoveredAtReplica: + case RecoveryStatus.InitializeRecover: + resetHandler.Cancel(); + break; + } + } + public void Dispose() { _disposed = true; @@ -470,6 +465,8 @@ public void Dispose() replicaReplayTaskCts.Dispose(); ctsRepManager.Cancel(); ctsRepManager.Dispose(); + resetHandler.Cancel(); + resetHandler.Dispose(); aofTaskStore.Dispose(); aofProcessor?.Dispose(); networkPool?.Dispose(); diff --git a/libs/cluster/Server/Replication/SyncMetadata.cs b/libs/cluster/Server/Replication/SyncMetadata.cs index 927f600e123..c85853ecf6d 100644 --- a/libs/cluster/Server/Replication/SyncMetadata.cs +++ b/libs/cluster/Server/Replication/SyncMetadata.cs @@ -26,7 +26,6 @@ public static void LogSyncMetadata(this ILogger log, LogLevel logLevel, string m "originNodeId:{originNodeId}\n" + "currentPrimaryReplId:{currentPrimaryReplId}\n" + "currentStoreVersion:{currentStoreVersion}\n" + - "currentObjectStoreVersion:{currentObjectStoreVersion}\n" + "currentAofBeginAddress:{currentAofBeginAddress}\n" + "currentAofTailAddress:{currentAofTailAddress}\n" + "currentReplicationOffset:{currentReplicationOffset}\n" + @@ -37,7 +36,6 @@ public static void LogSyncMetadata(this ILogger log, LogLevel logLevel, string m syncMetadata.originNodeId, syncMetadata.currentPrimaryReplId, syncMetadata.currentStoreVersion, - syncMetadata.currentObjectStoreVersion, syncMetadata.currentAofBeginAddress, syncMetadata.currentAofTailAddress, syncMetadata.currentReplicationOffset, @@ -63,7 +61,6 @@ public static void LogSyncMetadata(this ILogger log, LogLevel logLevel, string m "originNodeId:{originNodeId}\n" + "currentPrimaryReplId:{currentPrimaryReplId}\n" + "currentStoreVersion:{currentStoreVersion}\n" + - "currentObjectStoreVersion:{currentObjectStoreVersion}\n" + "currentAofBeginAddress:{currentAofBeginAddress}\n" + "currentAofTailAddress:{currentAofTailAddress}\n" + "currentReplicationOffset:{currentReplicationOffset}\n" + @@ -74,7 +71,6 @@ public static void LogSyncMetadata(this ILogger log, LogLevel logLevel, string m "recoverOriginNodeId:{originNodeId}\n" + "recoverCurrentPrimaryReplId:{currentPrimaryReplId}\n" + "recoverCurrentStoreVersion:{currentStoreVersion}\n" + - "recoverCurrentObjectStoreVersion:{currentObjectStoreVersion}\n" + "recoverCurrentAofBeginAddress:{currentAofBeginAddress}\n" + "recoverCurrentAofTailAddress:{currentAofTailAddress}\n" + "recoverCurrentReplicationOffset:{currentReplicationOffset}\n" + @@ -85,7 +81,6 @@ public static void LogSyncMetadata(this ILogger log, LogLevel logLevel, string m origin.originNodeId, origin.currentPrimaryReplId, origin.currentStoreVersion, - origin.currentObjectStoreVersion, origin.currentAofBeginAddress, origin.currentAofTailAddress, origin.currentReplicationOffset, @@ -95,7 +90,6 @@ public static void LogSyncMetadata(this ILogger log, LogLevel logLevel, string m local.originNodeId, local.currentPrimaryReplId, local.currentStoreVersion, - local.currentObjectStoreVersion, local.currentAofBeginAddress, local.currentAofTailAddress, local.currentReplicationOffset, @@ -109,7 +103,6 @@ internal sealed class SyncMetadata( string originNodeId, string currentPrimaryReplId, long currentStoreVersion, - long currentObjectStoreVersion, long currentAofBeginAddress, long currentAofTailAddress, long currentReplicationOffset, @@ -120,7 +113,6 @@ internal sealed class SyncMetadata( public readonly string originNodeId = originNodeId; public readonly string currentPrimaryReplId = currentPrimaryReplId; public readonly long currentStoreVersion = currentStoreVersion; - public readonly long currentObjectStoreVersion = currentObjectStoreVersion; public readonly long currentAofBeginAddress = currentAofBeginAddress; public readonly long currentAofTailAddress = currentAofTailAddress; public readonly long currentReplicationOffset = currentReplicationOffset; @@ -137,7 +129,6 @@ public byte[] ToByteArray() writer.Write(currentPrimaryReplId); writer.Write(currentStoreVersion); - writer.Write(currentObjectStoreVersion); writer.Write(currentAofBeginAddress); writer.Write(currentAofTailAddress); @@ -168,7 +159,6 @@ public static SyncMetadata FromByteArray(byte[] serialized) originNodeId: reader.ReadString(), currentPrimaryReplId: reader.ReadString(), currentStoreVersion: reader.ReadInt64(), - currentObjectStoreVersion: reader.ReadInt64(), currentAofBeginAddress: reader.ReadInt64(), currentAofTailAddress: reader.ReadInt64(), currentReplicationOffset: reader.ReadInt64(), diff --git a/libs/cluster/Session/ClusterCommands.cs b/libs/cluster/Session/ClusterCommands.cs index 104e05144b7..c9dd8c4b13c 100644 --- a/libs/cluster/Session/ClusterCommands.cs +++ b/libs/cluster/Session/ClusterCommands.cs @@ -15,7 +15,7 @@ internal sealed unsafe partial class ClusterSession : IClusterSession { ClusterConfig lastSentConfig; - private int CountKeysInSessionStore(int slot) + private int CountKeysInSlot(int slot) { ClusterKeyIterationFunctions.MainStoreCountKeys iterFuncs = new(slot); var cursor = 0L; @@ -23,33 +23,12 @@ private int CountKeysInSessionStore(int slot) return iterFuncs.KeyCount; } - private int CountKeysInObjectStore(int slot) - { - if (!clusterProvider.serverOptions.DisableObjects) - { - ClusterKeyIterationFunctions.ObjectStoreCountKeys iterFuncs = new(slot); - var cursor = 0L; - _ = basicGarnetApi.IterateObjectStore(ref iterFuncs, ref cursor); - return iterFuncs.KeyCount; - } - return 0; - } - - private int CountKeysInSlot(int slot) => CountKeysInSessionStore(slot) + CountKeysInObjectStore(slot); - private List GetKeysInSlot(int slot, int keyCount) { List keys = []; ClusterKeyIterationFunctions.MainStoreGetKeysInSlot mainIterFuncs = new(keys, slot, keyCount); var cursor = 0L; _ = basicGarnetApi.IterateMainStore(ref mainIterFuncs, ref cursor); - - if (!clusterProvider.serverOptions.DisableObjects) - { - ClusterKeyIterationFunctions.ObjectStoreGetKeysInSlot objectIterFuncs = new(keys, slot); - var objectCursor = 0L; - _ = basicGarnetApi.IterateObjectStore(ref objectIterFuncs, ref objectCursor); - } return keys; } diff --git a/libs/cluster/Session/ClusterSession.cs b/libs/cluster/Session/ClusterSession.cs index 642ba634791..ee67c83175b 100644 --- a/libs/cluster/Session/ClusterSession.cs +++ b/libs/cluster/Session/ClusterSession.cs @@ -13,10 +13,13 @@ namespace Garnet.cluster { using BasicGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, BasicContext, + ObjectAllocator>>, + BasicContext, ObjectAllocator>>>; internal sealed unsafe partial class ClusterSession : IClusterSession diff --git a/libs/cluster/Session/RespClusterMigrateCommands.cs b/libs/cluster/Session/RespClusterMigrateCommands.cs index 55a96a0b608..7d1f9b63ce5 100644 --- a/libs/cluster/Session/RespClusterMigrateCommands.cs +++ b/libs/cluster/Session/RespClusterMigrateCommands.cs @@ -13,11 +13,14 @@ namespace Garnet.cluster { using BasicGarnetApi = GarnetApi, - SpanByteAllocator>>, - BasicContext, - ObjectAllocator>>>; + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, + BasicContext, + ObjectAllocator>>, + BasicContext, + ObjectAllocator>>>; internal sealed unsafe partial class ClusterSession : IClusterSession { @@ -107,7 +110,7 @@ void Process(BasicGarnetApi basicGarnetApi, byte[] input, string storeTypeSpan, continue; } - diskLogRecord = DiskLogRecord.Deserialize(recordSpan, valueObjectSerializer: default, transientObjectIdMap: default, storeWrapper.mainStoreFunctions); + diskLogRecord = DiskLogRecord.Deserialize(recordSpan, valueObjectSerializer: default, transientObjectIdMap: default, storeWrapper.storeFunctions); var slot = HashSlotUtils.HashSlot(diskLogRecord.Key); if (!currentConfig.IsImportingSlot(slot)) // Slot is not in importing state { @@ -119,7 +122,7 @@ void Process(BasicGarnetApi basicGarnetApi, byte[] input, string storeTypeSpan, // Set if key replace flag is set or key does not exist var keySlice = PinnedSpanByte.FromPinnedSpan(diskLogRecord.Key); if (replaceOption || !Exists(keySlice)) - _ = basicGarnetApi.SET(in diskLogRecord, StoreType.Main); + _ = basicGarnetApi.SET_Main(in diskLogRecord); diskLogRecord.Dispose(); i++; } @@ -136,7 +139,7 @@ void Process(BasicGarnetApi basicGarnetApi, byte[] input, string storeTypeSpan, var i = 0; TrackImportProgress(keyCount, isMainStore: false, keyCount == 0); var storeWrapper = clusterProvider.storeWrapper; - var transientObjectIdMap = storeWrapper.objectStore.Log.TransientObjectIdMap; + var transientObjectIdMap = storeWrapper.store.Log.TransientObjectIdMap; // Use try/finally instead of "using" because we don't want the boxing that an interface call would entail. Double-Dispose() is OK for DiskLogRecord. DiskLogRecord diskLogRecord = default; @@ -151,7 +154,7 @@ void Process(BasicGarnetApi basicGarnetApi, byte[] input, string storeTypeSpan, if (migrateState > 0) continue; - diskLogRecord = DiskLogRecord.Deserialize(recordSpan, storeWrapper.GarnetObjectSerializer, transientObjectIdMap, storeWrapper.objectStoreFunctions); + diskLogRecord = DiskLogRecord.Deserialize(recordSpan, storeWrapper.GarnetObjectSerializer, transientObjectIdMap, storeWrapper.storeFunctions); var slot = HashSlotUtils.HashSlot(diskLogRecord.Key); if (!currentConfig.IsImportingSlot(slot)) // Slot is not in importing state { @@ -162,7 +165,7 @@ void Process(BasicGarnetApi basicGarnetApi, byte[] input, string storeTypeSpan, // Set if key replace flag is set or key does not exist var keySlice = PinnedSpanByte.FromPinnedSpan(diskLogRecord.Key); if (replaceOption || !Exists(keySlice)) - _ = basicGarnetApi.SET(in diskLogRecord, StoreType.Object); + _ = basicGarnetApi.SET_Object(in diskLogRecord); diskLogRecord.Dispose(); i++; } diff --git a/libs/cluster/Session/RespClusterReplicationCommands.cs b/libs/cluster/Session/RespClusterReplicationCommands.cs index eac6f680ce2..317e55f196a 100644 --- a/libs/cluster/Session/RespClusterReplicationCommands.cs +++ b/libs/cluster/Session/RespClusterReplicationCommands.cs @@ -348,26 +348,25 @@ private bool NetworkClusterBeginReplicaRecover(out bool invalidParameters) invalidParameters = false; // Expecting exactly 7 arguments - if (parseState.Count != 7) + if (parseState.Count != 6) { invalidParameters = true; return true; } if (!parseState.TryGetBool(0, out var recoverMainStoreFromToken) || - !parseState.TryGetBool(1, out var recoverObjectStoreFromToken) || - !parseState.TryGetBool(2, out var replayAOF)) + !parseState.TryGetBool(1, out var replayAOF)) { while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_BOOLEAN, ref dcurr, dend)) SendAndReset(); return true; } - var primaryReplicaId = parseState.GetString(3); - var checkpointEntryBytes = parseState.GetArgSliceByRef(4).ToArray(); + var primaryReplicaId = parseState.GetString(2); + var checkpointEntryBytes = parseState.GetArgSliceByRef(3).ToArray(); - if (!parseState.TryGetLong(5, out var beginAddress) || - !parseState.TryGetLong(6, out var tailAddress)) + if (!parseState.TryGetLong(4, out var beginAddress) || + !parseState.TryGetLong(5, out var tailAddress)) { while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_GENERIC_VALUE_IS_NOT_INTEGER, ref dcurr, dend)) SendAndReset(); @@ -377,7 +376,6 @@ private bool NetworkClusterBeginReplicaRecover(out bool invalidParameters) var entry = CheckpointEntry.FromByteArray(checkpointEntryBytes); var replicationOffset = clusterProvider.replicationManager.BeginReplicaRecover( recoverMainStoreFromToken, - recoverObjectStoreFromToken, replayAOF, primaryReplicaId, entry, @@ -479,8 +477,8 @@ private bool NetworkClusterSync(out bool invalidParameters) if (!RespReadUtils.GetSerializedRecordSpan(out var recordSpan, ref payloadPtr, payloadEndPtr)) return false; - diskLogRecord = DiskLogRecord.Deserialize(recordSpan, valueObjectSerializer: default, transientObjectIdMap: default, storeWrapper.mainStoreFunctions); - _ = basicGarnetApi.SET(in diskLogRecord, StoreType.Main); + diskLogRecord = DiskLogRecord.Deserialize(recordSpan, valueObjectSerializer: default, transientObjectIdMap: default, storeWrapper.storeFunctions); + _ = basicGarnetApi.SET_Main(in diskLogRecord); diskLogRecord.Dispose(); i++; } @@ -494,7 +492,7 @@ private bool NetworkClusterSync(out bool invalidParameters) { TrackImportProgress(recordCount, isMainStore: false, recordCount == 0); var storeWrapper = clusterProvider.storeWrapper; - var transientObjectIdMap = storeWrapper.objectStore.Log.TransientObjectIdMap; + var transientObjectIdMap = storeWrapper.store.Log.TransientObjectIdMap; // Use try/finally instead of "using" because we don't want the boxing that an interface call would entail. Double-Dispose() is OK for DiskLogRecord. DiskLogRecord diskLogRecord = default; @@ -505,8 +503,8 @@ private bool NetworkClusterSync(out bool invalidParameters) if (!RespReadUtils.GetSerializedRecordSpan(out var recordSpan, ref payloadPtr, payloadEndPtr)) return false; - diskLogRecord = DiskLogRecord.Deserialize(recordSpan, storeWrapper.GarnetObjectSerializer, transientObjectIdMap, storeWrapper.objectStoreFunctions); - _ = basicGarnetApi.SET(in diskLogRecord, StoreType.Object); + diskLogRecord = DiskLogRecord.Deserialize(recordSpan, storeWrapper.GarnetObjectSerializer, transientObjectIdMap, storeWrapper.storeFunctions); + _ = basicGarnetApi.SET_Object(in diskLogRecord); diskLogRecord.Dispose(); i++; } diff --git a/libs/cluster/Session/RespClusterSlotManagementCommands.cs b/libs/cluster/Session/RespClusterSlotManagementCommands.cs index 692f37ba8b9..a47b6509833 100644 --- a/libs/cluster/Session/RespClusterSlotManagementCommands.cs +++ b/libs/cluster/Session/RespClusterSlotManagementCommands.cs @@ -288,8 +288,7 @@ private bool NetworkClusterDelKeysInSlot(out bool invalidParameters) var slots = new HashSet { slot }; ClusterManager.DeleteKeysInSlotsFromMainStore(basicGarnetApi, slots); - if (!clusterProvider.serverOptions.DisableObjects) - ClusterManager.DeleteKeysInSlotsFromObjectStore(basicGarnetApi, slots); + ClusterManager.DeleteKeysInSlotsFromObjectStore(basicGarnetApi, slots); while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) SendAndReset(); @@ -325,8 +324,7 @@ private bool NetworkClusterDelKeysInSlotRange(out bool invalidParameters) } ClusterManager.DeleteKeysInSlotsFromMainStore(basicGarnetApi, slots); - if (!clusterProvider.serverOptions.DisableObjects) - ClusterManager.DeleteKeysInSlotsFromObjectStore(basicGarnetApi, slots); + ClusterManager.DeleteKeysInSlotsFromObjectStore(basicGarnetApi, slots); while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) SendAndReset(); diff --git a/libs/cluster/Session/SlotVerification/ClusterSlotVerify.cs b/libs/cluster/Session/SlotVerification/ClusterSlotVerify.cs index bcb34cd55f1..4d5afeb0c18 100644 --- a/libs/cluster/Session/SlotVerification/ClusterSlotVerify.cs +++ b/libs/cluster/Session/SlotVerification/ClusterSlotVerify.cs @@ -14,7 +14,7 @@ internal sealed unsafe partial class ClusterSession : IClusterSession { [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool Exists(PinnedSpanByte keySlice) - => basicGarnetApi.EXISTS(keySlice, StoreType.All) == GarnetStatus.OK; + => basicGarnetApi.EXISTS(keySlice) == GarnetStatus.OK; private ClusterSlotVerificationResult SingleKeySlotVerify(ref ClusterConfig config, ref PinnedSpanByte keySlice, bool readOnly, byte SessionAsking, int slot = -1) { diff --git a/libs/common/ExceptionInjectionType.cs b/libs/common/ExceptionInjectionType.cs index dd122b0cbfd..8dc62305fd3 100644 --- a/libs/common/ExceptionInjectionType.cs +++ b/libs/common/ExceptionInjectionType.cs @@ -53,5 +53,13 @@ public enum ExceptionInjectionType /// Delay response on receive checkpoint to trigger timeout /// Replication_Timeout_On_Receive_Checkpoint, + /// + /// Replication InProgress during disk-based replica attach sync operation + /// + Replication_InProgress_During_DiskBased_Replica_Attach_Sync, + /// + /// Replication InProgress during diskless replica attach sync operation + /// + Replication_InProgress_During_Diskless_Replica_Attach_Sync, } } \ No newline at end of file diff --git a/libs/common/Metrics/InfoMetricsType.cs b/libs/common/Metrics/InfoMetricsType.cs index e309b4cd9b9..2e68f6798a7 100644 --- a/libs/common/Metrics/InfoMetricsType.cs +++ b/libs/common/Metrics/InfoMetricsType.cs @@ -39,26 +39,14 @@ public enum InfoMetricsType : byte /// STORE, /// - /// Object store info - /// - OBJECTSTORE, - /// /// Store hash table info /// STOREHASHTABLE, /// - /// Object store hash table info - /// - OBJECTSTOREHASHTABLE, - /// /// Store revivification info /// STOREREVIV, /// - /// Object store hash table info - /// - OBJECTSTOREREVIV, - /// /// Persistence information /// PERSISTENCE, diff --git a/libs/common/RespMemoryWriter.cs b/libs/common/RespMemoryWriter.cs index 8d6ee383374..ebb864855c1 100644 --- a/libs/common/RespMemoryWriter.cs +++ b/libs/common/RespMemoryWriter.cs @@ -378,6 +378,17 @@ public void WriteSimpleString(ReadOnlySpan simpleString) ReallocateOutput(simpleString.Length); } + /// + /// Write simple string to memory. + /// + /// An ASCII encoded simple string. The string mustn't contain a CR (\r) or LF (\n) bytes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSimpleString(ReadOnlySpan simpleString) + { + while (!RespWriteUtils.TryWriteSimpleString(simpleString, ref curr, end)) + ReallocateOutput(simpleString.Length); + } + /// /// Write RESP3 true /// diff --git a/libs/host/Configuration/Options.cs b/libs/host/Configuration/Options.cs index b7826d8f79c..d1db2b6fba2 100644 --- a/libs/host/Configuration/Options.cs +++ b/libs/host/Configuration/Options.cs @@ -90,48 +90,12 @@ internal sealed class Options : ICloneable public string ReadCachePageSize { get; set; } [MemorySizeValidation(false)] - [Option("obj-heap-memory", Required = false, HelpText = "Object store heap memory size in bytes (Sum of size taken up by all object instances in the heap)")] - public string ObjectStoreHeapMemorySize { get; set; } - - [MemorySizeValidation] - [Option("obj-log-memory", Required = false, HelpText = "Object store log memory used in bytes (Size of only the log with references to heap objects, excludes size of heap memory consumed by the objects themselves referred to from the log)")] - public string ObjectStoreLogMemorySize { get; set; } - - [MemorySizeValidation] - [Option("obj-page", Required = false, HelpText = "Size of each object store page in bytes (rounds down to power of 2)")] - public string ObjectStorePageSize { get; set; } - - [MemorySizeValidation] - [Option("obj-segment", Required = false, HelpText = "Size of each object store log segment in bytes on disk (rounds down to power of 2)")] - public string ObjectStoreSegmentSize { get; set; } - - [MemorySizeValidation] - [Option("obj-index", Required = false, HelpText = "Start size of object store hash index in bytes (rounds down to power of 2)")] - public string ObjectStoreIndexSize { get; set; } - - [MemorySizeValidation(false)] - [Option("obj-index-max-size", Required = false, HelpText = "Max size of object store hash index in bytes (rounds down to power of 2)")] - public string ObjectStoreIndexMaxSize { get; set; } - - [PercentageValidation] - [Option("obj-mutable-percent", Required = false, HelpText = "Percentage of object store log memory that is kept mutable")] - public int ObjectStoreMutablePercent { get; set; } - - [OptionValidation] - [Option("obj-readcache", Required = false, HelpText = "Enables object store read cache for faster access to on-disk records.")] - public bool? EnableObjectStoreReadCache { get; set; } - - [MemorySizeValidation] - [Option("obj-readcache-log-memory", Required = false, HelpText = "Total object store read cache log memory used in bytes (rounds down to power of 2)")] - public string ObjectStoreReadCacheLogMemorySize { get; set; } - - [MemorySizeValidation] - [Option("obj-readcache-page", Required = false, HelpText = "Size of each object store read cache page in bytes (rounds down to power of 2)")] - public string ObjectStoreReadCachePageSize { get; set; } + [Option("heap-memory", Required = false, HelpText = "Heap memory size in bytes (Sum of size taken up by all object instances in the heap)")] + public string HeapMemorySize { get; set; } [MemorySizeValidation(false)] - [Option("obj-readcache-heap-memory", Required = false, HelpText = "Object store read cache heap memory size in bytes (Sum of size taken up by all object instances in the heap)")] - public string ObjectStoreReadCacheHeapMemorySize { get; set; } + [Option("readcache-heap-memory", Required = false, HelpText = "Read cache heap memory size in bytes (Sum of size taken up by all object instances in the heap)")] + public string ReadCacheHeapMemorySize { get; set; } [OptionValidation] [Option("storage-tier", Required = false, HelpText = "Enable tiering of records (hybrid log) to storage, to support a larger-than-memory store. Use --logdir to specify storage directory.")] @@ -141,10 +105,6 @@ internal sealed class Options : ICloneable [Option("copy-reads-to-tail", Required = false, HelpText = "When records are read from the main store's in-memory immutable region or storage device, copy them to the tail of the log.")] public bool? CopyReadsToTail { get; set; } - [OptionValidation] - [Option("obj-copy-reads-to-tail", Required = false, HelpText = "When records are read from the object store's in-memory immutable region or storage device, copy them to the tail of the log.")] - public bool? ObjectStoreCopyReadsToTail { get; set; } - [LogDirValidation(false, false)] [Option('l', "logdir", Required = false, HelpText = "Storage directory for tiered records (hybrid log), if storage tiering (--storage) is enabled. Uses current directory if unspecified.")] public string LogDir { get; set; } @@ -169,10 +129,6 @@ internal sealed class Options : ICloneable [Option("pubsub-pagesize", Required = false, HelpText = "Page size of log used for pub/sub (rounds down to power of 2)")] public string PubSubPageSize { get; set; } - [OptionValidation] - [Option("no-obj", Required = false, HelpText = "Disable support for data structure objects.")] - public bool? DisableObjects { get; set; } - [OptionValidation] [Option("cluster", Required = false, HelpText = "Enable cluster.")] public bool? EnableCluster { get; set; } @@ -273,10 +229,6 @@ internal sealed class Options : ICloneable [Option("compaction-max-segments", Required = false, HelpText = "Number of log segments created on disk before compaction triggers.")] public int CompactionMaxSegments { get; set; } - [IntRangeValidation(0, int.MaxValue)] - [Option("obj-compaction-max-segments", Required = false, HelpText = "Number of object store log segments created on disk before compaction triggers.")] - public int ObjectStoreCompactionMaxSegments { get; set; } - [OptionValidation] [Option("lua", Required = false, HelpText = "Enable Lua scripts on server.")] public bool? EnableLua { get; set; } @@ -518,8 +470,7 @@ internal sealed class Options : ICloneable [DoubleRangeValidation(0, 1)] [Option("reviv-fraction", Required = false, - HelpText = "#: Fraction of mutable in-memory log space, from the highest log address down to the read-only region, that is eligible for revivification." + - " Applies to both main and object store.")] + HelpText = "#: Fraction of mutable in-memory log space, from the highest log address down to the read-only region, that is eligible for revivification.")] public double RevivifiableFraction { get; set; } [OptionValidation] @@ -545,15 +496,9 @@ internal sealed class Options : ICloneable [OptionValidation] [Option("reviv-in-chain-only", Required = false, HelpText = "Revivify tombstoned records in tag chains only (do not use free list)." + - " Cannot be used with reviv-bin-record-sizes or reviv-bin-record-counts. Propagates to object store by default.")] + " Cannot be used with reviv-bin-record-sizes or reviv-bin-record-counts.")] public bool? RevivInChainOnly { get; set; } - [IntRangeValidation(0, int.MaxValue)] - [Option("reviv-obj-bin-record-count", Required = false, - HelpText = "Number of records in the single free record bin for the object store. The Object store has only a single bin, unlike the main store." + - " Ignored unless the main store is using the free record list.")] - public int RevivObjBinRecordCount { get; set; } - [IntRangeValidation(0, int.MaxValue)] [Option("object-scan-count-limit", Required = false, HelpText = "Limit of items to return in one iteration of *SCAN command")] public int ObjectScanCountLimit { get; set; } @@ -823,27 +768,16 @@ public GarnetServerOptions GetServerOptions(ILogger logger = null) EnableReadCache = EnableReadCache.GetValueOrDefault(), ReadCacheMemorySize = ReadCacheMemorySize, ReadCachePageSize = ReadCachePageSize, - ObjectStoreHeapMemorySize = ObjectStoreHeapMemorySize, - ObjectStoreLogMemorySize = ObjectStoreLogMemorySize, - ObjectStorePageSize = ObjectStorePageSize, - ObjectStoreSegmentSize = ObjectStoreSegmentSize, - ObjectStoreIndexSize = ObjectStoreIndexSize, - ObjectStoreIndexMaxSize = ObjectStoreIndexMaxSize, - ObjectStoreMutablePercent = ObjectStoreMutablePercent, - EnableObjectStoreReadCache = EnableObjectStoreReadCache.GetValueOrDefault(), - ObjectStoreReadCachePageSize = ObjectStoreReadCachePageSize, - ObjectStoreReadCacheLogMemorySize = ObjectStoreReadCacheLogMemorySize, - ObjectStoreReadCacheHeapMemorySize = ObjectStoreReadCacheHeapMemorySize, + HeapMemorySize = HeapMemorySize, + ReadCacheHeapMemorySize = ReadCacheHeapMemorySize, EnableStorageTier = enableStorageTier, CopyReadsToTail = CopyReadsToTail.GetValueOrDefault(), - ObjectStoreCopyReadsToTail = ObjectStoreCopyReadsToTail.GetValueOrDefault(), LogDir = logDir, CheckpointDir = checkpointDir, Recover = Recover.GetValueOrDefault(), EnableIncrementalSnapshots = EnableIncrementalSnapshots.GetValueOrDefault(), DisablePubSub = DisablePubSub.GetValueOrDefault(), PubSubPageSize = PubSubPageSize, - DisableObjects = DisableObjects.GetValueOrDefault(), EnableCluster = EnableCluster.GetValueOrDefault(), CleanClusterConfig = CleanClusterConfig.GetValueOrDefault(), ParallelMigrateTaskCount = ParallelMigrateTaskCount, @@ -863,7 +797,6 @@ public GarnetServerOptions GetServerOptions(ILogger logger = null) CompactionType = CompactionType, CompactionForceDelete = CompactionForceDelete.GetValueOrDefault(), CompactionMaxSegments = CompactionMaxSegments, - ObjectStoreCompactionMaxSegments = ObjectStoreCompactionMaxSegments, GossipSamplePercent = GossipSamplePercent, GossipDelay = GossipDelay, ClusterTimeout = ClusterTimeout, @@ -920,7 +853,6 @@ public GarnetServerOptions GetServerOptions(ILogger logger = null) RevivBinBestFitScanLimit = RevivBinBestFitScanLimit, RevivNumberOfBinsToSearch = RevivNumberOfBinsToSearch, RevivInChainOnly = RevivInChainOnly.GetValueOrDefault(), - RevivObjBinRecordCount = RevivObjBinRecordCount, EnableDebugCommand = EnableDebugCommand, EnableModuleCommand = EnableModuleCommand, ExtensionBinPaths = FileUtils.ConvertToAbsolutePaths(ExtensionBinPaths), diff --git a/libs/host/GarnetServer.cs b/libs/host/GarnetServer.cs index f8cc704fb89..380f85dd744 100644 --- a/libs/host/GarnetServer.cs +++ b/libs/host/GarnetServer.cs @@ -20,11 +20,8 @@ namespace Garnet { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Implementation Garnet server @@ -49,7 +46,6 @@ static string GetVersion() private IGarnetServer[] servers; private SubscribeBroker subscribeBroker; private KVSettings kvSettings; - private KVSettings objKvSettings; private INamedDeviceFactory logFactory; private MemoryLogger initLogger; private ILogger logger; @@ -264,13 +260,10 @@ private void InitializeServer() var configMemoryLimit = (storeWrapper.store.IndexSize * 64) + storeWrapper.store.Log.MaxMemorySizeBytes + (storeWrapper.store.ReadCache?.MaxMemorySizeBytes ?? 0) + - (storeWrapper.appendOnlyFile?.MaxMemorySizeBytes ?? 0); - if (storeWrapper.objectStore != null) - configMemoryLimit += (storeWrapper.objectStore.IndexSize * 64) + - storeWrapper.objectStore.Log.MaxMemorySizeBytes + - (storeWrapper.objectStore.ReadCache?.MaxMemorySizeBytes ?? 0) + - (storeWrapper.objectStoreSizeTracker?.TargetSize ?? 0) + - (storeWrapper.objectStoreSizeTracker?.ReadCacheTargetSize ?? 0); + (storeWrapper.appendOnlyFile?.MaxMemorySizeBytes ?? 0) + + (storeWrapper.sizeTracker?.TargetSize ?? 0) + + (storeWrapper.sizeTracker?.ReadCacheTargetSize ?? 0); + logger.LogInformation("Total configured memory limit: {configMemoryLimit}", configMemoryLimit); } @@ -300,12 +293,11 @@ private void InitializeServer() private GarnetDatabase CreateDatabase(int dbId, GarnetServerOptions serverOptions, ClusterFactory clusterFactory, CustomCommandManager customCommandManager) { - var store = CreateMainStore(dbId, clusterFactory, out var epoch, out var stateMachineDriver); - var objectStore = CreateObjectStore(dbId, clusterFactory, customCommandManager, epoch, stateMachineDriver, out var objectStoreSizeTracker); + var store = CreateStore(dbId, clusterFactory, customCommandManager, out var epoch, out var stateMachineDriver, out var sizeTracker); var (aofDevice, aof) = CreateAOF(dbId); - return new GarnetDatabase(dbId, store, objectStore, epoch, stateMachineDriver, objectStoreSizeTracker, - aofDevice, aof, serverOptions.AdjustedIndexMaxCacheLines == 0, - serverOptions.AdjustedObjectStoreIndexMaxCacheLines == 0); + + return new GarnetDatabase(dbId, store, epoch, stateMachineDriver, sizeTracker, + aofDevice, aof, serverOptions.AdjustedIndexMaxCacheLines == 0); } private void LoadModules(CustomCommandManager customCommandManager) @@ -331,60 +323,36 @@ private void LoadModules(CustomCommandManager customCommandManager) } } - private TsavoriteKV CreateMainStore(int dbId, IClusterFactory clusterFactory, - out LightEpoch epoch, out StateMachineDriver stateMachineDriver) + private TsavoriteKV CreateStore(int dbId, IClusterFactory clusterFactory, CustomCommandManager customCommandManager, + out LightEpoch epoch, out StateMachineDriver stateMachineDriver, out CacheSizeTracker sizeTracker) { + sizeTracker = null; + epoch = new LightEpoch(); stateMachineDriver = new StateMachineDriver(epoch, loggerFactory?.CreateLogger($"StateMachineDriver")); - kvSettings = opts.GetSettings(loggerFactory, epoch, stateMachineDriver, out logFactory); + kvSettings = opts.GetSettings(loggerFactory, epoch, stateMachineDriver, out logFactory, out var heapMemorySize, out var readCacheHeapMemorySize); // Run checkpoint on its own thread to control p99 kvSettings.ThrottleCheckpointFlushDelayMs = opts.CheckpointThrottleFlushDelayMs; - var baseName = opts.GetMainStoreCheckpointDirectory(dbId); + var baseName = opts.GetStoreCheckpointDirectory(dbId); var defaultNamingScheme = new DefaultCheckpointNamingScheme(baseName); kvSettings.CheckpointManager = opts.EnableCluster ? clusterFactory.CreateCheckpointManager(opts.DeviceFactoryCreator, defaultNamingScheme, isMainStore: true, logger) : new GarnetCheckpointManager(opts.DeviceFactoryCreator, defaultNamingScheme, removeOutdated: true); - return new(kvSettings - , StoreFunctions.Create() - , (allocatorSettings, storeFunctions) => new(allocatorSettings, storeFunctions)); - } - - private TsavoriteKV CreateObjectStore(int dbId, IClusterFactory clusterFactory, CustomCommandManager customCommandManager, - LightEpoch epoch, StateMachineDriver stateMachineDriver, out CacheSizeTracker objectStoreSizeTracker) - { - objectStoreSizeTracker = null; - if (opts.DisableObjects) - return null; - - objKvSettings = opts.GetObjectStoreSettings(loggerFactory, epoch, stateMachineDriver, - out var objHeapMemorySize, out var objReadCacheHeapMemorySize); - - // Run checkpoint on its own thread to control p99 - objKvSettings.ThrottleCheckpointFlushDelayMs = opts.CheckpointThrottleFlushDelayMs; - - var baseName = opts.GetObjectStoreCheckpointDirectory(dbId); - var defaultNamingScheme = new DefaultCheckpointNamingScheme(baseName); - - objKvSettings.CheckpointManager = opts.EnableCluster ? - clusterFactory.CreateCheckpointManager(opts.DeviceFactoryCreator, defaultNamingScheme, isMainStore: false, logger) : - new GarnetCheckpointManager(opts.DeviceFactoryCreator, defaultNamingScheme, removeOutdated: true); - - var objStore = new TsavoriteKV(objKvSettings - , StoreFunctions.Create(new SpanByteComparer(), + var store = new TsavoriteKV(kvSettings + , Tsavorite.core.StoreFunctions.Create(new SpanByteComparer(), () => new GarnetObjectSerializer(customCommandManager)) , (allocatorSettings, storeFunctions) => new(allocatorSettings, storeFunctions)); - if (objHeapMemorySize > 0 || objReadCacheHeapMemorySize > 0) - objectStoreSizeTracker = new CacheSizeTracker(objStore, objKvSettings, objHeapMemorySize, objReadCacheHeapMemorySize, + if (heapMemorySize > 0 || readCacheHeapMemorySize > 0) + sizeTracker = new CacheSizeTracker(store, kvSettings, heapMemorySize, readCacheHeapMemorySize, this.loggerFactory); - return objStore; - + return store; } private (IDevice, TsavoriteLog) CreateAOF(int dbId) @@ -454,11 +422,6 @@ private void InternalDispose() servers[i]?.Dispose(); subscribeBroker?.Dispose(); kvSettings.LogDevice?.Dispose(); - if (!opts.DisableObjects) - { - objKvSettings.LogDevice?.Dispose(); - objKvSettings.ObjectLogDevice?.Dispose(); - } opts.AuthSettings?.Dispose(); if (disposeLoggerFactory) loggerFactory?.Dispose(); diff --git a/libs/host/defaults.conf b/libs/host/defaults.conf index af7be040685..a2ea1cc575d 100644 --- a/libs/host/defaults.conf +++ b/libs/host/defaults.conf @@ -42,48 +42,18 @@ /* Size of each read cache page in bytes (rounds down to power of 2) */ "ReadCachePageSize" : "32m", - /* Object store heap memory size in bytes (Sum of size taken up by all object instances in the heap) */ - "ObjectStoreHeapMemorySize" : "", + /* Heap memory size in bytes (Sum of size taken up by all object instances in the heap) */ + "HeapMemorySize" : "", - /* Object store log memory used in bytes (Size of only the log with references to heap objects, excludes size of heap memory consumed by the objects themselves referred to from the log) */ - "ObjectStoreLogMemorySize" : "32m", - - /* Size of each object store page in bytes (rounds down to power of 2) */ - "ObjectStorePageSize" : "4k", - - /* Size of each object store log segment in bytes on disk (rounds down to power of 2) */ - "ObjectStoreSegmentSize" : "32m", - - /* Start size of object store hash index in bytes (rounds down to power of 2) */ - "ObjectStoreIndexSize" : "16m", - - /* Max size of object store hash index in bytes (rounds down to power of 2) */ - "ObjectStoreIndexMaxSize": "", - - /* Percentage of object store log memory that is kept mutable */ - "ObjectStoreMutablePercent" : 90, - - /* Enables object store read cache for faster access to on-disk records */ - "EnableObjectStoreReadCache" : false, - - /* Total object store read cache log memory used in bytes (rounds down to power of 2) */ - "ObjectStoreReadCacheLogMemorySize" : "32m", - - /* Size of each object store read cache page in bytes (rounds down to power of 2) */ - "ObjectStoreReadCachePageSize" : "1m", - - /* Object store read cache heap memory size in bytes (Sum of size taken up by all object instances in the heap) */ - "ObjectStoreReadCacheHeapMemorySize" : "", + /* Read cache heap memory size in bytes (Sum of size taken up by all object instances in the heap) */ + "ReadCacheHeapMemorySize" : "", /* Enable tiering of records (hybrid log) to storage, to support a larger-than-memory store. Use --logdir to specify storage directory. */ "EnableStorageTier" : false, - /* When records are read from the main store's in-memory immutable region or storage device, copy them to the tail of the log. */ + /* When records are read from the main store''s in-memory immutable region or storage device, copy them to the tail of the log. */ "CopyReadsToTail" : false, - /* When records are read from the object store's in-memory immutable region or storage device, copy them to the tail of the log. */ - "ObjectStoreCopyReadsToTail" : false, - /* Storage directory for tiered records (hybrid log), if storage tiering (--storage) is enabled. Uses current directory if unspecified. */ "LogDir" : null, @@ -102,9 +72,6 @@ /* Page size of log used for pub/sub (rounds down to power of 2) */ "PubSubPageSize" : "4k", - /* Disable support for data structure objects. */ - "DisableObjects" : false, - /* Enable cluster. */ "EnableCluster" : false, @@ -191,9 +158,6 @@ /* Number of log segments created on disk before compaction triggers. */ "CompactionMaxSegments" : 32, - /* Number of object store log segments created on disk before compaction triggers. */ - "ObjectStoreCompactionMaxSegments" : 32, - /* Enable Lua scripts on server. */ "EnableLua" : false, @@ -368,9 +332,6 @@ /* Revivify tombstoned records in tag chains only (do not use free list). Cannot be used with reviv-bin-record-sizes or reviv-bin-record-counts. Propagates to object store by default. */ "RevivInChainOnly" : false, - /* Number of records in the single free record bin for the object store. The Object store has only a single bin, unlike the main store. Ignored unless the main store is using the free record list. */ - "RevivObjBinRecordCount" : 256, - /* Limit of items to return in one iteration of *SCAN command */ "ObjectScanCountLimit" : 1000, diff --git a/libs/resources/RespCommandsDocs.json b/libs/resources/RespCommandsDocs.json index e4d86c25bb6..be77703a3ed 100644 --- a/libs/resources/RespCommandsDocs.json +++ b/libs/resources/RespCommandsDocs.json @@ -41,13 +41,14 @@ { "Command": "ACL_GENPASS", "Name": "ACL|GENPASS", - "Summary": "Generate a pseudorandom secure password to use for ACL users", + "Summary": "Generates a pseudorandom, secure password that can be used to identify ACL users.", "Group": "Server", "Complexity": "O(1)", "Arguments": [ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "BITS", + "DisplayText": "bits", "Type": "Integer", "ArgumentFlags": "Optional" } @@ -127,21 +128,6 @@ } ] }, - { - "Command": "EXPDELSCAN", - "Name": "EXPDELSCAN", - "Summary": "Scans mutable records to delete expired ones.", - "Group": "Generic", - "Complexity": "O(1) for every call. O(N) for a complete iteration. N is the number of records in memory.", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "DBID", - "DisplayText": "dbid", - "Type": "Integer" - } - ] - }, { "Command": "APPEND", "Name": "APPEND", @@ -1383,6 +1369,13 @@ } ] }, + { + "Command": "CLUSTER_FLUSHALL", + "Name": "CLUSTER|FLUSHALL", + "Summary": "Sent by primary to replica to force to FLUSH its database", + "Group": "Cluster", + "Complexity": "O(1)" + }, { "Command": "CLUSTER_FORGET", "Name": "CLUSTER|FORGET", @@ -1925,22 +1918,6 @@ } ] }, - { - "Command": "DELIFEXPIM", - "Name": "DELIFEXPIM", - "Summary": "Deletes a key if it is in memory and expired.", - "Group": "Generic", - "Complexity": "O(1)", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandKeyArgument", - "Name": "KEY", - "DisplayText": "key", - "Type": "Key", - "KeySpecIndex": 0 - } - ] - }, { "Command": "DELIFGREATER", "Name": "DELIFGREATER", @@ -2097,6 +2074,21 @@ } ] }, + { + "Command": "EXPDELSCAN", + "Name": "EXPDELSCAN", + "Summary": "Scans mutable records to delete expired ones.", + "Group": "Generic", + "Complexity": "O(1) for every call. O(N) for a complete iteration. N is the number of records in memory.", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "DBID", + "DisplayText": "dbid", + "Type": "Integer" + } + ] + }, { "Command": "EXPIRE", "Name": "EXPIRE", @@ -2536,7 +2528,7 @@ { "Command": "GEORADIUS", "Name": "GEORADIUS", - "Summary": "Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point", + "Summary": "Queries a geospatial index for members within a distance from a coordinate, optionally stores the result.", "Group": "Geo", "Complexity": "O(N\u002Blog(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.", "DocFlags": "Deprecated", @@ -2545,22 +2537,26 @@ { "TypeDiscriminator": "RespCommandKeyArgument", "Name": "KEY", + "DisplayText": "key", "Type": "Key", "KeySpecIndex": 0 }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "LONGITUDE", + "DisplayText": "longitude", "Type": "Double" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "LATITUDE", + "DisplayText": "latitude", "Type": "Double" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "RADIUS", + "DisplayText": "radius", "Type": "Double" }, { @@ -2571,24 +2567,28 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "M", + "DisplayText": "m", "Type": "PureToken", "Token": "M" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "KM", + "DisplayText": "km", "Type": "PureToken", "Token": "KM" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "FT", + "DisplayText": "ft", "Type": "PureToken", "Token": "FT" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "MI", + "DisplayText": "mi", "Type": "PureToken", "Token": "MI" } @@ -2597,6 +2597,7 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHCOORD", + "DisplayText": "withcoord", "Type": "PureToken", "Token": "WITHCOORD", "ArgumentFlags": "Optional" @@ -2604,6 +2605,7 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHDIST", + "DisplayText": "withdist", "Type": "PureToken", "Token": "WITHDIST", "ArgumentFlags": "Optional" @@ -2611,25 +2613,28 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHHASH", + "DisplayText": "withhash", "Type": "PureToken", "Token": "WITHHASH", "ArgumentFlags": "Optional" }, { "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "COUNT", + "Name": "COUNT-BLOCK", "Type": "Block", "ArgumentFlags": "Optional", "Arguments": [ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "COUNT", + "DisplayText": "count", "Type": "Integer", "Token": "COUNT" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "ANY", + "DisplayText": "any", "Type": "PureToken", "Token": "ANY", "ArgumentFlags": "Optional" @@ -2645,39 +2650,49 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "ASC", + "DisplayText": "asc", "Type": "PureToken", "Token": "ASC" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "DESC", + "DisplayText": "desc", "Type": "PureToken", "Token": "DESC" } ] }, { - "TypeDiscriminator": "RespCommandKeyArgument", - "Name": "KEY", - "Type": "Key", - "Token": "STORE", - "ArgumentFlags": "Optional", - "KeySpecIndex": 1 - }, - { - "TypeDiscriminator": "RespCommandKeyArgument", - "Name": "KEY", - "Type": "Key", - "Token": "STOREDIST", + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "STORE", + "Type": "OneOf", "ArgumentFlags": "Optional", - "KeySpecIndex": 2 + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "STOREKEY", + "DisplayText": "key", + "Type": "Key", + "Token": "STORE", + "KeySpecIndex": 1 + }, + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "STOREDISTKEY", + "DisplayText": "key", + "Type": "Key", + "Token": "STOREDIST", + "KeySpecIndex": 2 + } + ] } ] }, { "Command": "GEORADIUS_RO", "Name": "GEORADIUS_RO", - "Summary": "A read-only variant for GEORADIUS", + "Summary": "Returns members from a geospatial index that are within a distance from a coordinate.", "Group": "Geo", "Complexity": "O(N\u002Blog(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.", "DocFlags": "Deprecated", @@ -2686,22 +2701,26 @@ { "TypeDiscriminator": "RespCommandKeyArgument", "Name": "KEY", + "DisplayText": "key", "Type": "Key", "KeySpecIndex": 0 }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "LONGITUDE", + "DisplayText": "longitude", "Type": "Double" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "LATITUDE", + "DisplayText": "latitude", "Type": "Double" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "RADIUS", + "DisplayText": "radius", "Type": "Double" }, { @@ -2712,24 +2731,28 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "M", + "DisplayText": "m", "Type": "PureToken", "Token": "M" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "KM", + "DisplayText": "km", "Type": "PureToken", "Token": "KM" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "FT", + "DisplayText": "ft", "Type": "PureToken", "Token": "FT" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "MI", + "DisplayText": "mi", "Type": "PureToken", "Token": "MI" } @@ -2738,6 +2761,7 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHCOORD", + "DisplayText": "withcoord", "Type": "PureToken", "Token": "WITHCOORD", "ArgumentFlags": "Optional" @@ -2745,6 +2769,7 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHDIST", + "DisplayText": "withdist", "Type": "PureToken", "Token": "WITHDIST", "ArgumentFlags": "Optional" @@ -2752,25 +2777,28 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHHASH", + "DisplayText": "withhash", "Type": "PureToken", "Token": "WITHHASH", "ArgumentFlags": "Optional" }, { "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "COUNT", + "Name": "COUNT-BLOCK", "Type": "Block", "ArgumentFlags": "Optional", "Arguments": [ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "COUNT", + "DisplayText": "count", "Type": "Integer", "Token": "COUNT" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "ANY", + "DisplayText": "any", "Type": "PureToken", "Token": "ANY", "ArgumentFlags": "Optional" @@ -2786,12 +2814,14 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "ASC", + "DisplayText": "asc", "Type": "PureToken", "Token": "ASC" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "DESC", + "DisplayText": "desc", "Type": "PureToken", "Token": "DESC" } @@ -2802,7 +2832,7 @@ { "Command": "GEORADIUSBYMEMBER", "Name": "GEORADIUSBYMEMBER", - "Summary": "Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member", + "Summary": "Queries a geospatial index for members within a distance from a member, optionally stores the result.", "Group": "Geo", "Complexity": "O(N\u002Blog(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.", "DocFlags": "Deprecated", @@ -2811,17 +2841,20 @@ { "TypeDiscriminator": "RespCommandKeyArgument", "Name": "KEY", + "DisplayText": "key", "Type": "Key", "KeySpecIndex": 0 }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "MEMBER", + "DisplayText": "member", "Type": "String" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "RADIUS", + "DisplayText": "radius", "Type": "Double" }, { @@ -2832,24 +2865,28 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "M", + "DisplayText": "m", "Type": "PureToken", "Token": "M" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "KM", + "DisplayText": "km", "Type": "PureToken", "Token": "KM" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "FT", + "DisplayText": "ft", "Type": "PureToken", "Token": "FT" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "MI", + "DisplayText": "mi", "Type": "PureToken", "Token": "MI" } @@ -2858,6 +2895,7 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHCOORD", + "DisplayText": "withcoord", "Type": "PureToken", "Token": "WITHCOORD", "ArgumentFlags": "Optional" @@ -2865,6 +2903,7 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHDIST", + "DisplayText": "withdist", "Type": "PureToken", "Token": "WITHDIST", "ArgumentFlags": "Optional" @@ -2872,25 +2911,28 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHHASH", + "DisplayText": "withhash", "Type": "PureToken", "Token": "WITHHASH", "ArgumentFlags": "Optional" }, { "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "COUNT", + "Name": "COUNT-BLOCK", "Type": "Block", "ArgumentFlags": "Optional", "Arguments": [ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "COUNT", + "DisplayText": "count", "Type": "Integer", "Token": "COUNT" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "ANY", + "DisplayText": "any", "Type": "PureToken", "Token": "ANY", "ArgumentFlags": "Optional" @@ -2906,39 +2948,49 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "ASC", + "DisplayText": "asc", "Type": "PureToken", "Token": "ASC" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "DESC", + "DisplayText": "desc", "Type": "PureToken", "Token": "DESC" } ] }, { - "TypeDiscriminator": "RespCommandKeyArgument", - "Name": "KEY", - "Type": "Key", - "Token": "STORE", - "ArgumentFlags": "Optional", - "KeySpecIndex": 1 - }, - { - "TypeDiscriminator": "RespCommandKeyArgument", - "Name": "KEY", - "Type": "Key", - "Token": "STOREDIST", + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "STORE", + "Type": "OneOf", "ArgumentFlags": "Optional", - "KeySpecIndex": 2 + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "STOREKEY", + "DisplayText": "key", + "Type": "Key", + "Token": "STORE", + "KeySpecIndex": 1 + }, + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "STOREDISTKEY", + "DisplayText": "key", + "Type": "Key", + "Token": "STOREDIST", + "KeySpecIndex": 2 + } + ] } ] }, { "Command": "GEORADIUSBYMEMBER_RO", "Name": "GEORADIUSBYMEMBER_RO", - "Summary": "A read-only variant for GEORADIUSBYMEMBER", + "Summary": "Returns members from a geospatial index that are within a distance from a member.", "Group": "Geo", "Complexity": "O(N\u002Blog(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.", "DocFlags": "Deprecated", @@ -2947,17 +2999,20 @@ { "TypeDiscriminator": "RespCommandKeyArgument", "Name": "KEY", + "DisplayText": "key", "Type": "Key", "KeySpecIndex": 0 }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "MEMBER", + "DisplayText": "member", "Type": "String" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "RADIUS", + "DisplayText": "radius", "Type": "Double" }, { @@ -2968,24 +3023,28 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "M", + "DisplayText": "m", "Type": "PureToken", "Token": "M" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "KM", + "DisplayText": "km", "Type": "PureToken", "Token": "KM" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "FT", + "DisplayText": "ft", "Type": "PureToken", "Token": "FT" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "MI", + "DisplayText": "mi", "Type": "PureToken", "Token": "MI" } @@ -2994,6 +3053,7 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHCOORD", + "DisplayText": "withcoord", "Type": "PureToken", "Token": "WITHCOORD", "ArgumentFlags": "Optional" @@ -3001,6 +3061,7 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHDIST", + "DisplayText": "withdist", "Type": "PureToken", "Token": "WITHDIST", "ArgumentFlags": "Optional" @@ -3008,25 +3069,28 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "WITHHASH", + "DisplayText": "withhash", "Type": "PureToken", "Token": "WITHHASH", "ArgumentFlags": "Optional" }, { "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "COUNT", + "Name": "COUNT-BLOCK", "Type": "Block", "ArgumentFlags": "Optional", "Arguments": [ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "COUNT", + "DisplayText": "count", "Type": "Integer", "Token": "COUNT" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "ANY", + "DisplayText": "any", "Type": "PureToken", "Token": "ANY", "ArgumentFlags": "Optional" @@ -3042,12 +3106,14 @@ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "ASC", + "DisplayText": "asc", "Type": "PureToken", "Token": "ASC" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "DESC", + "DisplayText": "desc", "Type": "PureToken", "Token": "DESC" } @@ -4536,14 +4602,6 @@ "Type": "Integer", "Token": "COUNT", "ArgumentFlags": "Optional" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "NOVALUES", - "DisplayText": "novalues", - "Type": "PureToken", - "Token": "NOVALUES", - "ArgumentFlags": "Optional" } ] }, @@ -6933,7 +6991,8 @@ "Name": "NOGET", "DisplayText": "noget", "Type": "PureToken", - "Token": "NOGET" + "Token": "NOGET", + "ArgumentFlags": "Optional" } ] }, @@ -6990,7 +7049,8 @@ "Name": "NOGET", "DisplayText": "noget", "Type": "PureToken", - "Token": "NOGET" + "Token": "NOGET", + "ArgumentFlags": "Optional" } ] }, @@ -7562,18 +7622,20 @@ { "Command": "SWAPDB", "Name": "SWAPDB", - "Summary": "Swaps two Memurai databases", + "Summary": "Swaps two Redis databases.", "Group": "Server", "Complexity": "O(N) where N is the count of clients watching or blocking on keys from both databases.", "Arguments": [ { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "INDEX1", + "DisplayText": "index1", "Type": "Integer" }, { "TypeDiscriminator": "RespCommandBasicArgument", "Name": "INDEX2", + "DisplayText": "index2", "Type": "Integer" } ] diff --git a/libs/resources/RespCommandsInfo.json b/libs/resources/RespCommandsInfo.json index f381ba1548f..daa1acd29d3 100644 --- a/libs/resources/RespCommandsInfo.json +++ b/libs/resources/RespCommandsInfo.json @@ -23,6 +23,20 @@ "response_policy:all_succeeded" ] }, + { + "Command": "ACL_GENPASS", + "Name": "ACL|GENPASS", + "Arity": -2, + "Flags": "Loading, NoScript, Stale", + "AclCategories": "Slow" + }, + { + "Command": "ACL_GETUSER", + "Name": "ACL|GETUSER", + "Arity": 3, + "Flags": "Admin, Loading, NoScript, Stale", + "AclCategories": "Admin, Dangerous, Slow" + }, { "Command": "ACL_LIST", "Name": "ACL|LIST", @@ -72,33 +86,9 @@ "Arity": 2, "Flags": "Loading, NoScript, Stale", "AclCategories": "Slow" - }, - { - "Command": "ACL_GETUSER", - "Name": "ACL|GETUSER", - "Arity": 3, - "Flags": "Admin, Loading, NoScript, Stale", - "AclCategories": "Admin, Dangerous, Slow" - }, - { - "Command": "ACL_GENPASS", - "Name": "ACL|GENPASS", - "Arity": -2, - "Flags": "Loading, NoScript, Stale", - "AclCategories": "Slow" } ] }, - { - "Command": "EXPDELSCAN", - "Name": "EXPDELSCAN", - "Arity": -1, - "Flags": "Admin, NoMulti, NoScript, ReadOnly", - "FirstKey": 1, - "LastKey": 1, - "Step": 1, - "AclCategories": "Admin, Garnet" - }, { "Command": "APPEND", "Name": "APPEND", @@ -122,7 +112,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Main" }, { "Command": "ASKING", @@ -175,7 +166,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "BITFIELD", @@ -201,7 +193,8 @@ "Notes": "This command allows both access and modification of the key", "Flags": "RW, Access, Update, VariableFlags" } - ] + ], + "StoreType": "Main" }, { "Command": "BITFIELD_RO", @@ -226,7 +219,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "BITOP", @@ -264,7 +258,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "BITPOS", @@ -289,7 +284,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "BLMOVE", @@ -327,7 +323,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "BLMPOP", @@ -349,7 +346,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "BLPOP", @@ -374,7 +372,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "BRPOP", @@ -399,7 +398,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "BRPOPLPUSH", @@ -437,7 +437,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "BZMPOP", @@ -459,7 +460,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "BZPOPMAX", @@ -484,7 +486,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "BZPOPMIN", @@ -509,7 +512,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "CLIENT", @@ -727,6 +731,14 @@ "Flags": "Admin, NoMulti, NoScript", "AclCategories": "Admin, Dangerous, Slow, Garnet" }, + { + "Command": "CLUSTER_FLUSHALL", + "Name": "CLUSTER|FLUSHALL", + "IsInternal": true, + "Arity": 2, + "Flags": "Admin, NoMulti, NoScript", + "AclCategories": "Admin, Dangerous, Slow, Garnet" + }, { "Command": "CLUSTER_FORGET", "Name": "CLUSTER|FORGET", @@ -966,14 +978,6 @@ "Arity": 4, "Flags": "Admin, NoMulti, NoScript", "AclCategories": "Admin, Dangerous, Slow, Garnet" - }, - { - "Command": "CLUSTER_FLUSHALL", - "Name": "CLUSTER|FLUSHALL", - "IsInternal": true, - "Arity": 2, - "Flags": "Admin, NoMulti, NoScript", - "AclCategories": "Admin, Dangerous, Slow, Garnet" } ] }, @@ -1085,14 +1089,16 @@ "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "Read, Slow, Garnet" + "AclCategories": "Read, Slow, Garnet", + "StoreType": "Object" }, { "Command": "CustomObjCmd", "Name": "CustomObjCmd", "IsInternal": true, "Arity": 1, - "AclCategories": "Dangerous, Slow, Garnet, Custom" + "AclCategories": "Dangerous, Slow, Garnet, Custom", + "StoreType": "Object" }, { "Command": "CustomProcedure", @@ -1106,7 +1112,8 @@ "Name": "CustomRawStringCmd", "IsInternal": true, "Arity": 1, - "AclCategories": "Dangerous, Slow, Garnet, Custom" + "AclCategories": "Dangerous, Slow, Garnet, Custom", + "StoreType": "Main" }, { "Command": "CustomTxn", @@ -1156,7 +1163,8 @@ }, "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "DECRBY", @@ -1181,7 +1189,8 @@ }, "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "DEL", @@ -1210,7 +1219,8 @@ }, "Flags": "RM, Delete" } - ] + ], + "StoreType": "All" }, { "Command": "DELIFGREATER", @@ -1234,7 +1244,8 @@ }, "Flags": "RM, Delete" } - ] + ], + "StoreType": "Main" }, { "Command": "DISCARD", @@ -1269,7 +1280,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "All" }, { "Command": "ECHO", @@ -1357,7 +1369,18 @@ }, "Flags": "RO" } - ] + ], + "StoreType": "All" + }, + { + "Command": "EXPDELSCAN", + "Name": "EXPDELSCAN", + "Arity": -1, + "Flags": "Admin, NoMulti, NoScript, ReadOnly", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Admin, Garnet" }, { "Command": "EXPIRE", @@ -1382,7 +1405,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "All" }, { "Command": "EXPIREAT", @@ -1407,7 +1431,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "All" }, { "Command": "EXPIRETIME", @@ -1432,7 +1457,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "All" }, { "Command": "FAILOVER", @@ -1496,7 +1522,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "GEODIST", @@ -1521,7 +1548,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "GEOHASH", @@ -1546,7 +1574,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "GEOPOS", @@ -1571,7 +1600,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "GEORADIUS", @@ -1624,7 +1654,8 @@ }, "Flags": "OW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "GEORADIUS_RO", @@ -1649,7 +1680,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "GEORADIUSBYMEMBER", @@ -1702,7 +1734,8 @@ }, "Flags": "OW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "GEORADIUSBYMEMBER_RO", @@ -1727,7 +1760,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "GEOSEARCH", @@ -1752,7 +1786,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "GEOSEARCHSTORE", @@ -1790,7 +1825,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "GET", @@ -1815,7 +1851,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "GETBIT", @@ -1840,7 +1877,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "GETDEL", @@ -1865,7 +1903,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Main" }, { "Command": "GETEX", @@ -1891,7 +1930,8 @@ "Notes": "RW and UPDATE because it changes the TTL", "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "GETIFNOTMATCH", @@ -1915,7 +1955,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "GETRANGE", @@ -1940,7 +1981,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "GETSET", @@ -1965,7 +2007,8 @@ }, "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "GETWITHETAG", @@ -1989,7 +2032,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "HCOLLECT", @@ -2014,7 +2058,8 @@ }, "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "HDEL", @@ -2039,7 +2084,8 @@ }, "Flags": "RW, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "HELLO", @@ -2071,7 +2117,8 @@ }, "Flags": "RO" } - ] + ], + "StoreType": "Object" }, { "Command": "HEXPIRE", @@ -2096,7 +2143,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "HEXPIREAT", @@ -2121,7 +2169,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "HEXPIRETIME", @@ -2146,7 +2195,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "HGET", @@ -2171,7 +2221,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "HGETALL", @@ -2199,7 +2250,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "HINCRBY", @@ -2224,7 +2276,8 @@ }, "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "HINCRBYFLOAT", @@ -2249,7 +2302,8 @@ }, "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "HKEYS", @@ -2277,7 +2331,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "HLEN", @@ -2302,7 +2357,8 @@ }, "Flags": "RO" } - ] + ], + "StoreType": "Object" }, { "Command": "HMGET", @@ -2327,7 +2383,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "HMSET", @@ -2352,7 +2409,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "HPERSIST", @@ -2377,7 +2435,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "HPEXPIRE", @@ -2402,7 +2461,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "HPEXPIREAT", @@ -2427,7 +2487,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "HPEXPIRETIME", @@ -2452,7 +2513,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "HPTTL", @@ -2477,7 +2539,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "HRANDFIELD", @@ -2505,7 +2568,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "HSCAN", @@ -2533,7 +2597,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "HSET", @@ -2558,7 +2623,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "HSETNX", @@ -2583,7 +2649,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "HSTRLEN", @@ -2608,7 +2675,8 @@ }, "Flags": "RO" } - ] + ], + "StoreType": "Object" }, { "Command": "HTTL", @@ -2633,7 +2701,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "HVALS", @@ -2661,7 +2730,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "INCR", @@ -2686,7 +2756,8 @@ }, "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "INCRBY", @@ -2711,7 +2782,8 @@ }, "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "INCRBYFLOAT", @@ -2736,7 +2808,8 @@ }, "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "INFO", @@ -2832,7 +2905,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "LINDEX", @@ -2857,7 +2931,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "LINSERT", @@ -2882,7 +2957,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "LLEN", @@ -2907,7 +2983,8 @@ }, "Flags": "RO" } - ] + ], + "StoreType": "Object" }, { "Command": "LMOVE", @@ -2945,7 +3022,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "LMPOP", @@ -2967,7 +3045,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "LPOP", @@ -2992,7 +3071,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "LPOS", @@ -3017,7 +3097,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "LPUSH", @@ -3042,7 +3123,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "LPUSHX", @@ -3067,7 +3149,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "LRANGE", @@ -3092,7 +3175,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "LREM", @@ -3117,7 +3201,8 @@ }, "Flags": "RW, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "LSET", @@ -3142,7 +3227,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "LTRIM", @@ -3167,7 +3253,8 @@ }, "Flags": "RW, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "MEMORY", @@ -3228,7 +3315,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "MIGRATE", @@ -3322,7 +3410,8 @@ }, "Flags": "OW, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "MSETNX", @@ -3347,7 +3436,8 @@ }, "Flags": "OW, Insert" } - ] + ], + "StoreType": "Main" }, { "Command": "MULTI", @@ -3379,7 +3469,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "All" }, { "Command": "PEXPIRE", @@ -3404,7 +3495,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "All" }, { "Command": "PEXPIREAT", @@ -3429,7 +3521,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "All" }, { "Command": "PEXPIRETIME", @@ -3454,7 +3547,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "All" }, { "Command": "PFADD", @@ -3479,7 +3573,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Main" }, { "Command": "PFCOUNT", @@ -3505,7 +3600,8 @@ "Notes": "RW because it may change the internal representation of the key, and propagate to replicas", "Flags": "RW, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "PFMERGE", @@ -3543,7 +3639,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "PING", @@ -3579,7 +3676,8 @@ }, "Flags": "OW, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "PSUBSCRIBE", @@ -3614,7 +3712,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "All" }, { "Command": "PUBLISH", @@ -3736,7 +3835,8 @@ }, "Flags": "OW, Update" } - ] + ], + "StoreType": "All" }, { "Command": "RENAMENX", @@ -3774,7 +3874,8 @@ }, "Flags": "OW, Insert" } - ] + ], + "StoreType": "All" }, { "Command": "REPLICAOF", @@ -3806,7 +3907,8 @@ }, "Flags": "OW, Update" } - ] + ], + "StoreType": "All" }, { "Command": "ROLE", @@ -3838,7 +3940,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "RPOPLPUSH", @@ -3876,7 +3979,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "RPUSH", @@ -3901,7 +4005,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "RPUSHX", @@ -3926,7 +4031,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "RUNTXP", @@ -3961,7 +4067,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "SAVE", @@ -3980,7 +4087,8 @@ "nondeterministic_output", "request_policy:special", "response_policy:special" - ] + ], + "StoreType": "All" }, { "Command": "SCARD", @@ -4005,7 +4113,8 @@ }, "Flags": "RO" } - ] + ], + "StoreType": "Object" }, { "Command": "SCRIPT", @@ -4074,7 +4183,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "SDIFFSTORE", @@ -4112,7 +4222,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "SECONDARYOF", @@ -4152,7 +4263,8 @@ "Notes": "RW and ACCESS due to the optional \u0060GET\u0060 argument", "Flags": "RW, Access, Update, VariableFlags" } - ] + ], + "StoreType": "Main" }, { "Command": "SETBIT", @@ -4177,7 +4289,8 @@ }, "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "SETEX", @@ -4202,7 +4315,8 @@ }, "Flags": "OW, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "SETIFGREATER", @@ -4226,7 +4340,8 @@ }, "Flags": "RW, Access, Update, VariableFlags" } - ] + ], + "StoreType": "Main" }, { "Command": "SETIFMATCH", @@ -4250,7 +4365,8 @@ }, "Flags": "RW, Access, Update, VariableFlags" } - ] + ], + "StoreType": "Main" }, { "Command": "SETNX", @@ -4275,7 +4391,8 @@ }, "Flags": "OW, Insert" } - ] + ], + "StoreType": "Main" }, { "Command": "SETRANGE", @@ -4300,7 +4417,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Main" }, { "Command": "SINTER", @@ -4328,7 +4446,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "SINTERCARD", @@ -4350,7 +4469,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "SINTERSTORE", @@ -4373,7 +4493,7 @@ "KeyStep": 1, "Limit": 0 }, - "Flags": "OW, Update" + "Flags": "RW, Update" }, { "BeginSearch": { @@ -4388,7 +4508,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "SISMEMBER", @@ -4413,7 +4534,8 @@ }, "Flags": "RO" } - ] + ], + "StoreType": "Object" }, { "Command": "SECONDARYOF", @@ -4497,7 +4619,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "SMISMEMBER", @@ -4522,7 +4645,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "SMOVE", @@ -4560,7 +4684,8 @@ }, "Flags": "RW, Insert" } - ] + ], + "StoreType": "Object" }, { "Command": "SPOP", @@ -4588,7 +4713,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "SPUBLISH", @@ -4641,7 +4767,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "SREM", @@ -4666,7 +4793,8 @@ }, "Flags": "RW, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "SSCAN", @@ -4694,7 +4822,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "SSUBSCRIBE", @@ -4744,7 +4873,8 @@ }, "Flags": "RO" } - ] + ], + "StoreType": "Main" }, { "Command": "SUBSCRIBE", @@ -4776,7 +4906,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Main" }, { "Command": "SUNION", @@ -4804,7 +4935,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "SUNIONSTORE", @@ -4842,7 +4974,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "SWAPDB", @@ -4887,7 +5020,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "All" }, { "Command": "TYPE", @@ -4912,7 +5046,8 @@ }, "Flags": "RO" } - ] + ], + "StoreType": "All" }, { "Command": "UNLINK", @@ -4941,7 +5076,8 @@ }, "Flags": "RM, Delete" } - ] + ], + "StoreType": "All" }, { "Command": "UNSUBSCRIBE", @@ -5055,7 +5191,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "ZCARD", @@ -5080,17 +5217,18 @@ }, "Flags": "RO" } - ] + ], + "StoreType": "Object" }, { - "Command": "ZCOUNT", - "Name": "ZCOUNT", - "Arity": 4, - "Flags": "Fast, ReadOnly", + "Command": "ZCOLLECT", + "Name": "ZCOLLECT", + "Arity": 2, + "Flags": "Admin, Write", "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "Fast, Read, SortedSet", + "AclCategories": "Admin, SortedSet, Write, Garnet", "KeySpecifications": [ { "BeginSearch": { @@ -5103,19 +5241,20 @@ "KeyStep": 1, "Limit": 0 }, - "Flags": "RO, Access" + "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Object" }, { - "Command": "ZCOLLECT", - "Name": "ZCOLLECT", - "Arity": 2, - "Flags": "Admin, Write", + "Command": "ZCOUNT", + "Name": "ZCOUNT", + "Arity": 4, + "Flags": "Fast, ReadOnly", "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "SortedSet, Write, Admin, Garnet", + "AclCategories": "Fast, Read, SortedSet", "KeySpecifications": [ { "BeginSearch": { @@ -5128,9 +5267,10 @@ "KeyStep": 1, "Limit": 0 }, - "Flags": "RW, Access, Update" + "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZDIFF", @@ -5152,7 +5292,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZDIFFSTORE", @@ -5190,7 +5331,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZEXPIRE", @@ -5200,7 +5342,7 @@ "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "SortedSet, Fast, Write, Garnet", + "AclCategories": "Fast, SortedSet, Write, Garnet", "KeySpecifications": [ { "BeginSearch": { @@ -5215,7 +5357,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "ZEXPIREAT", @@ -5225,7 +5368,7 @@ "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "SortedSet, Fast, Write, Garnet", + "AclCategories": "Fast, SortedSet, Write, Garnet", "KeySpecifications": [ { "BeginSearch": { @@ -5240,7 +5383,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "ZEXPIRETIME", @@ -5250,7 +5394,7 @@ "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "SortedSet, Fast, Read, Garnet", + "AclCategories": "Fast, Read, SortedSet, Garnet", "KeySpecifications": [ { "BeginSearch": { @@ -5265,7 +5409,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZINCRBY", @@ -5290,7 +5435,8 @@ }, "Flags": "RW, Access, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "ZINTER", @@ -5312,7 +5458,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZINTERCARD", @@ -5334,7 +5481,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZINTERSTORE", @@ -5372,7 +5520,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZLEXCOUNT", @@ -5397,7 +5546,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZMPOP", @@ -5419,7 +5569,8 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "ZMSCORE", @@ -5444,7 +5595,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZPERSIST", @@ -5454,7 +5606,7 @@ "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "SortedSet, Fast, Write, Garnet", + "AclCategories": "Fast, SortedSet, Write, Garnet", "KeySpecifications": [ { "BeginSearch": { @@ -5469,7 +5621,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "ZPEXPIRE", @@ -5479,7 +5632,7 @@ "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "SortedSet, Fast, Write, Garnet", + "AclCategories": "Fast, SortedSet, Write, Garnet", "KeySpecifications": [ { "BeginSearch": { @@ -5494,7 +5647,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "ZPEXPIREAT", @@ -5504,7 +5658,7 @@ "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "SortedSet, Fast, Write, Garnet", + "AclCategories": "Fast, SortedSet, Write, Garnet", "KeySpecifications": [ { "BeginSearch": { @@ -5519,7 +5673,8 @@ }, "Flags": "RW, Update" } - ] + ], + "StoreType": "Object" }, { "Command": "ZPEXPIRETIME", @@ -5529,7 +5684,7 @@ "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "SortedSet, Fast, Read, Garnet", + "AclCategories": "Fast, Read, SortedSet, Garnet", "KeySpecifications": [ { "BeginSearch": { @@ -5544,17 +5699,18 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { - "Command": "ZPTTL", - "Name": "ZPTTL", - "Arity": -5, - "Flags": "Fast, ReadOnly", + "Command": "ZPOPMAX", + "Name": "ZPOPMAX", + "Arity": -2, + "Flags": "Fast, Write", "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "SortedSet, Fast, Read, Garnet", + "AclCategories": "Fast, SortedSet, Write", "KeySpecifications": [ { "BeginSearch": { @@ -5567,13 +5723,14 @@ "KeyStep": 1, "Limit": 0 }, - "Flags": "RO, Access" + "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { - "Command": "ZPOPMAX", - "Name": "ZPOPMAX", + "Command": "ZPOPMIN", + "Name": "ZPOPMIN", "Arity": -2, "Flags": "Fast, Write", "FirstKey": 1, @@ -5594,17 +5751,18 @@ }, "Flags": "RW, Access, Delete" } - ] + ], + "StoreType": "Object" }, { - "Command": "ZPOPMIN", - "Name": "ZPOPMIN", - "Arity": -2, - "Flags": "Fast, Write", + "Command": "ZPTTL", + "Name": "ZPTTL", + "Arity": -5, + "Flags": "Fast, ReadOnly", "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "Fast, SortedSet, Write", + "AclCategories": "Fast, Read, SortedSet, Garnet", "KeySpecifications": [ { "BeginSearch": { @@ -5617,9 +5775,10 @@ "KeyStep": 1, "Limit": 0 }, - "Flags": "RW, Access, Delete" + "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZRANDMEMBER", @@ -5647,7 +5806,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZRANGE", @@ -5672,7 +5832,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZRANGEBYLEX", @@ -5697,7 +5858,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZRANGEBYSCORE", @@ -5722,7 +5884,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZRANGESTORE", @@ -5760,7 +5923,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZRANK", @@ -5785,7 +5949,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZREM", @@ -5810,7 +5975,8 @@ }, "Flags": "RW, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "ZREMRANGEBYLEX", @@ -5835,7 +6001,8 @@ }, "Flags": "RW, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "ZREMRANGEBYRANK", @@ -5860,7 +6027,8 @@ }, "Flags": "RW, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "ZREMRANGEBYSCORE", @@ -5885,7 +6053,8 @@ }, "Flags": "RW, Delete" } - ] + ], + "StoreType": "Object" }, { "Command": "ZREVRANGE", @@ -5910,7 +6079,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZREVRANGEBYLEX", @@ -5935,7 +6105,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZREVRANGEBYSCORE", @@ -5960,7 +6131,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZREVRANK", @@ -5985,7 +6157,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZSCAN", @@ -6013,17 +6186,18 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { - "Command": "ZTTL", - "Name": "ZTTL", - "Arity": -5, + "Command": "ZSCORE", + "Name": "ZSCORE", + "Arity": 3, "Flags": "Fast, ReadOnly", "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "SortedSet, Fast, Read, Garnet", + "AclCategories": "Fast, Read, SortedSet", "KeySpecifications": [ { "BeginSearch": { @@ -6038,17 +6212,18 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { - "Command": "ZSCORE", - "Name": "ZSCORE", - "Arity": 3, + "Command": "ZTTL", + "Name": "ZTTL", + "Arity": -5, "Flags": "Fast, ReadOnly", "FirstKey": 1, "LastKey": 1, "Step": 1, - "AclCategories": "Fast, Read, SortedSet", + "AclCategories": "Fast, Read, SortedSet, Garnet", "KeySpecifications": [ { "BeginSearch": { @@ -6063,7 +6238,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZUNION", @@ -6085,7 +6261,8 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" }, { "Command": "ZUNIONSTORE", @@ -6123,6 +6300,7 @@ }, "Flags": "RO, Access" } - ] + ], + "StoreType": "Object" } ] \ No newline at end of file diff --git a/libs/server/AOF/AofEntryType.cs b/libs/server/AOF/AofEntryType.cs index bccbdd6af07..1a3369166d9 100644 --- a/libs/server/AOF/AofEntryType.cs +++ b/libs/server/AOF/AofEntryType.cs @@ -79,6 +79,23 @@ public enum AofEntryType : byte /// FlushDb = 0x61, + /// + /// Unified store upsert sting + /// + UnifiedStoreStringUpsert = 0x70, + /// + /// Unified store upsert object + /// + UnifiedStoreObjectUpsert = 0x71, + /// + /// Unified store RMW + /// + UnifiedStoreRMW = 0x72, + /// + /// Unified store delete + /// + UnifiedStoreDelete = 0x73, + #region Deprecated markers /// /// Deprecated with unified checkpointing: Checkpoint for object store start diff --git a/libs/server/AOF/AofProcessor.cs b/libs/server/AOF/AofProcessor.cs index 679c83bd51c..73f7468d0ff 100644 --- a/libs/server/AOF/AofProcessor.cs +++ b/libs/server/AOF/AofProcessor.cs @@ -13,11 +13,8 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Wrapper for store and store-specific information @@ -29,6 +26,7 @@ public sealed unsafe partial class AofProcessor private readonly RawStringInput storeInput; private readonly ObjectInput objectStoreInput; + private readonly UnifiedStoreInput unifiedStoreInput; private readonly CustomProcedureInput customProcInput; private readonly SessionParseState parseState; @@ -42,12 +40,17 @@ public sealed unsafe partial class AofProcessor /// /// Session for main store /// - BasicContext basicContext; + BasicContext basicContext; /// /// Session for object store /// - BasicContext objectStoreBasicContext; + BasicContext objectStoreBasicContext; + + /// + /// Session for unified store + /// + BasicContext unifiedStoreBasicContext; readonly Dictionary> inflightTxns; readonly byte[] buffer; @@ -80,6 +83,7 @@ public AofProcessor( parseState.Initialize(); storeInput.parseState = parseState; objectStoreInput.parseState = parseState; + unifiedStoreInput.parseState = parseState; customProcInput.parseState = parseState; inflightTxns = []; @@ -99,6 +103,7 @@ public void Dispose() { dbSession.StorageSession.basicContext.Session?.Dispose(); dbSession.StorageSession.objectStoreBasicContext.Session?.Dispose(); + dbSession.StorageSession.unifiedStoreBasicContext.Session?.Dispose(); } handle.Free(); @@ -295,7 +300,7 @@ public unsafe void ProcessAofRecordInternal(byte* ptr, int length, bool asReplic case AofEntryType.ObjectStoreStreamingCheckpointStartCommit: Debug.Assert(storeWrapper.serverOptions.ReplicaDisklessSync); if (asReplica && header.storeVersion > storeWrapper.store.CurrentVersion) - storeWrapper.objectStore.SetVersion(header.storeVersion); + storeWrapper.store.SetVersion(header.storeVersion); break; case AofEntryType.FlushAll: storeWrapper.FlushAllDatabases(unsafeTruncateLog: header.unsafeTruncateLog == 1); @@ -354,6 +359,18 @@ private unsafe bool ReplayOp(byte* entryPtr, int length, bool replayAsReplica) case AofEntryType.ObjectStoreDelete: ObjectStoreDelete(objectStoreBasicContext, entryPtr); break; + case AofEntryType.UnifiedStoreRMW: + UnifiedStoreRMW(unifiedStoreBasicContext, unifiedStoreInput, entryPtr, bufferPtr, buffer.Length); + break; + case AofEntryType.UnifiedStoreStringUpsert: + UnifiedStoreStringUpsert(unifiedStoreBasicContext, unifiedStoreInput, entryPtr, bufferPtr, buffer.Length); + break; + case AofEntryType.UnifiedStoreObjectUpsert: + UnifiedStoreObjectUpsert(unifiedStoreBasicContext, storeWrapper.GarnetObjectSerializer, entryPtr, bufferPtr, buffer.Length); + break; + case AofEntryType.UnifiedStoreDelete: + UnifiedStoreDelete(unifiedStoreBasicContext, entryPtr); + break; case AofEntryType.StoredProcedure: RunStoredProc(header.procedureId, customProcInput, entryPtr); break; @@ -372,7 +389,7 @@ void RunStoredProc(byte id, CustomProcedureInput customProcInput, byte* ptr) customProcInput.DeserializeFrom(curr); // Run the stored procedure with the reconstructed input - respServerSession.RunTransactionProc(id, ref customProcInput, ref output); + respServerSession.RunTransactionProc(id, ref customProcInput, ref output, isRecovering: true); } } @@ -388,16 +405,14 @@ private void SwitchActiveDatabaseContext(GarnetDatabase db, bool initialSetup = // Switch the storage context to match the session, if necessary if (this.activeDbId != db.Id || initialSetup) { - var session = respServerSession.storageSession.basicContext.Session; - basicContext = session.BasicContext; - var objectStoreSession = respServerSession.storageSession.objectStoreBasicContext.Session; - if (objectStoreSession is not null) - objectStoreBasicContext = objectStoreSession.BasicContext; + basicContext = respServerSession.storageSession.basicContext.Session.BasicContext; + objectStoreBasicContext = respServerSession.storageSession.objectStoreBasicContext.Session.BasicContext; + unifiedStoreBasicContext = respServerSession.storageSession.unifiedStoreBasicContext.Session.BasicContext; this.activeDbId = db.Id; } } - static void StoreUpsert(BasicContext basicContext, + static void StoreUpsert(BasicContext basicContext, RawStringInput storeInput, byte* ptr) { var curr = ptr + sizeof(AofHeader); @@ -417,7 +432,7 @@ static void StoreUpsert(BasicContext basicContext, RawStringInput storeInput, byte* ptr) + static void StoreRMW(BasicContext basicContext, RawStringInput storeInput, byte* ptr) { var curr = ptr + sizeof(AofHeader); var key = PinnedSpanByte.FromLengthPrefixedPinnedPointer(curr); @@ -436,13 +451,13 @@ static void StoreRMW(BasicContext basicContext, byte* ptr) + static void StoreDelete(BasicContext basicContext, byte* ptr) { var key = SpanByte.FromLengthPrefixedPinnedPointer(ptr + sizeof(AofHeader)); basicContext.Delete(key); } - static void ObjectStoreUpsert(BasicContext basicContext, + static void ObjectStoreUpsert(BasicContext basicContext, GarnetObjectSerializer garnetObjectSerializer, byte* ptr, byte* outputPtr, int outputLength) { var key = PinnedSpanByte.FromLengthPrefixedPinnedPointer(ptr + sizeof(AofHeader)); @@ -459,7 +474,7 @@ static void ObjectStoreUpsert(BasicContext basicContext, + static void ObjectStoreRMW(BasicContext basicContext, ObjectInput objectStoreInput, byte* ptr, byte* outputPtr, int outputLength) { var curr = ptr + sizeof(AofHeader); @@ -480,7 +495,73 @@ static void ObjectStoreRMW(BasicContext basicContext, byte* ptr) + static void ObjectStoreDelete(BasicContext basicContext, byte* ptr) + { + var key = SpanByte.FromLengthPrefixedPinnedPointer(ptr + sizeof(AofHeader)); + basicContext.Delete(key); + } + + static void UnifiedStoreStringUpsert(BasicContext basicContext, + UnifiedStoreInput storeInput, byte* ptr, byte* outputPtr, int outputLength) + { + var curr = ptr + sizeof(AofHeader); + var key = PinnedSpanByte.FromLengthPrefixedPinnedPointer(curr); + curr += key.TotalSize; + + var value = PinnedSpanByte.FromLengthPrefixedPinnedPointer(curr); + curr += value.TotalSize; + + // Reconstructing UnifiedStoreInput + + // input + _ = storeInput.DeserializeFrom(curr); + + var output = GarnetUnifiedStoreOutput.FromPinnedPointer(outputPtr, outputLength); + basicContext.Upsert(key.ReadOnlySpan, ref storeInput, value.ReadOnlySpan, ref output); + if (!output.SpanByteAndMemory.IsSpanByte) + output.SpanByteAndMemory.Memory.Dispose(); + } + + static void UnifiedStoreObjectUpsert(BasicContext basicContext, + GarnetObjectSerializer garnetObjectSerializer, byte* ptr, byte* outputPtr, int outputLength) + { + var key = PinnedSpanByte.FromLengthPrefixedPinnedPointer(ptr + sizeof(AofHeader)); + + var value = PinnedSpanByte.FromLengthPrefixedPinnedPointer(ptr + sizeof(AofHeader) + key.TotalSize); + var valB = garnetObjectSerializer.Deserialize(value.ToArray()); + + // input + // TODOMigrate: _ = unifiedStoreInput.DeserializeFrom(curr); // TODO - need to serialize this as well + + var output = GarnetUnifiedStoreOutput.FromPinnedPointer(outputPtr, outputLength); + basicContext.Upsert(key.ReadOnlySpan, valB); + if (!output.SpanByteAndMemory.IsSpanByte) + output.SpanByteAndMemory.Memory.Dispose(); + } + + static void UnifiedStoreRMW(BasicContext basicContext, + UnifiedStoreInput unifiedStoreInput, byte* ptr, byte* outputPtr, int outputLength) + { + var curr = ptr + sizeof(AofHeader); + var key = PinnedSpanByte.FromLengthPrefixedPinnedPointer(curr); + curr += key.TotalSize; + + // Reconstructing UnifiedStoreInput + + // input + _ = unifiedStoreInput.DeserializeFrom(curr); + + // Call RMW with the reconstructed key & UnifiedStoreInput + var output = GarnetUnifiedStoreOutput.FromPinnedPointer(outputPtr, outputLength); + if (basicContext.RMW(key.ReadOnlySpan, ref unifiedStoreInput, ref output).IsPending) + basicContext.CompletePending(true); + + if (!output.SpanByteAndMemory.IsSpanByte) + output.SpanByteAndMemory.Memory.Dispose(); + } + + static void UnifiedStoreDelete( + BasicContext basicContext, byte* ptr) { var key = SpanByte.FromLengthPrefixedPinnedPointer(ptr + sizeof(AofHeader)); basicContext.Delete(key); @@ -513,42 +594,9 @@ bool BufferNewVersionRecord(AofHeader header, byte* entryPtr, int length) } bool IsOldVersionRecord(AofHeader header) - { - var storeType = ToAofStoreType(header.opType); - - return storeType switch - { - AofStoreType.MainStoreType => header.storeVersion < storeWrapper.store.CurrentVersion, - AofStoreType.ObjectStoreType => header.storeVersion < storeWrapper.objectStore.CurrentVersion, - AofStoreType.TxnType => header.storeVersion < storeWrapper.objectStore.CurrentVersion, - _ => throw new GarnetException($"Unexpected AOF header store type {storeType}"), - }; - } + => header.storeVersion < storeWrapper.store.CurrentVersion; bool IsNewVersionRecord(AofHeader header) - { - var storeType = ToAofStoreType(header.opType); - return storeType switch - { - AofStoreType.MainStoreType => header.storeVersion > storeWrapper.store.CurrentVersion, - AofStoreType.ObjectStoreType => header.storeVersion > storeWrapper.objectStore.CurrentVersion, - AofStoreType.TxnType => header.storeVersion > storeWrapper.objectStore.CurrentVersion, - _ => throw new GarnetException($"Unknown AOF header store type {storeType}"), - }; - } - - static AofStoreType ToAofStoreType(AofEntryType type) - { - return type switch - { - AofEntryType.StoreUpsert or AofEntryType.StoreRMW or AofEntryType.StoreDelete => AofStoreType.MainStoreType, - AofEntryType.ObjectStoreUpsert or AofEntryType.ObjectStoreRMW or AofEntryType.ObjectStoreDelete => AofStoreType.ObjectStoreType, - AofEntryType.TxnStart or AofEntryType.TxnCommit or AofEntryType.TxnAbort or AofEntryType.StoredProcedure => AofStoreType.TxnType, - AofEntryType.CheckpointStartCommit or AofEntryType.ObjectStoreCheckpointStartCommit or AofEntryType.MainStoreStreamingCheckpointStartCommit or AofEntryType.ObjectStoreStreamingCheckpointStartCommit => AofStoreType.CheckpointType, - AofEntryType.CheckpointEndCommit or AofEntryType.ObjectStoreCheckpointEndCommit or AofEntryType.MainStoreStreamingCheckpointEndCommit or AofEntryType.ObjectStoreStreamingCheckpointEndCommit => AofStoreType.CheckpointType, - AofEntryType.FlushAll or AofEntryType.FlushDb => AofStoreType.FlushDbType, - _ => throw new GarnetException($"Conversion to AofStoreType not possible for {type}"), - }; - } + => header.storeVersion > storeWrapper.store.CurrentVersion; } } \ No newline at end of file diff --git a/libs/server/API/GarnetApi.cs b/libs/server/API/GarnetApi.cs index 99886b74fd5..617eac186eb 100644 --- a/libs/server/API/GarnetApi.cs +++ b/libs/server/API/GarnetApi.cs @@ -8,30 +8,30 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; // See TransactionManager.cs for aliases BasicGarnetApi and TransactionalGarnetApi /// /// Garnet API implementation /// - public partial struct GarnetApi : IGarnetApi, IGarnetWatchApi - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext + public partial struct GarnetApi : IGarnetApi, IGarnetWatchApi + where TContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext + where TUnifiedContext : ITsavoriteContext { readonly StorageSession storageSession; TContext context; TObjectContext objectContext; + TUnifiedContext unifiedContext; - internal GarnetApi(StorageSession storageSession, TContext context, TObjectContext objectContext) + internal GarnetApi(StorageSession storageSession, TContext context, TObjectContext objectContext, TUnifiedContext unifiedContext) { this.storageSession = storageSession; this.context = context; this.objectContext = objectContext; + this.unifiedContext = unifiedContext; } #region WATCH @@ -87,30 +87,6 @@ public GarnetStatus GETRANGE(PinnedSpanByte key, ref RawStringInput input, ref S => storageSession.GETRANGE(key, ref input, ref output, ref context); #endregion - #region TTL - - /// - public GarnetStatus TTL(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output) - => storageSession.TTL(key, storeType, ref output, ref context, ref objectContext); - - /// - public GarnetStatus PTTL(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output) - => storageSession.TTL(key, storeType, ref output, ref context, ref objectContext, milliseconds: true); - - #endregion - - #region EXPIRETIME - - /// - public GarnetStatus EXPIRETIME(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output) - => storageSession.EXPIRETIME(key, storeType, ref output, ref context, ref objectContext); - - /// - public GarnetStatus PEXPIRETIME(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output) - => storageSession.EXPIRETIME(key, storeType, ref output, ref context, ref objectContext, milliseconds: true); - - #endregion - #region SET /// public GarnetStatus SET(PinnedSpanByte key, PinnedSpanByte value) @@ -141,9 +117,15 @@ public GarnetStatus SET(PinnedSpanByte key, IGarnetObject value) => storageSession.SET(key, value, ref objectContext); /// - public GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType storeType) + public GarnetStatus SET_Main(in TSourceLogRecord srcLogRecord) where TSourceLogRecord : ISourceLogRecord - => storageSession.SET(in srcLogRecord, storeType, ref context, ref objectContext); + => storageSession.SET_Main(in srcLogRecord, ref context); + + /// + public GarnetStatus SET_Object(in TSourceLogRecord srcLogRecord) + where TSourceLogRecord : ISourceLogRecord + => storageSession.SET_Object(in srcLogRecord, ref objectContext); + #endregion #region SETEX @@ -193,44 +175,6 @@ public GarnetStatus RENAMENX(PinnedSpanByte oldKey, PinnedSpanByte newKey, out i => storageSession.RENAMENX(oldKey, newKey, storeType, out result, withEtag); #endregion - #region EXISTS - /// - public GarnetStatus EXISTS(PinnedSpanByte key, StoreType storeType = StoreType.All) - => storageSession.EXISTS(key, storeType, ref context, ref objectContext); - #endregion - - #region EXPIRE - /// - public unsafe GarnetStatus EXPIRE(PinnedSpanByte key, ref RawStringInput input, out bool timeoutSet, StoreType storeType = StoreType.All) - => storageSession.EXPIRE(key, ref input, out timeoutSet, storeType, ref context, ref objectContext); - - /// - public unsafe GarnetStatus EXPIRE(PinnedSpanByte key, PinnedSpanByte expiryMs, out bool timeoutSet, StoreType storeType = StoreType.All, ExpireOption expireOption = ExpireOption.None) - => storageSession.EXPIRE(key, expiryMs, out timeoutSet, storeType, expireOption, ref context, ref objectContext); - - /// - public GarnetStatus EXPIRE(PinnedSpanByte key, TimeSpan expiry, out bool timeoutSet, StoreType storeType = StoreType.All, ExpireOption expireOption = ExpireOption.None) - => storageSession.EXPIRE(key, expiry, out timeoutSet, storeType, expireOption, ref context, ref objectContext); - #endregion - - #region EXPIREAT - - /// - public GarnetStatus EXPIREAT(PinnedSpanByte key, long expiryTimestamp, out bool timeoutSet, StoreType storeType = StoreType.All, ExpireOption expireOption = ExpireOption.None) - => storageSession.EXPIREAT(key, expiryTimestamp, out timeoutSet, storeType, expireOption, ref context, ref objectContext); - - /// - public GarnetStatus PEXPIREAT(PinnedSpanByte key, long expiryTimestamp, out bool timeoutSet, StoreType storeType = StoreType.All, ExpireOption expireOption = ExpireOption.None) - => storageSession.EXPIREAT(key, expiryTimestamp, out timeoutSet, storeType, expireOption, ref context, ref objectContext, milliseconds: true); - - #endregion - - #region PERSIST - /// - public unsafe GarnetStatus PERSIST(PinnedSpanByte key, StoreType storeType = StoreType.All) - => storageSession.PERSIST(key, storeType, ref context, ref objectContext); - #endregion - #region Increment (INCR, INCRBY, DECR, DECRBY) /// public GarnetStatus Increment(PinnedSpanByte key, ref RawStringInput input, ref PinnedSpanByte output) @@ -290,34 +234,12 @@ public GarnetStatus IncrementByFloat(PinnedSpanByte key, out double output, doub } #endregion - #region DELETE - /// - public GarnetStatus DELETE(PinnedSpanByte key, StoreType storeType = StoreType.All) - => storageSession.DELETE(key, storeType, ref context, ref objectContext); - #endregion - #region GETDEL /// public GarnetStatus GETDEL(PinnedSpanByte key, ref SpanByteAndMemory output) => storageSession.GETDEL(key, ref output, ref context); #endregion - #region TYPE - - /// - public GarnetStatus GetKeyType(PinnedSpanByte key, out string typeName) - => storageSession.GetKeyType(key, out typeName, ref context, ref objectContext); - - #endregion - - #region MEMORY - - /// - public GarnetStatus MemoryUsageForKey(PinnedSpanByte key, out long memoryUsage, int samples = 0) - => storageSession.MemoryUsageForKey(key, out memoryUsage, ref context, ref objectContext, samples); - - #endregion - #region Advanced ops /// public GarnetStatus RMW_MainStore(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output) diff --git a/libs/server/API/GarnetApiObjectCommands.cs b/libs/server/API/GarnetApiObjectCommands.cs index 82ed66e00f6..2bff58addc1 100644 --- a/libs/server/API/GarnetApiObjectCommands.cs +++ b/libs/server/API/GarnetApiObjectCommands.cs @@ -7,18 +7,16 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Garnet API implementation /// - public partial struct GarnetApi : IGarnetApi, IGarnetWatchApi - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext + public partial struct GarnetApi : IGarnetApi, IGarnetWatchApi + where TContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext + where TUnifiedContext : ITsavoriteContext { #region SortedSet Methods @@ -47,7 +45,7 @@ public GarnetStatus SortedSetRemove(PinnedSpanByte key, PinnedSpanByte[] members => storageSession.SortedSetRemove(key, members, out zaddCount, ref objectContext); /// - public GarnetStatus SortedSetRemove(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus SortedSetRemove(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.SortedSetRemove(key, ref input, out output, ref objectContext); /// @@ -55,7 +53,7 @@ public GarnetStatus SortedSetLength(PinnedSpanByte key, out int len) => storageSession.SortedSetLength(key, out len, ref objectContext); /// - public GarnetStatus SortedSetLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus SortedSetLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.SortedSetLength(key, ref input, out output, ref objectContext); /// @@ -91,11 +89,11 @@ public GarnetStatus SortedSetCount(PinnedSpanByte key, ref ObjectInput input, re => storageSession.SortedSetCount(key, ref input, ref output, ref objectContext); /// - public GarnetStatus SortedSetLengthByValue(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus SortedSetLengthByValue(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.SortedSetLengthByValue(key, ref input, out output, ref objectContext); /// - public GarnetStatus SortedSetRemoveRangeByLex(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus SortedSetRemoveRangeByLex(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.SortedSetRemoveRangeByLex(key, ref input, out output, ref objectContext); /// @@ -241,7 +239,7 @@ public GarnetStatus ListRightPush(PinnedSpanByte key, PinnedSpanByte[] elements, => storageSession.ListPush(key, elements, whenExists ? ListOperation.RPUSHX : ListOperation.RPUSH, out itemsCount, ref objectContext); /// - public GarnetStatus ListRightPush(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus ListRightPush(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.ListPush(key, ref input, out output, ref objectContext); /// @@ -253,7 +251,7 @@ public GarnetStatus ListLeftPush(PinnedSpanByte key, PinnedSpanByte element, out => storageSession.ListPush(key, element, onlyWhenExists ? ListOperation.LPUSHX : ListOperation.LPUSH, out count, ref objectContext); /// - public GarnetStatus ListLeftPush(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus ListLeftPush(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.ListPush(key, ref input, out output, ref objectContext); /// @@ -299,7 +297,7 @@ public GarnetStatus ListLength(PinnedSpanByte key, out int count) => storageSession.ListLength(key, ref objectContext, out count); /// - public GarnetStatus ListLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus ListLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.ListLength(key, ref input, out output, ref objectContext); /// @@ -319,7 +317,7 @@ public GarnetStatus ListRange(PinnedSpanByte key, ref ObjectInput input, ref Gar => storageSession.ListRange(key, ref input, ref output, ref objectContext); /// - public GarnetStatus ListInsert(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus ListInsert(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.ListInsert(key, ref input, out output, ref objectContext); /// @@ -327,7 +325,7 @@ public GarnetStatus ListIndex(PinnedSpanByte key, ref ObjectInput input, ref Gar => storageSession.ListIndex(key, ref input, ref output, ref objectContext); /// - public GarnetStatus ListRemove(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus ListRemove(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.ListRemove(key, ref input, out output, ref objectContext); /// @@ -347,7 +345,7 @@ public GarnetStatus SetAdd(PinnedSpanByte key, PinnedSpanByte[] members, out int => storageSession.SetAdd(key, members, out saddCount, ref objectContext); /// - public GarnetStatus SetAdd(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus SetAdd(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.SetAdd(key, ref input, out output, ref objectContext); /// @@ -359,7 +357,7 @@ public GarnetStatus SetRemove(PinnedSpanByte key, PinnedSpanByte[] members, out => storageSession.SetRemove(key, members, out sremCount, ref objectContext); /// - public GarnetStatus SetRemove(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus SetRemove(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.SetRemove(key, ref input, out output, ref objectContext); /// @@ -367,7 +365,7 @@ public GarnetStatus SetLength(PinnedSpanByte key, out int count) => storageSession.SetLength(key, out count, ref objectContext); /// - public GarnetStatus SetLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus SetLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.SetLength(key, ref input, out output, ref objectContext); /// @@ -454,7 +452,7 @@ public GarnetStatus HashSet(PinnedSpanByte key, (PinnedSpanByte field, PinnedSpa => storageSession.HashSet(key, elements, out count, ref objectContext); /// - public GarnetStatus HashSet(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus HashSet(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.HashSet(key, ref input, out output, ref objectContext); /// @@ -494,11 +492,11 @@ public GarnetStatus HashLength(PinnedSpanByte key, out int count) => storageSession.HashLength(key, out count, ref objectContext); /// - public GarnetStatus HashLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus HashLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.HashLength(key, ref input, out output, ref objectContext); /// - public GarnetStatus HashStrLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus HashStrLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.HashStrLength(key, ref input, out output, ref objectContext); /// @@ -506,7 +504,7 @@ public GarnetStatus HashExists(PinnedSpanByte key, PinnedSpanByte field, out boo => storageSession.HashExists(key, field, out exists, ref objectContext); /// - public GarnetStatus HashExists(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus HashExists(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.HashExists(key, ref input, out output, ref objectContext); /// @@ -522,7 +520,7 @@ public GarnetStatus HashRandomField(PinnedSpanByte key, ref ObjectInput input, r => storageSession.HashRandomField(key, ref input, ref output, ref objectContext); /// - public GarnetStatus HashDelete(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus HashDelete(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) => storageSession.HashDelete(key, ref input, out output, ref objectContext); /// @@ -534,7 +532,7 @@ public GarnetStatus HashVals(PinnedSpanByte key, ref ObjectInput input, ref Garn => storageSession.HashVals(key, ref input, ref output, ref objectContext); /// - public GarnetStatus HashIncrement(PinnedSpanByte key, PinnedSpanByte input, out ObjectOutputHeader output) + public GarnetStatus HashIncrement(PinnedSpanByte key, PinnedSpanByte input, out OutputHeader output) => storageSession.HashIncrement(key, input, out output, ref objectContext); /// diff --git a/libs/server/API/GarnetApiUnifiedCommands.cs b/libs/server/API/GarnetApiUnifiedCommands.cs new file mode 100644 index 00000000000..8f86389a373 --- /dev/null +++ b/libs/server/API/GarnetApiUnifiedCommands.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using Tsavorite.core; + +namespace Garnet.server +{ + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; + + /// + /// Garnet API implementation + /// + public partial struct GarnetApi : IGarnetApi, IGarnetWatchApi + where TContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext + where TUnifiedContext : ITsavoriteContext + { + #region MEMORY + + /// + public GarnetStatus MEMORYUSAGE(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output) + => storageSession.Read_UnifiedStore(key, ref input, ref output, ref unifiedContext); + + #endregion + + #region TYPE + + /// + public GarnetStatus TYPE(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output) + => storageSession.Read_UnifiedStore(key, ref input, ref output, ref unifiedContext); + + #endregion + + #region TTL + + /// + public GarnetStatus TTL(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output) + => storageSession.Read_UnifiedStore(key, ref input, ref output, ref unifiedContext); + + #endregion + + #region EXPIRETIME + + /// + public GarnetStatus EXPIRETIME(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output) + => storageSession.Read_UnifiedStore(key, ref input, ref output, ref unifiedContext); + + #endregion + + #region EXISTS + + /// + public GarnetStatus EXISTS(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output) + => storageSession.Read_UnifiedStore(key, ref input, ref output, ref unifiedContext); + + /// + public GarnetStatus EXISTS(PinnedSpanByte key) + => storageSession.EXISTS(key, ref unifiedContext); + + #endregion + + #region DELETE + + /// + public GarnetStatus DELETE(PinnedSpanByte key) + => storageSession.DELETE(key, ref unifiedContext); + + #endregion + + #region EXPIRE + + /// + public unsafe GarnetStatus EXPIRE(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output) + => storageSession.RMW_UnifiedStore(key, ref input, ref output, ref unifiedContext); + + /// + public unsafe GarnetStatus EXPIRE(PinnedSpanByte key, PinnedSpanByte expiryMs, out bool timeoutSet, ExpireOption expireOption = ExpireOption.None) + => storageSession.EXPIRE(key, expiryMs, out timeoutSet, expireOption, ref unifiedContext); + + /// + public GarnetStatus EXPIRE(PinnedSpanByte key, TimeSpan expiry, out bool timeoutSet, ExpireOption expireOption = ExpireOption.None) + => storageSession.EXPIRE(key, expiry, out timeoutSet, expireOption, ref unifiedContext); + + #endregion + + #region EXPIREAT + + /// + public GarnetStatus EXPIREAT(PinnedSpanByte key, long expiryTimestamp, out bool timeoutSet, ExpireOption expireOption = ExpireOption.None) + => storageSession.EXPIREAT(key, expiryTimestamp, out timeoutSet, expireOption, ref unifiedContext); + + /// + public GarnetStatus PEXPIREAT(PinnedSpanByte key, long expiryTimestamp, out bool timeoutSet, ExpireOption expireOption = ExpireOption.None) + => storageSession.EXPIREAT(key, expiryTimestamp, out timeoutSet, expireOption, ref unifiedContext, milliseconds: true); + + #endregion + + #region PERSIST + + /// + public unsafe GarnetStatus PERSIST(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output) + => storageSession.RMW_UnifiedStore(key, ref input, ref output, ref unifiedContext); + + #endregion + } +} \ No newline at end of file diff --git a/libs/server/API/GarnetWatchApi.cs b/libs/server/API/GarnetWatchApi.cs index 2452019132c..617ea5849ba 100644 --- a/libs/server/API/GarnetWatchApi.cs +++ b/libs/server/API/GarnetWatchApi.cs @@ -70,17 +70,10 @@ public GarnetStatus GETRANGE(PinnedSpanByte key, ref RawStringInput input, ref S #region TTL /// - public GarnetStatus TTL(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output) + public GarnetStatus TTL(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output) { - garnetApi.WATCH(key, storeType); - return garnetApi.TTL(key, storeType, ref output); - } - - /// - public GarnetStatus PTTL(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output) - { - garnetApi.WATCH(key, storeType); - return garnetApi.PTTL(key, storeType, ref output); + garnetApi.WATCH(key, StoreType.All); + return garnetApi.TTL(key, ref input, ref output); } #endregion @@ -88,17 +81,10 @@ public GarnetStatus PTTL(PinnedSpanByte key, StoreType storeType, ref SpanByteAn #region EXPIRETIME /// - public GarnetStatus EXPIRETIME(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output) + public GarnetStatus EXPIRETIME(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output) { - garnetApi.WATCH(key, storeType); - return garnetApi.EXPIRETIME(key, storeType, ref output); - } - - /// - public GarnetStatus PEXPIRETIME(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output) - { - garnetApi.WATCH(key, storeType); - return garnetApi.PEXPIRETIME(key, storeType, ref output); + garnetApi.WATCH(key, StoreType.All); + return garnetApi.EXPIRETIME(key, ref input, ref output); } #endregion @@ -113,7 +99,7 @@ public GarnetStatus SortedSetLength(PinnedSpanByte key, out int zcardCount) } /// - public GarnetStatus SortedSetLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus SortedSetLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) { garnetApi.WATCH(key, StoreType.Object); return garnetApi.SortedSetLength(key, ref input, out output); @@ -134,7 +120,7 @@ public GarnetStatus SortedSetCount(PinnedSpanByte key, ref ObjectInput input, re } /// - public GarnetStatus SortedSetLengthByValue(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus SortedSetLengthByValue(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) { garnetApi.WATCH(key, StoreType.Object); return garnetApi.SortedSetLengthByValue(key, ref input, out output); @@ -277,7 +263,7 @@ public GarnetStatus ListLength(PinnedSpanByte key, out int count) } /// - public GarnetStatus ListLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus ListLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) { garnetApi.WATCH(key, StoreType.Object); return garnetApi.ListLength(key, ref input, out output); @@ -309,7 +295,7 @@ public GarnetStatus SetLength(PinnedSpanByte key, out int scardCount) } /// - public GarnetStatus SetLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus SetLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) { garnetApi.WATCH(key, StoreType.Object); return garnetApi.SetLength(key, ref input, out output); @@ -468,14 +454,14 @@ public GarnetStatus HashGetMultiple(PinnedSpanByte key, ref ObjectInput input, r } /// - public GarnetStatus HashStrLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus HashStrLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) { garnetApi.WATCH(key, StoreType.Object); return garnetApi.HashStrLength(key, ref input, out output); } /// - public GarnetStatus HashExists(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus HashExists(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) { garnetApi.WATCH(key, StoreType.Object); return garnetApi.HashExists(key, ref input, out output); @@ -496,7 +482,7 @@ public GarnetStatus HashVals(PinnedSpanByte key, ref ObjectInput input, ref Garn } /// - public GarnetStatus HashLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output) + public GarnetStatus HashLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output) { garnetApi.WATCH(key, StoreType.Object); return garnetApi.HashLength(key, ref input, out output); diff --git a/libs/server/API/IGarnetApi.cs b/libs/server/API/IGarnetApi.cs index 5d27b7d6591..72726bb0d35 100644 --- a/libs/server/API/IGarnetApi.cs +++ b/libs/server/API/IGarnetApi.cs @@ -59,10 +59,15 @@ public interface IGarnetApi : IGarnetReadApi, IGarnetAdvancedApi GarnetStatus SET(PinnedSpanByte key, IGarnetObject value); /// - /// SET + /// SET in main store /// - GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType storeType) - where TSourceLogRecord : ISourceLogRecord; + GarnetStatus SET_Main(in TSourceLogRecord srcLogRecord) where TSourceLogRecord : ISourceLogRecord; + + /// + /// SET in object store + /// + GarnetStatus SET_Object(in TSourceLogRecord srcLogRecord) where TSourceLogRecord : ISourceLogRecord; + #endregion #region SETEX @@ -149,13 +154,23 @@ GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType s #endregion #region EXISTS + /// /// EXISTS /// /// - /// + /// + /// + /// + GarnetStatus EXISTS(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output); + + /// + /// EXISTS + /// + /// Key /// - GarnetStatus EXISTS(PinnedSpanByte key, StoreType storeType = StoreType.All); + GarnetStatus EXISTS(PinnedSpanByte key); + #endregion #region EXPIRE @@ -165,20 +180,18 @@ GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType s /// Key /// Expiry in milliseconds, formatted as ASCII digits /// Whether timeout was set by the call - /// Store type: main, object, or both /// Expire option /// - GarnetStatus EXPIRE(PinnedSpanByte key, PinnedSpanByte expiryMs, out bool timeoutSet, StoreType storeType = StoreType.All, ExpireOption expireOption = ExpireOption.None); + GarnetStatus EXPIRE(PinnedSpanByte key, PinnedSpanByte expiryMs, out bool timeoutSet, ExpireOption expireOption = ExpireOption.None); /// /// Set a timeout on key using a timeSpan in seconds /// /// Key /// - /// Whether timeout was set by the call - /// Store type: main, object, or both + /// /// - GarnetStatus EXPIRE(PinnedSpanByte key, ref RawStringInput input, out bool timeoutSet, StoreType storeType = StoreType.All); + GarnetStatus EXPIRE(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output); /// /// Set a timeout on key using a timeSpan in seconds @@ -186,10 +199,9 @@ GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType s /// Key /// Expiry in TimeSpan /// Whether timeout was set by the call - /// Store type: main, object, or both /// Expire option /// - GarnetStatus EXPIRE(PinnedSpanByte key, TimeSpan expiry, out bool timeoutSet, StoreType storeType = StoreType.All, ExpireOption expireOption = ExpireOption.None); + GarnetStatus EXPIRE(PinnedSpanByte key, TimeSpan expiry, out bool timeoutSet, ExpireOption expireOption = ExpireOption.None); #endregion #region EXPIREAT @@ -200,10 +212,9 @@ GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType s /// Key /// Absolute Unix timestamp in seconds /// Whether timeout was set by the call - /// Store type: main, object, or both /// Expire option /// - GarnetStatus EXPIREAT(PinnedSpanByte key, long expiryTimestamp, out bool timeoutSet, StoreType storeType = StoreType.All, ExpireOption expireOption = ExpireOption.None); + GarnetStatus EXPIREAT(PinnedSpanByte key, long expiryTimestamp, out bool timeoutSet, ExpireOption expireOption = ExpireOption.None); /// /// Set a timeout on key using absolute Unix timestamp (seconds since January 1, 1970) in milliseconds @@ -211,21 +222,22 @@ GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType s /// Key /// Absolute Unix timestamp in milliseconds /// Whether timeout was set by the call - /// Store type: main, object, or both /// Expire option /// - GarnetStatus PEXPIREAT(PinnedSpanByte key, long expiryTimestamp, out bool timeoutSet, StoreType storeType = StoreType.All, ExpireOption expireOption = ExpireOption.None); + GarnetStatus PEXPIREAT(PinnedSpanByte key, long expiryTimestamp, out bool timeoutSet, ExpireOption expireOption = ExpireOption.None); #endregion #region PERSIST + /// /// PERSIST /// /// Key - /// Store type: main, object, or both + /// + /// /// - GarnetStatus PERSIST(PinnedSpanByte key, StoreType storeType = StoreType.All); + GarnetStatus PERSIST(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output); #endregion #region Increment (INCR, INCRBY, DECR, DECRBY) @@ -276,13 +288,14 @@ GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType s #endregion #region DELETE + /// - /// DELETE + /// Deletes a key from the unified store /// /// - /// /// - GarnetStatus DELETE(PinnedSpanByte key, StoreType storeType = StoreType.All); + GarnetStatus DELETE(PinnedSpanByte key); + #endregion #region GETDEL @@ -302,9 +315,10 @@ GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType s /// string, list, set, zset, and hash. /// /// - /// + /// + /// /// - GarnetStatus GetKeyType(PinnedSpanByte key, out string typeName); + GarnetStatus TYPE(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output); #endregion @@ -314,10 +328,10 @@ GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType s /// Gets the number of bytes that a key and its value require to be stored in RAM. /// /// Name of the key or object to get the memory usage - /// The value in bytes the key or object is using - /// Number of sampled nested values + /// + /// /// GarnetStatus - GarnetStatus MemoryUsageForKey(PinnedSpanByte key, out long memoryUsage, int samples = 0); + GarnetStatus MEMORYUSAGE(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output); #endregion @@ -386,7 +400,7 @@ GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType s /// /// /// - GarnetStatus SortedSetRemove(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus SortedSetRemove(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Removes all elements in the sorted set between the @@ -396,7 +410,7 @@ GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType s /// /// /// - GarnetStatus SortedSetRemoveRangeByLex(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus SortedSetRemoveRangeByLex(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Removes and returns the first element from the sorted set stored at key, @@ -637,7 +651,7 @@ GarnetStatus GeoSearchStore(PinnedSpanByte key, PinnedSpanByte destinationKey, r /// /// /// - GarnetStatus SetAdd(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus SetAdd(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Removes the specified member from the set. @@ -670,7 +684,7 @@ GarnetStatus GeoSearchStore(PinnedSpanByte key, PinnedSpanByte destinationKey, r /// /// /// - GarnetStatus SetRemove(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus SetRemove(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Removes and returns one random member from the set at key. @@ -769,13 +783,13 @@ GarnetStatus GeoSearchStore(PinnedSpanByte key, PinnedSpanByte destinationKey, r GarnetStatus ListPosition(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output); /// - /// ListLeftPush ArgSlice version with ObjectOutputHeader output + /// ListLeftPush ArgSlice version with OutputHeader output /// /// /// /// /// - GarnetStatus ListLeftPush(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus ListLeftPush(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// ListLeftPush ArgSlice version, one element @@ -798,13 +812,13 @@ GarnetStatus GeoSearchStore(PinnedSpanByte key, PinnedSpanByte destinationKey, r GarnetStatus ListLeftPush(PinnedSpanByte key, PinnedSpanByte[] elements, out int count, bool whenExists = false); /// - /// ListRightPush ArgSlice version with ObjectOutputHeader output + /// ListRightPush ArgSlice version with OutputHeader output /// /// /// /// /// - public GarnetStatus ListRightPush(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + public GarnetStatus ListRightPush(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// ListRightPush ArgSlice version, one element @@ -941,7 +955,7 @@ GarnetStatus GeoSearchStore(PinnedSpanByte key, PinnedSpanByte destinationKey, r /// /// /// - GarnetStatus ListInsert(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus ListInsert(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Removes the first count occurrences of elements equal to element from the list. @@ -950,7 +964,7 @@ GarnetStatus GeoSearchStore(PinnedSpanByte key, PinnedSpanByte destinationKey, r /// /// /// - GarnetStatus ListRemove(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus ListRemove(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Sets the list element at index to element. @@ -995,7 +1009,7 @@ GarnetStatus GeoSearchStore(PinnedSpanByte key, PinnedSpanByte destinationKey, r /// /// /// - GarnetStatus HashSet(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus HashSet(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Set only if field does not yet exist. If key does not exist, a new key holding a hash is created. @@ -1034,7 +1048,7 @@ GarnetStatus GeoSearchStore(PinnedSpanByte key, PinnedSpanByte destinationKey, r /// /// /// - GarnetStatus HashDelete(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus HashDelete(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Increments the number stored at field in the hash key by increment parameter. @@ -1043,7 +1057,7 @@ GarnetStatus GeoSearchStore(PinnedSpanByte key, PinnedSpanByte destinationKey, r /// /// /// - GarnetStatus HashIncrement(PinnedSpanByte key, PinnedSpanByte input, out ObjectOutputHeader output); + GarnetStatus HashIncrement(PinnedSpanByte key, PinnedSpanByte input, out OutputHeader output); /// /// Increments the number stored at field representing a floating point value @@ -1241,19 +1255,10 @@ public interface IGarnetReadApi /// Returns the remaining time to live in seconds of a key that has a timeout. /// /// The key to return the remaining time to live in the store - /// The store type to operate on. - /// The span to allocate the output of the operation. - /// - GarnetStatus TTL(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output); - - /// - /// Returns the remaining time to live in milliseconds of a key that has a timeout. - /// - /// The key to return the remaining time to live in the store. - /// The store type to operate on. + /// /// The span to allocate the output of the operation. /// - GarnetStatus PTTL(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output); + GarnetStatus TTL(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output); #endregion @@ -1263,19 +1268,10 @@ public interface IGarnetReadApi /// Returns the absolute Unix timestamp (since January 1, 1970) in seconds at which the given key will expire. /// /// The key to get the expiration time for. - /// The type of store to retrieve the key from. - /// The output containing the expiration time. - /// The status of the operation. - GarnetStatus EXPIRETIME(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output); - - /// - /// Returns the absolute Unix timestamp (since January 1, 1970) in milliseconds at which the given key will expire. - /// - /// The key to get the expiration time for. - /// The type of store to retrieve the key from. + /// /// The output containing the expiration time. /// The status of the operation. - GarnetStatus PEXPIRETIME(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output); + GarnetStatus EXPIRETIME(PinnedSpanByte key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output); #endregion @@ -1296,7 +1292,7 @@ public interface IGarnetReadApi /// /// /// - GarnetStatus SortedSetLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus SortedSetLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Returns the specified range of elements in the sorted set stored at key. @@ -1357,7 +1353,7 @@ public interface IGarnetReadApi /// /// /// - GarnetStatus SortedSetLengthByValue(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus SortedSetLengthByValue(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// ZRANK: Returns the rank of member in the sorted set, the scores in the sorted set are ordered from low to high @@ -1521,7 +1517,7 @@ public interface IGarnetReadApi /// /// /// - GarnetStatus ListLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus ListLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Gets the specified elements of the list stored at key. @@ -1560,7 +1556,7 @@ public interface IGarnetReadApi /// /// /// - GarnetStatus SetLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus SetLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// SMEMBERS key @@ -1716,7 +1712,7 @@ public interface IGarnetReadApi /// /// /// - GarnetStatus HashStrLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus HashStrLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Returns the number of fields contained in the hash Key. @@ -1725,7 +1721,7 @@ public interface IGarnetReadApi /// /// /// - GarnetStatus HashLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus HashLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Returns if field is an existing field in the hash stored at key. @@ -1743,7 +1739,7 @@ public interface IGarnetReadApi /// /// /// - GarnetStatus HashExists(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output); + GarnetStatus HashExists(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output); /// /// Returns count random fields from the hash value. diff --git a/libs/server/Cluster/CheckpointMetadata.cs b/libs/server/Cluster/CheckpointMetadata.cs index 912e10462c4..798e02106a8 100644 --- a/libs/server/Cluster/CheckpointMetadata.cs +++ b/libs/server/Cluster/CheckpointMetadata.cs @@ -13,12 +13,6 @@ public sealed class CheckpointMetadata public long storeCheckpointCoveredAofAddress; public string storePrimaryReplId; - public long objectStoreVersion; - public Guid objectStoreHlogToken; - public Guid objectStoreIndexToken; - public long objectCheckpointCoveredAofAddress; - public string objectStorePrimaryReplId; - public CheckpointMetadata() { storeVersion = -1; @@ -26,12 +20,6 @@ public CheckpointMetadata() storeIndexToken = default; storeCheckpointCoveredAofAddress = long.MaxValue; storePrimaryReplId = null; - - objectStoreVersion = -1; - objectStoreHlogToken = default; - objectStoreIndexToken = default; - objectCheckpointCoveredAofAddress = long.MaxValue; - objectStorePrimaryReplId = null; } /// @@ -45,12 +33,7 @@ public override string ToString() $"storeHlogToken={storeHlogToken}," + $"storeIndexToken={storeIndexToken}," + $"storeCheckpointCoveredAofAddress={storeCheckpointCoveredAofAddress}," + - $"storePrimaryReplId={storePrimaryReplId ?? "(empty)"}," + - $"objectStoreVersion={objectStoreVersion}," + - $"objectStoreHlogToken={objectStoreHlogToken}," + - $"objectStoreIndexToken={objectStoreIndexToken}," + - $"objectCheckpointCoveredAofAddress={objectCheckpointCoveredAofAddress}," + - $"objectStorePrimaryReplId={objectStorePrimaryReplId ?? "(empty)"}"; + $"storePrimaryReplId={storePrimaryReplId ?? "(empty)"}"; } } } \ No newline at end of file diff --git a/libs/server/Cluster/IClusterProvider.cs b/libs/server/Cluster/IClusterProvider.cs index 8f169b4ae31..2a11d483ff3 100644 --- a/libs/server/Cluster/IClusterProvider.cs +++ b/libs/server/Cluster/IClusterProvider.cs @@ -13,10 +13,13 @@ namespace Garnet.server { using BasicGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, BasicContext, + ObjectAllocator>>, + BasicContext, ObjectAllocator>>>; /// diff --git a/libs/server/Cluster/StoreType.cs b/libs/server/Cluster/StoreType.cs index b841ba34c59..1efa27fb52e 100644 --- a/libs/server/Cluster/StoreType.cs +++ b/libs/server/Cluster/StoreType.cs @@ -10,6 +10,11 @@ namespace Garnet.server /// public enum StoreType : byte { + /// + /// No store specified + /// + None = 0, + /// /// Main (raw string) store /// diff --git a/libs/server/Custom/CustomObjectBase.cs b/libs/server/Custom/CustomObjectBase.cs index 3dddf4f1af9..e7a86f49317 100644 --- a/libs/server/Custom/CustomObjectBase.cs +++ b/libs/server/Custom/CustomObjectBase.cs @@ -83,7 +83,7 @@ public sealed override bool Operate(ref ObjectInput input, ref GarnetObjectStore if ((byte)input.header.type != this.type) { // Indicates an incorrect type of key - output.OutputFlags |= ObjectStoreOutputFlags.WrongType; + output.OutputFlags |= OutputFlags.WrongType; output.SpanByteAndMemory.Length = 0; return true; } diff --git a/libs/server/Custom/CustomRespCommands.cs b/libs/server/Custom/CustomRespCommands.cs index 43cd3f8b013..c4514718b43 100644 --- a/libs/server/Custom/CustomRespCommands.cs +++ b/libs/server/Custom/CustomRespCommands.cs @@ -53,11 +53,11 @@ private bool TryTransactionProc(byte id, CustomTransactionProcedure proc, int st return true; } - public bool RunTransactionProc(byte id, ref CustomProcedureInput procInput, ref MemoryResult output) + public bool RunTransactionProc(byte id, ref CustomProcedureInput procInput, ref MemoryResult output, bool isRecovering = false) { var proc = customCommandManagerSession .GetCustomTransactionProcedure(id, this, txnManager, scratchBufferAllocator, out _); - return txnManager.RunTransactionProc(id, ref procInput, proc, ref output); + return txnManager.RunTransactionProc(id, ref procInput, proc, ref output, isRecovering); } private void TryCustomProcedure(CustomProcedure proc, int startIdx = 0) diff --git a/libs/server/Custom/CustomTransactionProcedure.cs b/libs/server/Custom/CustomTransactionProcedure.cs index 3c7210bd742..13bf8037ba3 100644 --- a/libs/server/Custom/CustomTransactionProcedure.cs +++ b/libs/server/Custom/CustomTransactionProcedure.cs @@ -36,10 +36,11 @@ public abstract class CustomTransactionProcedure : CustomProcedureBase /// /// /// - /// - protected void AddKey(PinnedSpanByte key, LockType type, bool isObject) + /// + protected void AddKey(PinnedSpanByte key, LockType type, StoreType storeType) { - txnManager.SaveKeyEntryToLock(key, isObject, type); + txnManager.AddTransactionStoreType(storeType); + txnManager.SaveKeyEntryToLock(key, type); txnManager.VerifyKeyOwnership(key, type); } @@ -76,6 +77,8 @@ public abstract void Main(TGarnetApi api, ref CustomProcedureInput p /// /// Finalize transaction: runs after the transactions commits/aborts, allowed to read and write (non-transactionally) with per-key locks and produce output + /// NOTE: Finalize is considered post transaction processing and therefore is not executed at recovery time. Instead, the individual Tsavorite commands are logged and replayed through the AOF. + /// If you are not using AOF for persistence then this is implementation detail you can ignore. /// public virtual void Finalize(TGarnetApi api, ref CustomProcedureInput procInput, ref MemoryResult output) where TGarnetApi : IGarnetApi diff --git a/libs/server/Databases/DatabaseManagerBase.cs b/libs/server/Databases/DatabaseManagerBase.cs index a889863afdd..f06be9d602b 100644 --- a/libs/server/Databases/DatabaseManagerBase.cs +++ b/libs/server/Databases/DatabaseManagerBase.cs @@ -10,11 +10,8 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Base class for logical database management @@ -40,8 +37,7 @@ internal abstract class DatabaseManagerBase : IDatabaseManager public abstract void ResumeCheckpoints(int dbId); /// - public abstract void RecoverCheckpoint(bool replicaRecover = false, bool recoverMainStoreFromToken = false, - bool recoverObjectStoreFromToken = false, CheckpointMetadata metadata = null); + public abstract void RecoverCheckpoint(bool replicaRecover = false, bool recoverMainStoreFromToken = false, CheckpointMetadata metadata = null); /// public abstract bool TakeCheckpoint(bool background, ILogger logger = null, CancellationToken token = default); @@ -84,7 +80,7 @@ public abstract Task TaskCheckpointBasedOnAofSizeLimitAsync(long aofSizeLimit, public abstract void ExpiredKeyDeletionScan(); /// - public abstract void StartObjectSizeTrackers(CancellationToken token = default); + public abstract void StartSizeTrackers(CancellationToken token = default); /// public abstract void Reset(int dbId = 0); @@ -120,10 +116,7 @@ public abstract Task TaskCheckpointBasedOnAofSizeLimitAsync(long aofSizeLimit, public abstract IDatabaseManager Clone(bool enableAof); /// - public TsavoriteKV MainStore => DefaultDatabase.MainStore; - - /// - public TsavoriteKV ObjectStore => DefaultDatabase.ObjectStore; + public TsavoriteKV Store => DefaultDatabase.Store; /// public TsavoriteLog AppendOnlyFile => DefaultDatabase.AppendOnlyFile; @@ -132,7 +125,7 @@ public abstract Task TaskCheckpointBasedOnAofSizeLimitAsync(long aofSizeLimit, public DateTimeOffset LastSaveTime => DefaultDatabase.LastSaveTime; /// - public CacheSizeTracker ObjectStoreSizeTracker => DefaultDatabase.ObjectStoreSizeTracker; + public CacheSizeTracker SizeTracker => DefaultDatabase.SizeTracker; /// public WatchVersionMap VersionMap => DefaultDatabase.VersionMap; @@ -174,37 +167,14 @@ protected DatabaseManagerBase(StoreWrapper.DatabaseCreatorDelegate createDatabas /// /// Database to recover /// Store version - /// Object store version - protected void RecoverDatabaseCheckpoint(GarnetDatabase db, out long storeVersion, out long objectStoreVersion) + protected void RecoverDatabaseCheckpoint(GarnetDatabase db, out long storeVersion) { storeVersion = 0; - objectStoreVersion = 0; - if (db.ObjectStore != null) - { - // Get store recover version - var currStoreVersion = db.MainStore.GetRecoverVersion(); - // Get object store recover version - var currObjectStoreVersion = db.ObjectStore.GetRecoverVersion(); - - // Choose the minimum common recover version for both stores - if (currStoreVersion < currObjectStoreVersion) - currObjectStoreVersion = currStoreVersion; - else if (objectStoreVersion > 0) // handle the case where object store was disabled at checkpointing time - currStoreVersion = currObjectStoreVersion; - - // Recover to the minimum common recover version - storeVersion = db.MainStore.Recover(recoverTo: currStoreVersion); - objectStoreVersion = db.ObjectStore.Recover(recoverTo: currObjectStoreVersion); - Logger?.LogInformation("Recovered store to version {storeVersion} and object store to version {objectStoreVersion}", storeVersion, objectStoreVersion); - } - else - { - storeVersion = db.MainStore.Recover(); - Logger?.LogInformation("Recovered store to version {storeVersion}", storeVersion); - } + storeVersion = db.Store.Recover(); + Logger?.LogInformation("Recovered store to version {storeVersion}", storeVersion); - if (storeVersion > 0 || objectStoreVersion > 0) + if (storeVersion > 0) { db.LastSaveTime = DateTimeOffset.UtcNow; } @@ -217,36 +187,31 @@ protected void RecoverDatabaseCheckpoint(GarnetDatabase db, out long storeVersio /// Logger /// Cancellation token /// Tuple of store tail address and object store tail address - protected async Task<(long?, long?)> TakeCheckpointAsync(GarnetDatabase db, ILogger logger = null, CancellationToken token = default) + protected async Task TakeCheckpointAsync(GarnetDatabase db, ILogger logger = null, CancellationToken token = default) { try { DoCompaction(db, isFromCheckpoint: true, logger); - var lastSaveStoreTailAddress = db.MainStore.Log.TailAddress; - var lastSaveObjectStoreTailAddress = (db.ObjectStore?.Log.TailAddress).GetValueOrDefault(); + var lastSaveStoreTailAddress = db.Store.Log.TailAddress; var full = db.LastSaveStoreTailAddress == 0 || - lastSaveStoreTailAddress - db.LastSaveStoreTailAddress >= StoreWrapper.serverOptions.FullCheckpointLogInterval || - (db.ObjectStore != null && (db.LastSaveObjectStoreTailAddress == 0 || - lastSaveObjectStoreTailAddress - db.LastSaveObjectStoreTailAddress >= StoreWrapper.serverOptions.FullCheckpointLogInterval)); + lastSaveStoreTailAddress - db.LastSaveStoreTailAddress >= StoreWrapper.serverOptions.FullCheckpointLogInterval; var tryIncremental = StoreWrapper.serverOptions.EnableIncrementalSnapshots; - if (db.MainStore.IncrementalSnapshotTailAddress >= StoreWrapper.serverOptions.IncrementalSnapshotLogSizeLimit) - tryIncremental = false; - if (db.ObjectStore?.IncrementalSnapshotTailAddress >= StoreWrapper.serverOptions.IncrementalSnapshotLogSizeLimit) + if (db.Store.IncrementalSnapshotTailAddress >= StoreWrapper.serverOptions.IncrementalSnapshotLogSizeLimit) tryIncremental = false; var checkpointType = StoreWrapper.serverOptions.UseFoldOverCheckpoints ? CheckpointType.FoldOver : CheckpointType.Snapshot; await InitiateCheckpointAsync(db, full, checkpointType, tryIncremental, logger); - return full ? new(lastSaveStoreTailAddress, lastSaveObjectStoreTailAddress) : (null, null); + return full ? lastSaveStoreTailAddress : null; } catch (Exception ex) { logger?.LogError(ex, "Checkpointing threw exception, DB ID: {id}", db.Id); } - return (null, null); + return null; } /// @@ -310,10 +275,8 @@ protected void ResetDatabase(GarnetDatabase db) { try { - if (db.MainStore.Log.TailAddress > 64) - db.MainStore.Reset(); - if (db.ObjectStore?.Log.TailAddress > 64) - db.ObjectStore?.Reset(); + if (db.Store.Log.TailAddress > 64) + db.Store.Reset(); db.AppendOnlyFile?.Reset(); var lastSave = DateTimeOffset.FromUnixTimeSeconds(0); @@ -353,8 +316,7 @@ protected static void EnqueueDatabaseCommit(GarnetDatabase db, AofEntryType entr /// Truncate AOF log protected static void FlushDatabase(GarnetDatabase db, bool unsafeTruncateLog, bool truncateAof = true) { - db.MainStore.Log.ShiftBeginAddress(db.MainStore.Log.TailAddress, truncateLog: unsafeTruncateLog); - db.ObjectStore?.Log.ShiftBeginAddress(db.ObjectStore.Log.TailAddress, truncateLog: unsafeTruncateLog); + db.Store.Log.ShiftBeginAddress(db.Store.Log.TailAddress, truncateLog: unsafeTruncateLog); if (truncateAof) db.AppendOnlyFile?.TruncateUntil(db.AppendOnlyFile.TailAddress); @@ -369,30 +331,13 @@ protected bool GrowIndexesIfNeeded(GarnetDatabase db) { var indexesMaxedOut = true; - if (!DefaultDatabase.MainStoreIndexMaxedOut) + if (!DefaultDatabase.StoreIndexMaxedOut) { - var dbMainStore = DefaultDatabase.MainStore; - if (GrowIndexIfNeeded(StoreType.Main, - StoreWrapper.serverOptions.AdjustedIndexMaxCacheLines, dbMainStore.OverflowBucketAllocations, + var dbMainStore = DefaultDatabase.Store; + if (GrowIndexIfNeeded(StoreWrapper.serverOptions.AdjustedIndexMaxCacheLines, dbMainStore.OverflowBucketAllocations, () => dbMainStore.IndexSize, async () => await dbMainStore.GrowIndexAsync())) { - db.MainStoreIndexMaxedOut = true; - } - else - { - indexesMaxedOut = false; - } - } - - if (!db.ObjectStoreIndexMaxedOut) - { - var dbObjectStore = db.ObjectStore; - if (GrowIndexIfNeeded(StoreType.Object, - StoreWrapper.serverOptions.AdjustedObjectStoreIndexMaxCacheLines, - dbObjectStore.OverflowBucketAllocations, - () => dbObjectStore.IndexSize, async () => await dbObjectStore.GrowIndexAsync())) - { - db.ObjectStoreIndexMaxedOut = true; + db.StoreIndexMaxedOut = true; } else { @@ -410,15 +355,15 @@ protected bool GrowIndexesIfNeeded(GarnetDatabase db) /// Logger protected void ExecuteObjectCollection(GarnetDatabase db, ILogger logger = null) { - if (db.ObjectStoreCollectionDbStorageSession == null) + if (db.StoreCollectionDbStorageSession == null) { var scratchBufferManager = new ScratchBufferBuilder(); - db.ObjectStoreCollectionDbStorageSession = + db.StoreCollectionDbStorageSession = new StorageSession(StoreWrapper, scratchBufferManager, null, null, db.Id, Logger); } - ExecuteHashCollect(db.ObjectStoreCollectionDbStorageSession); - ExecuteSortedSetCollect(db.ObjectStoreCollectionDbStorageSession); + ExecuteHashCollect(db.StoreCollectionDbStorageSession); + ExecuteSortedSetCollect(db.StoreCollectionDbStorageSession); } /// @@ -427,12 +372,7 @@ protected void ExecuteObjectCollection(GarnetDatabase db, ILogger logger = null) /// Database protected void ExpiredKeyDeletionScan(GarnetDatabase db) { - _ = MainStoreExpiredKeyDeletionScan(db); - - if (StoreWrapper.serverOptions.DisableObjects) - return; - - _ = ObjectStoreExpiredKeyDeletionScan(db); + _ = StoreExpiredKeyDeletionScan(db); } /// @@ -448,8 +388,7 @@ protected void DoCompaction(GarnetDatabase db, bool isFromCheckpoint = false, IL // If periodic compaction is enabled and this is called from checkpointing, skip compaction if (isFromCheckpoint && StoreWrapper.serverOptions.CompactionFrequencySecs > 0) return; - DoCompaction(db, StoreWrapper.serverOptions.CompactionMaxSegments, - StoreWrapper.serverOptions.ObjectStoreCompactionMaxSegments, 1, + DoCompaction(db, StoreWrapper.serverOptions.CompactionMaxSegments, 1, StoreWrapper.serverOptions.CompactionType, StoreWrapper.serverOptions.CompactionForceDelete); } catch (Exception ex) @@ -466,125 +405,79 @@ protected void DoCompaction(GarnetDatabase db, bool isFromCheckpoint = false, IL /// Decision is based on whether overflow bucket allocation is more than a threshold which indicates a contention /// in the index leading many allocations to the same bucket. /// - /// /// /// /// /// /// True if index has reached its max size - protected bool GrowIndexIfNeeded(StoreType storeType, long indexMaxSize, long overflowCount, Func indexSizeRetriever, Action growAction) + protected bool GrowIndexIfNeeded(long indexMaxSize, long overflowCount, Func indexSizeRetriever, Action growAction) { Logger?.LogDebug( - $"IndexAutoGrowTask[{{storeType}}]: checking index size {{indexSizeRetriever}} against max {{indexMaxSize}} with overflow {{overflowCount}}", - storeType, indexSizeRetriever(), indexMaxSize, overflowCount); + $"IndexAutoGrowTask: checking index size {{indexSizeRetriever}} against max {{indexMaxSize}} with overflow {{overflowCount}}", + indexSizeRetriever(), indexMaxSize, overflowCount); if (indexSizeRetriever() < indexMaxSize && overflowCount > (indexSizeRetriever() * StoreWrapper.serverOptions.IndexResizeThreshold / 100)) { Logger?.LogInformation( - $"IndexAutoGrowTask[{{storeType}}]: overflowCount {{overflowCount}} ratio more than threshold {{indexResizeThreshold}}%. Doubling index size...", - storeType, overflowCount, StoreWrapper.serverOptions.IndexResizeThreshold); + $"IndexAutoGrowTask: overflowCount {{overflowCount}} ratio more than threshold {{indexResizeThreshold}}%. Doubling index size...", + overflowCount, StoreWrapper.serverOptions.IndexResizeThreshold); growAction(); } if (indexSizeRetriever() < indexMaxSize) return false; Logger?.LogDebug( - $"IndexAutoGrowTask[{{storeType}}]: checking index size {{indexSizeRetriever}} against max {{indexMaxSize}} with overflow {{overflowCount}}", - storeType, indexSizeRetriever(), indexMaxSize, overflowCount); + $"IndexAutoGrowTask: checking index size {{indexSizeRetriever}} against max {{indexMaxSize}} with overflow {{overflowCount}}", + indexSizeRetriever(), indexMaxSize, overflowCount); return true; } - private void DoCompaction(GarnetDatabase db, int mainStoreMaxSegments, int objectStoreMaxSegments, int numSegmentsToCompact, LogCompactionType compactionType, bool compactionForceDelete) + private void DoCompaction(GarnetDatabase db, int mainStoreMaxSegments, int numSegmentsToCompact, LogCompactionType compactionType, bool compactionForceDelete) { if (compactionType == LogCompactionType.None) return; - var mainStoreLog = db.MainStore.Log; + var storeLog = db.Store.Log; var mainStoreMaxLogSize = (1L << StoreWrapper.serverOptions.SegmentSizeBits()) * mainStoreMaxSegments; - if (mainStoreLog.ReadOnlyAddress - mainStoreLog.BeginAddress > mainStoreMaxLogSize) + if (storeLog.ReadOnlyAddress - storeLog.BeginAddress > mainStoreMaxLogSize) { - var readOnlyAddress = mainStoreLog.ReadOnlyAddress; + var readOnlyAddress = storeLog.ReadOnlyAddress; var compactLength = (1L << StoreWrapper.serverOptions.SegmentSizeBits()) * (mainStoreMaxSegments - numSegmentsToCompact); var untilAddress = readOnlyAddress - compactLength; Logger?.LogInformation( "Begin main store compact until {untilAddress}, Begin = {beginAddress}, ReadOnly = {readOnlyAddress}, Tail = {tailAddress}", - untilAddress, mainStoreLog.BeginAddress, readOnlyAddress, mainStoreLog.TailAddress); + untilAddress, storeLog.BeginAddress, readOnlyAddress, storeLog.TailAddress); switch (compactionType) { case LogCompactionType.Shift: - mainStoreLog.ShiftBeginAddress(untilAddress, true, compactionForceDelete); + storeLog.ShiftBeginAddress(untilAddress, true, compactionForceDelete); break; case LogCompactionType.Scan: - mainStoreLog.Compact(untilAddress, CompactionType.Scan); + storeLog.Compact(untilAddress, CompactionType.Scan); if (compactionForceDelete) { CompactionCommitAof(db); - mainStoreLog.Truncate(); + storeLog.Truncate(); } break; case LogCompactionType.Lookup: - mainStoreLog.Compact(untilAddress, CompactionType.Lookup); + storeLog.Compact(untilAddress, CompactionType.Lookup); if (compactionForceDelete) { CompactionCommitAof(db); - mainStoreLog.Truncate(); + storeLog.Truncate(); } break; } Logger?.LogInformation( - "End main store compact until {untilAddress}, Begin = {beginAddress}, ReadOnly = {readOnlyAddress}, Tail = {tailAddress}", - untilAddress, mainStoreLog.BeginAddress, readOnlyAddress, mainStoreLog.TailAddress); - } - - if (db.ObjectStore == null) return; - - var objectStoreLog = db.ObjectStore.Log; - - var objectStoreMaxLogSize = (1L << StoreWrapper.serverOptions.ObjectStoreSegmentSizeBits()) * objectStoreMaxSegments; - - if (objectStoreLog.ReadOnlyAddress - objectStoreLog.BeginAddress > objectStoreMaxLogSize) - { - var readOnlyAddress = objectStoreLog.ReadOnlyAddress; - var compactLength = (1L << StoreWrapper.serverOptions.ObjectStoreSegmentSizeBits()) * (objectStoreMaxSegments - numSegmentsToCompact); - var untilAddress = readOnlyAddress - compactLength; - Logger?.LogInformation( - "Begin object store compact until {untilAddress}, Begin = {beginAddress}, ReadOnly = {readOnlyAddress}, Tail = {tailAddress}", - untilAddress, objectStoreLog.BeginAddress, readOnlyAddress, objectStoreLog.TailAddress); - - switch (compactionType) - { - case LogCompactionType.Shift: - objectStoreLog.ShiftBeginAddress(untilAddress, compactionForceDelete); - break; - - case LogCompactionType.Scan: - objectStoreLog.Compact(untilAddress, CompactionType.Scan); - if (compactionForceDelete) - { - CompactionCommitAof(db); - objectStoreLog.Truncate(); - } - break; - - case LogCompactionType.Lookup: - objectStoreLog.Compact(untilAddress, CompactionType.Lookup); - if (compactionForceDelete) - { - CompactionCommitAof(db); - objectStoreLog.Truncate(); - } - break; - } - - Logger?.LogInformation( - "End object store compact until {untilAddress}, Begin = {beginAddress}, ReadOnly = {readOnlyAddress}, Tail = {tailAddress}", - untilAddress, mainStoreLog.BeginAddress, readOnlyAddress, mainStoreLog.TailAddress); + "End store compact until {untilAddress}, Begin = {beginAddress}, ReadOnly = {readOnlyAddress}, Tail = {tailAddress}", + untilAddress, storeLog.BeginAddress, readOnlyAddress, storeLog.TailAddress); } } @@ -642,28 +535,15 @@ private async Task InitiateCheckpointAsync(GarnetDatabase db, bool full, Checkpo IStateMachine sm; if (full) { - sm = db.ObjectStore == null ? - Checkpoint.Full(db.MainStore, checkpointType, out checkpointResult.token) : - Checkpoint.Full(db.MainStore, db.ObjectStore, checkpointType, out checkpointResult.token); + sm = Checkpoint.Full(db.Store, checkpointType, out checkpointResult.token); } else { - tryIncremental = tryIncremental && db.MainStore.CanTakeIncrementalCheckpoint(checkpointType, out checkpointResult.token); - if (db.ObjectStore != null) - tryIncremental = tryIncremental && db.ObjectStore.CanTakeIncrementalCheckpoint(checkpointType, out var guid2) && checkpointResult.token == guid2; + tryIncremental = tryIncremental && db.Store.CanTakeIncrementalCheckpoint(checkpointType, out checkpointResult.token); - if (tryIncremental) - { - sm = db.ObjectStore == null ? - Checkpoint.IncrementalHybridLogOnly(db.MainStore, checkpointResult.token) : - Checkpoint.IncrementalHybridLogOnly(db.MainStore, db.ObjectStore, checkpointResult.token); - } - else - { - sm = db.ObjectStore == null ? - Checkpoint.HybridLogOnly(db.MainStore, checkpointType, out checkpointResult.token) : - Checkpoint.HybridLogOnly(db.MainStore, db.ObjectStore, checkpointType, out checkpointResult.token); - } + sm = tryIncremental + ? Checkpoint.IncrementalHybridLogOnly(db.Store, checkpointResult.token) + : Checkpoint.HybridLogOnly(db.Store, checkpointType, out checkpointResult.token); } checkpointResult.success = await db.StateMachineDriver.RunAsync(sm); @@ -680,18 +560,15 @@ private async Task InitiateCheckpointAsync(GarnetDatabase db, bool full, Checkpo db.AppendOnlyFile?.Commit(); } - if (db.ObjectStore != null) + // During the checkpoint, we may have serialized Garnet objects in (v) versions of objects. + // We can now safely remove these serialized versions as they are no longer needed. + using var iter1 = db.Store.Log.Scan(db.Store.Log.ReadOnlyAddress, + db.Store.Log.TailAddress, DiskScanBufferingMode.SinglePageBuffering, includeClosedRecords: true); + while (iter1.GetNext()) { - // During the checkpoint, we may have serialized Garnet objects in (v) versions of objects. - // We can now safely remove these serialized versions as they are no longer needed. - using var iter1 = db.ObjectStore.Log.Scan(db.ObjectStore.Log.ReadOnlyAddress, - db.ObjectStore.Log.TailAddress, DiskScanBufferingMode.SinglePageBuffering, includeClosedRecords: true); - while (iter1.GetNext()) - { - var valueObject = iter1.ValueObject; - if (valueObject != null) - ((GarnetObjectBase)iter1.ValueObject).ClearSerializedObjectData(); - } + var valueObject = iter1.ValueObject; + if (valueObject != null) + ((GarnetObjectBase)iter1.ValueObject).ClearSerializedObjectData(); } logger?.LogInformation("Completed checkpoint for DB ID: {id}", db.Id); @@ -716,34 +593,20 @@ private static void ExecuteSortedSetCollect(StorageSession storageSession) /// public abstract (long numExpiredKeysFound, long totalRecordsScanned) ExpiredKeyDeletionScan(int dbId); - protected (long numExpiredKeysFound, long totalRecordsScanned) MainStoreExpiredKeyDeletionScan(GarnetDatabase db) + protected (long numExpiredKeysFound, long totalRecordsScanned) StoreExpiredKeyDeletionScan(GarnetDatabase db) { - if (db.MainStoreExpiredKeyDeletionDbStorageSession == null) + if (db.StoreExpiredKeyDeletionDbStorageSession == null) { var scratchBufferManager = new ScratchBufferBuilder(); - db.MainStoreExpiredKeyDeletionDbStorageSession = new StorageSession(StoreWrapper, scratchBufferManager, null, null, db.Id, Logger); + db.StoreExpiredKeyDeletionDbStorageSession = new StorageSession(StoreWrapper, scratchBufferManager, null, null, db.Id, Logger); } var scanFrom = StoreWrapper.store.Log.ReadOnlyAddress; var scanUntil = StoreWrapper.store.Log.TailAddress; - (var deletedCount, var totalCount) = db.MainStoreExpiredKeyDeletionDbStorageSession.MainStoreExpiredKeyDeletionScan(scanFrom, scanUntil); - Logger?.LogDebug("Main Store - Deleted {deletedCount} keys out {totalCount} records in range {scanFrom} to {scanUntil} for DB {id}", deletedCount, totalCount, scanFrom, scanUntil, db.Id); - - return (deletedCount, totalCount); - } - - protected (long numExpiredKeysFound, long totalRecordsScanned) ObjectStoreExpiredKeyDeletionScan(GarnetDatabase db) - { - if (db.ObjectStoreExpiredKeyDeletionDbStorageSession == null) - { - var scratchBufferManager = new ScratchBufferBuilder(); - db.ObjectStoreExpiredKeyDeletionDbStorageSession = new StorageSession(StoreWrapper, scratchBufferManager, null, null, db.Id, Logger); - } - - var scanFrom = StoreWrapper.objectStore.Log.ReadOnlyAddress; - var scanUntil = StoreWrapper.objectStore.Log.TailAddress; - (var deletedCount, var totalCount) = db.ObjectStoreExpiredKeyDeletionDbStorageSession.ObjectStoreExpiredKeyDeletionScan(scanFrom, scanUntil); - Logger?.LogDebug("Object Store - Deleted {deletedCount} keys out {totalCount} records in range {scanFrom} to {scanUntil} for DB {id}", deletedCount, totalCount, scanFrom, scanUntil, db.Id); + var (deletedCount, totalCount) = db.StoreExpiredKeyDeletionDbStorageSession.ExpiredKeyDeletionScan(scanFrom, scanUntil); + Logger?.LogDebug( + "Store - Deleted {deletedCount} keys out {totalCount} records in range {scanFrom} to {scanUntil} for DB {id}", + deletedCount, totalCount, scanFrom, scanUntil, db.Id); return (deletedCount, totalCount); } @@ -753,16 +616,12 @@ private static void ExecuteSortedSetCollect(StorageSession storageSession) protected (HybridLogScanMetrics mainStore, HybridLogScanMetrics objectStore) CollectHybridLogStatsForDb(GarnetDatabase db) { - FunctionsState functionsState = CreateFunctionsState(); - MainSessionFunctions mainStoreSessionFuncs = new MainSessionFunctions(functionsState); - var mainStoreStats = CollectHybridLogStats(db, db.MainStore, mainStoreSessionFuncs); + var functionsState = CreateFunctionsState(); + var mainStoreSessionFunctions = new MainSessionFunctions(functionsState); + var mainStoreStats = CollectHybridLogStats(db, db.Store, mainStoreSessionFunctions); - HybridLogScanMetrics objectStoreStats = null; - if (ObjectStore != null) - { - ObjectSessionFunctions objectSessionFunctions = new ObjectSessionFunctions(functionsState); - objectStoreStats = CollectHybridLogStats(db, db.ObjectStore, objectSessionFunctions); - } + var objectSessionFunctions = new ObjectSessionFunctions(functionsState); + var objectStoreStats = CollectHybridLogStats(db, db.Store, objectSessionFunctions); return (mainStoreStats, objectStoreStats); } @@ -791,7 +650,7 @@ private HybridLogScanMetrics CollectHybridLogStats= db.MainStore.Log.ReadOnlyAddress ? "Mutable" : "Immutable"; + string region = iter.CurrentAddress >= db.Store.Log.ReadOnlyAddress ? "Mutable" : "Immutable"; string state = "Live"; if (iter.Info.IsSealed) { diff --git a/libs/server/Databases/DatabaseManagerFactory.cs b/libs/server/Databases/DatabaseManagerFactory.cs index a1a9bae89a8..12de2907e44 100644 --- a/libs/server/Databases/DatabaseManagerFactory.cs +++ b/libs/server/Databases/DatabaseManagerFactory.cs @@ -37,7 +37,7 @@ private static bool ShouldCreateMultipleDatabaseManager(GarnetServerOptions serv using (createDatabaseDelegate(0)) { // Check if there are multiple databases to recover from checkpoint - var checkpointParentDir = serverOptions.MainStoreCheckpointBaseDirectory; + var checkpointParentDir = serverOptions.StoreCheckpointBaseDirectory; var checkpointDirBaseName = serverOptions.GetCheckpointDirectoryName(0); if (MultiDatabaseManager.TryGetSavedDatabaseIds(checkpointParentDir, checkpointDirBaseName, diff --git a/libs/server/Databases/IDatabaseManager.cs b/libs/server/Databases/IDatabaseManager.cs index a49ddc6a494..709db854b1c 100644 --- a/libs/server/Databases/IDatabaseManager.cs +++ b/libs/server/Databases/IDatabaseManager.cs @@ -10,11 +10,8 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Interface for logical database management @@ -29,12 +26,7 @@ public interface IDatabaseManager : IDisposable /// /// Store (of DB 0) /// - public TsavoriteKV MainStore { get; } - - /// - /// Object store (of DB 0) - /// - public TsavoriteKV ObjectStore { get; } + public TsavoriteKV Store { get; } /// /// AOF (of DB 0) @@ -49,7 +41,7 @@ public interface IDatabaseManager : IDisposable /// /// Object store size tracker (of DB 0) /// - public CacheSizeTracker ObjectStoreSizeTracker { get; } + public CacheSizeTracker SizeTracker { get; } /// /// Version map (of DB 0) @@ -93,9 +85,8 @@ public interface IDatabaseManager : IDisposable /// /// /// - /// /// - public void RecoverCheckpoint(bool replicaRecover = false, bool recoverMainStoreFromToken = false, bool recoverObjectStoreFromToken = false, CheckpointMetadata metadata = null); + public void RecoverCheckpoint(bool replicaRecover = false, bool recoverMainStoreFromToken = false, CheckpointMetadata metadata = null); /// /// Take checkpoint of all active databases if checkpointing is not in progress @@ -191,9 +182,9 @@ public Task TaskCheckpointBasedOnAofSizeLimitAsync(long aofSizeLimit, Cancellati public void ExpiredKeyDeletionScan(); /// - /// Start object size trackers for all active databases + /// Start size trackers for all active databases /// - public void StartObjectSizeTrackers(CancellationToken token = default); + public void StartSizeTrackers(CancellationToken token = default); /// /// Reset diff --git a/libs/server/Databases/MultiDatabaseManager.cs b/libs/server/Databases/MultiDatabaseManager.cs index b5df1be96fb..9759e4a4dc6 100644 --- a/libs/server/Databases/MultiDatabaseManager.cs +++ b/libs/server/Databases/MultiDatabaseManager.cs @@ -87,13 +87,13 @@ public MultiDatabaseManager(SingleDatabaseManager src) : } /// - public override void RecoverCheckpoint(bool replicaRecover = false, bool recoverMainStoreFromToken = false, bool recoverObjectStoreFromToken = false, CheckpointMetadata metadata = null) + public override void RecoverCheckpoint(bool replicaRecover = false, bool recoverMainStoreFromToken = false, CheckpointMetadata metadata = null) { if (replicaRecover) throw new GarnetException( $"Unexpected call to {nameof(MultiDatabaseManager)}.{nameof(RecoverCheckpoint)} with {nameof(replicaRecover)} == true."); - var checkpointParentDir = StoreWrapper.serverOptions.MainStoreCheckpointBaseDirectory; + var checkpointParentDir = StoreWrapper.serverOptions.StoreCheckpointBaseDirectory; var checkpointDirBaseName = StoreWrapper.serverOptions.GetCheckpointDirectoryName(0); int[] dbIdsToRecover; @@ -122,7 +122,7 @@ public override void RecoverCheckpoint(bool replicaRecover = false, bool recover try { - RecoverDatabaseCheckpoint(db, out storeVersion, out objectStoreVersion); + RecoverDatabaseCheckpoint(db, out storeVersion); } catch (TsavoriteNoHybridLogException ex) { @@ -139,14 +139,6 @@ public override void RecoverCheckpoint(bool replicaRecover = false, bool recover if (StoreWrapper.serverOptions.FailOnRecoveryError) throw; } - - // After recovery, we check if store versions match - if (db.ObjectStore != null && storeVersion != objectStoreVersion) - { - Logger?.LogInformation("Main store and object store checkpoint versions do not match; storeVersion = {storeVersion}; objectStoreVersion = {objectStoreVersion}", storeVersion, objectStoreVersion); - if (StoreWrapper.serverOptions.FailOnRecoveryError) - throw new GarnetException("Main store and object store checkpoint versions do not match"); - } } } @@ -210,9 +202,8 @@ public override bool TakeCheckpoint(bool background, int dbId, ILogger logger = { if (t.IsCompletedSuccessfully) { - var storeTailAddress = t.Result.Item1; - var objectStoreTailAddress = t.Result.Item2; - UpdateLastSaveData(dbId, storeTailAddress, objectStoreTailAddress); + var storeTailAddress = t.Result; + UpdateLastSaveData(dbId, storeTailAddress); } } finally @@ -249,9 +240,8 @@ public override async Task TakeOnDemandCheckpointAsync(DateTimeOffset entryTime, // Necessary to take a checkpoint because the latest checkpoint is before entryTime var result = await TakeCheckpointAsync(db, logger: Logger); - var storeTailAddress = result.Item1; - var objectStoreTailAddress = result.Item2; - UpdateLastSaveData(dbId, storeTailAddress, objectStoreTailAddress); + var storeTailAddress = result; + UpdateLastSaveData(dbId, storeTailAddress); } finally { @@ -575,7 +565,7 @@ public override void ExpiredKeyDeletionScan() } /// - public override void StartObjectSizeTrackers(CancellationToken token = default) + public override void StartSizeTrackers(CancellationToken token = default) { sizeTrackersStarted = true; @@ -595,7 +585,7 @@ public override void StartObjectSizeTrackers(CancellationToken token = default) var db = databasesMapSnapshot[dbId]; Debug.Assert(db != null); - db.ObjectStoreSizeTracker?.Start(token); + db.SizeTracker?.Start(token); } } finally @@ -624,8 +614,7 @@ public override void ResetRevivificationStats() for (var i = 0; i < activeDbIdsMapSize; i++) { var dbId = activeDbIdsMapSnapshot[i]; - databaseMapSnapshot[dbId].MainStore.ResetRevivificationStats(); - databaseMapSnapshot[dbId].ObjectStore?.ResetRevivificationStats(); + databaseMapSnapshot[dbId].Store.ResetRevivificationStats(); } } @@ -711,7 +700,7 @@ public override FunctionsState CreateFunctionsState(int dbId = 0, byte respProto if (!success) throw new GarnetException($"Database with ID {dbId} was not found."); - return new(db.AppendOnlyFile, db.VersionMap, StoreWrapper, memoryPool: null, db.ObjectStoreSizeTracker, Logger, respProtocolVersion); + return new(db.AppendOnlyFile, db.VersionMap, StoreWrapper, memoryPool: null, db.SizeTracker, Logger, respProtocolVersion); } /// @@ -931,7 +920,7 @@ private void HandleDatabaseAdded(int dbId) // If size tracker exists and is stopped, start it (only if DB 0 size tracker is started as well) var db = databases.Map[dbId]; if (sizeTrackersStarted) - db.ObjectStoreSizeTracker?.Start(StoreWrapper.ctsCommit.Token); + db.SizeTracker?.Start(StoreWrapper.ctsCommit.Token); activeDbIds.TryGetNextId(out var nextIdx); activeDbIds.TrySetValue(nextIdx, db.Id); @@ -1018,9 +1007,8 @@ private async Task TakeDatabasesCheckpointAsync(int dbIdsCount, ILogger lo if (!t.IsCompletedSuccessfully) return; - var storeTailAddress = t.Result.Item1; - var objectStoreTailAddress = t.Result.Item2; - UpdateLastSaveData(dbId, storeTailAddress, objectStoreTailAddress); + var storeTailAddress = t.Result; + UpdateLastSaveData(dbId, storeTailAddress); }, TaskContinuationOptions.ExecuteSynchronously); } @@ -1038,7 +1026,7 @@ private async Task TakeDatabasesCheckpointAsync(int dbIdsCount, ILogger lo return true; } - private void UpdateLastSaveData(int dbId, long? storeTailAddress, long? objectStoreTailAddress) + private void UpdateLastSaveData(int dbId, long? storeTailAddress) { var databasesMapSnapshot = databases.Map; @@ -1048,9 +1036,6 @@ private void UpdateLastSaveData(int dbId, long? storeTailAddress, long? objectSt if (storeTailAddress.HasValue) { db.LastSaveStoreTailAddress = storeTailAddress.Value; - - if (db.ObjectStore != null && objectStoreTailAddress.HasValue) - db.LastSaveObjectStoreTailAddress = objectStoreTailAddress.Value; } } @@ -1070,11 +1055,7 @@ public override void Dispose() } public override (long numExpiredKeysFound, long totalRecordsScanned) ExpiredKeyDeletionScan(int dbId) - { - var (k1, t1) = MainStoreExpiredKeyDeletionScan(GetDbById(dbId)); - var (k2, t2) = StoreWrapper.serverOptions.DisableObjects ? (0, 0) : ObjectStoreExpiredKeyDeletionScan(GetDbById(dbId)); - return (k1 + k2, t1 + t2); - } + => StoreExpiredKeyDeletionScan(GetDbById(dbId)); private GarnetDatabase GetDbById(int dbId) { diff --git a/libs/server/Databases/SingleDatabaseManager.cs b/libs/server/Databases/SingleDatabaseManager.cs index 0a4b6053713..a7570118a2c 100644 --- a/libs/server/Databases/SingleDatabaseManager.cs +++ b/libs/server/Databases/SingleDatabaseManager.cs @@ -4,7 +4,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Garnet.common; using Garnet.server.Metrics; using Microsoft.Extensions.Logging; using Tsavorite.core; @@ -54,9 +53,9 @@ public override GarnetDatabase TryGetOrAddDatabase(int dbId, out bool success, o } /// - public override void RecoverCheckpoint(bool replicaRecover = false, bool recoverMainStoreFromToken = false, bool recoverObjectStoreFromToken = false, CheckpointMetadata metadata = null) + public override void RecoverCheckpoint(bool replicaRecover = false, bool recoverMainStoreFromToken = false, CheckpointMetadata metadata = null) { - long storeVersion = 0, objectStoreVersion = 0; + long storeVersion = 0; try { if (replicaRecover) @@ -64,49 +63,33 @@ public override void RecoverCheckpoint(bool replicaRecover = false, bool recover // Note: Since replicaRecover only pertains to cluster-mode, we can use the default store pointers (since multi-db mode is disabled in cluster-mode) if (metadata!.storeIndexToken != default && metadata.storeHlogToken != default) { - storeVersion = !recoverMainStoreFromToken ? MainStore.Recover() : MainStore.Recover(metadata.storeIndexToken, metadata.storeHlogToken); + storeVersion = !recoverMainStoreFromToken ? Store.Recover() : Store.Recover(metadata.storeIndexToken, metadata.storeHlogToken); } - if (ObjectStore != null) - { - if (metadata.objectStoreIndexToken != default && metadata.objectStoreHlogToken != default) - { - objectStoreVersion = !recoverObjectStoreFromToken ? ObjectStore.Recover() : ObjectStore.Recover(metadata.objectStoreIndexToken, metadata.objectStoreHlogToken); - } - } - - if (storeVersion > 0 || objectStoreVersion > 0) + if (storeVersion > 0) defaultDatabase.LastSaveTime = DateTimeOffset.UtcNow; } else { - RecoverDatabaseCheckpoint(defaultDatabase, out storeVersion, out objectStoreVersion); + RecoverDatabaseCheckpoint(defaultDatabase, out storeVersion); } } catch (TsavoriteNoHybridLogException ex) { // No hybrid log being found is not the same as an error in recovery. e.g. fresh start Logger?.LogInformation(ex, - "No Hybrid Log found for recovery; storeVersion = {storeVersion}; objectStoreVersion = {objectStoreVersion}", - storeVersion, objectStoreVersion); + "No Hybrid Log found for recovery; storeVersion = {storeVersion};", + storeVersion); } catch (Exception ex) { Logger?.LogInformation(ex, - "Error during recovery of store; storeVersion = {storeVersion}; objectStoreVersion = {objectStoreVersion}", - storeVersion, objectStoreVersion); + "Error during recovery of store; storeVersion = {storeVersion};", + storeVersion); if (StoreWrapper.serverOptions.FailOnRecoveryError) throw; } - - // After recovery, we check if store versions match - if (ObjectStore != null && storeVersion != objectStoreVersion) - { - Logger?.LogInformation("Main store and object store checkpoint versions do not match; storeVersion = {storeVersion}; objectStoreVersion = {objectStoreVersion}", storeVersion, objectStoreVersion); - if (StoreWrapper.serverOptions.FailOnRecoveryError) - throw new GarnetException("Main store and object store checkpoint versions do not match"); - } } /// @@ -139,13 +122,10 @@ public override bool TakeCheckpoint(bool background, ILogger logger = null, Canc { if (t.IsCompletedSuccessfully) { - var storeTailAddress = t.Result.Item1; - var objectStoreTailAddress = t.Result.Item2; + var storeTailAddress = t.Result; if (storeTailAddress.HasValue) defaultDatabase.LastSaveStoreTailAddress = storeTailAddress.Value; - if (ObjectStore != null && objectStoreTailAddress.HasValue) - defaultDatabase.LastSaveObjectStoreTailAddress = objectStoreTailAddress.Value; defaultDatabase.LastSaveTime = DateTimeOffset.UtcNow; } @@ -189,13 +169,10 @@ public override async Task TakeOnDemandCheckpointAsync(DateTimeOffset entryTime, // Necessary to take a checkpoint because the latest checkpoint is before entryTime var result = await TakeCheckpointAsync(defaultDatabase, logger: Logger); - var storeTailAddress = result.Item1; - var objectStoreTailAddress = result.Item2; + var storeTailAddress = result; if (storeTailAddress.HasValue) defaultDatabase.LastSaveStoreTailAddress = storeTailAddress.Value; - if (ObjectStore != null && objectStoreTailAddress.HasValue) - defaultDatabase.LastSaveObjectStoreTailAddress = objectStoreTailAddress.Value; defaultDatabase.LastSaveTime = DateTimeOffset.UtcNow; } @@ -222,13 +199,10 @@ public override async Task TaskCheckpointBasedOnAofSizeLimitAsync(long aofSizeLi { var result = await TakeCheckpointAsync(defaultDatabase, logger: logger, token: token); - var storeTailAddress = result.Item1; - var objectStoreTailAddress = result.Item2; + var storeTailAddress = result; if (storeTailAddress.HasValue) defaultDatabase.LastSaveStoreTailAddress = storeTailAddress.Value; - if (ObjectStore != null && objectStoreTailAddress.HasValue) - defaultDatabase.LastSaveObjectStoreTailAddress = objectStoreTailAddress.Value; defaultDatabase.LastSaveTime = DateTimeOffset.UtcNow; } @@ -307,8 +281,8 @@ public override void ExpiredKeyDeletionScan() => ExpiredKeyDeletionScan(defaultDatabase); /// - public override void StartObjectSizeTrackers(CancellationToken token = default) => - ObjectStoreSizeTracker?.Start(token); + public override void StartSizeTrackers(CancellationToken token = default) => + SizeTracker?.Start(token); /// public override void Reset(int dbId = 0) @@ -320,10 +294,7 @@ public override void Reset(int dbId = 0) /// public override void ResetRevivificationStats() - { - MainStore.ResetRevivificationStats(); - ObjectStore?.ResetRevivificationStats(); - } + => Store.ResetRevivificationStats(); /// public override void EnqueueCommit(AofEntryType entryType, long version, int dbId = 0) @@ -379,7 +350,7 @@ public override FunctionsState CreateFunctionsState(int dbId = 0, byte respProto { ArgumentOutOfRangeException.ThrowIfNotEqual(dbId, 0); - return new(AppendOnlyFile, VersionMap, StoreWrapper, null, ObjectStoreSizeTracker, Logger, respProtocolVersion); + return new(AppendOnlyFile, VersionMap, StoreWrapper, null, SizeTracker, Logger, respProtocolVersion); } private async Task TryPauseCheckpointsContinuousAsync(int dbId, @@ -401,9 +372,7 @@ private async Task TryPauseCheckpointsContinuousAsync(int dbId, public override (long numExpiredKeysFound, long totalRecordsScanned) ExpiredKeyDeletionScan(int dbId) { ArgumentOutOfRangeException.ThrowIfNotEqual(dbId, 0); - var (k1, t1) = MainStoreExpiredKeyDeletionScan(DefaultDatabase); - var (k2, t2) = StoreWrapper.serverOptions.DisableObjects ? (0, 0) : ObjectStoreExpiredKeyDeletionScan(DefaultDatabase); - return (k1 + k2, t1 + t2); + return StoreExpiredKeyDeletionScan(DefaultDatabase); } public override (HybridLogScanMetrics mainStore, HybridLogScanMetrics objectStore)[] CollectHybridLogStats() => [CollectHybridLogStatsForDb(defaultDatabase)]; diff --git a/libs/server/GarnetDatabase.cs b/libs/server/GarnetDatabase.cs index f09b1016681..31b5eccd604 100644 --- a/libs/server/GarnetDatabase.cs +++ b/libs/server/GarnetDatabase.cs @@ -8,11 +8,8 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Represents a logical database in Garnet @@ -31,14 +28,9 @@ public class GarnetDatabase : IDisposable public int Id { get; } /// - /// Main Store - /// - public TsavoriteKV MainStore { get; } - - /// - /// Object Store + /// Store /// - public TsavoriteKV ObjectStore { get; } + public TsavoriteKV Store { get; } /// /// Epoch instance used by server @@ -51,9 +43,9 @@ public class GarnetDatabase : IDisposable public StateMachineDriver StateMachineDriver { get; } /// - /// Size Tracker for Object Store + /// Size Tracker /// - public CacheSizeTracker ObjectStoreSizeTracker { get; } + public CacheSizeTracker SizeTracker { get; } /// /// Device used for AOF logging @@ -71,29 +63,19 @@ public class GarnetDatabase : IDisposable public WatchVersionMap VersionMap { get; } /// - /// Tail address of main store log at last save + /// Tail address of store log at last save /// public long LastSaveStoreTailAddress; - /// - /// Tail address of object store log at last save - /// - public long LastSaveObjectStoreTailAddress; - /// /// Last time checkpoint of database was taken /// public DateTimeOffset LastSaveTime; /// - /// True if database's main store index has maxed-out + /// True if database's store index has maxed-out /// - public bool MainStoreIndexMaxedOut; - - /// - /// True if database's object store index has maxed-out - /// - public bool ObjectStoreIndexMaxedOut; + public bool StoreIndexMaxedOut; /// /// Reader-Writer lock for database checkpointing @@ -103,59 +85,47 @@ public class GarnetDatabase : IDisposable /// /// Storage session intended for store-wide object collection operations /// - internal StorageSession ObjectStoreCollectionDbStorageSession; + internal StorageSession StoreCollectionDbStorageSession; /// - /// Storage session intended for main-store expired key deletion operations + /// Storage session intended for store expired key deletion operations /// - internal StorageSession MainStoreExpiredKeyDeletionDbStorageSession; - - /// - /// Storage session intended for object-store expired key deletion operations - /// - internal StorageSession ObjectStoreExpiredKeyDeletionDbStorageSession; - + internal StorageSession StoreExpiredKeyDeletionDbStorageSession; internal StorageSession HybridLogStatScanStorageSession; bool disposed = false; - public GarnetDatabase(int id, TsavoriteKV mainStore, - TsavoriteKV objectStore, + public GarnetDatabase(int id, TsavoriteKV store, LightEpoch epoch, StateMachineDriver stateMachineDriver, - CacheSizeTracker objectStoreSizeTracker, IDevice aofDevice, TsavoriteLog appendOnlyFile, - bool mainStoreIndexMaxedOut, bool objectStoreIndexMaxedOut) : this() + CacheSizeTracker sizeTracker, IDevice aofDevice, TsavoriteLog appendOnlyFile, + bool storeIndexMaxedOut) : this() { Id = id; - MainStore = mainStore; - ObjectStore = objectStore; + Store = store; Epoch = epoch; StateMachineDriver = stateMachineDriver; - ObjectStoreSizeTracker = objectStoreSizeTracker; + SizeTracker = sizeTracker; AofDevice = aofDevice; AppendOnlyFile = appendOnlyFile; - MainStoreIndexMaxedOut = mainStoreIndexMaxedOut; - ObjectStoreIndexMaxedOut = objectStoreIndexMaxedOut; + StoreIndexMaxedOut = storeIndexMaxedOut; } public GarnetDatabase(int id, GarnetDatabase srcDb, bool enableAof, bool copyLastSaveData = false) : this() { Id = id; - MainStore = srcDb.MainStore; - ObjectStore = srcDb.ObjectStore; + Store = srcDb.Store; Epoch = srcDb.Epoch; StateMachineDriver = srcDb.StateMachineDriver; - ObjectStoreSizeTracker = srcDb.ObjectStoreSizeTracker; + SizeTracker = srcDb.SizeTracker; AofDevice = enableAof ? srcDb.AofDevice : null; AppendOnlyFile = enableAof ? srcDb.AppendOnlyFile : null; - MainStoreIndexMaxedOut = srcDb.MainStoreIndexMaxedOut; - ObjectStoreIndexMaxedOut = srcDb.ObjectStoreIndexMaxedOut; + StoreIndexMaxedOut = srcDb.StoreIndexMaxedOut; if (copyLastSaveData) { LastSaveTime = srcDb.LastSaveTime; LastSaveStoreTailAddress = srcDb.LastSaveStoreTailAddress; - LastSaveObjectStoreTailAddress = srcDb.LastSaveObjectStoreTailAddress; } } @@ -163,7 +133,6 @@ public GarnetDatabase() { VersionMap = new WatchVersionMap(DefaultVersionMapSize); LastSaveStoreTailAddress = 0; - LastSaveObjectStoreTailAddress = 0; LastSaveTime = DateTimeOffset.FromUnixTimeSeconds(0); } @@ -177,20 +146,18 @@ public void Dispose() // Wait for checkpoints to complete and disable checkpointing CheckpointingLock.CloseLock(); - MainStore?.Dispose(); - ObjectStore?.Dispose(); + Store?.Dispose(); AofDevice?.Dispose(); AppendOnlyFile?.Dispose(); - ObjectStoreCollectionDbStorageSession?.Dispose(); - MainStoreExpiredKeyDeletionDbStorageSession?.Dispose(); - ObjectStoreExpiredKeyDeletionDbStorageSession?.Dispose(); + StoreCollectionDbStorageSession?.Dispose(); + StoreExpiredKeyDeletionDbStorageSession?.Dispose(); - if (ObjectStoreSizeTracker != null) + if (SizeTracker != null) { // If tracker has previously started, wait for it to stop - if (!ObjectStoreSizeTracker.TryPreventStart()) + if (!SizeTracker.TryPreventStart()) { - while (!ObjectStoreSizeTracker.Stopped) + while (!SizeTracker.Stopped) Thread.Yield(); } } diff --git a/libs/server/GarnetUnifiedStoreOutput.cs b/libs/server/GarnetUnifiedStoreOutput.cs new file mode 100644 index 00000000000..18e12464659 --- /dev/null +++ b/libs/server/GarnetUnifiedStoreOutput.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Tsavorite.core; + +namespace Garnet.server +{ + /// + /// Output type used by Garnet unified store. + /// Any field / property added to this struct must be set in the back-end (IFunctions) and used in the front-end (GarnetApi caller). + /// That is in order to justify transferring data in this struct through the Tsavorite storage layer. + /// + public struct GarnetUnifiedStoreOutput + { + /// + /// Span byte and memory + /// + public SpanByteAndMemory SpanByteAndMemory; + + /// + /// Output header + /// + public OutputHeader Header; + + /// + /// Output flags + /// + public OutputFlags OutputFlags; + + /// + /// True if output flag RemoveKey is set + /// + public readonly bool HasRemoveKey => + (OutputFlags & OutputFlags.RemoveKey) == OutputFlags.RemoveKey; + + public GarnetUnifiedStoreOutput() => SpanByteAndMemory = new(null); + + public GarnetUnifiedStoreOutput(SpanByteAndMemory span) => SpanByteAndMemory = span; + + public static unsafe GarnetUnifiedStoreOutput FromPinnedPointer(byte* pointer, int length) + => new(new SpanByteAndMemory() { SpanByte = PinnedSpanByte.FromPinnedPointer(pointer, length) }); + + public void ConvertToHeap() + { + // Does not convert to heap when going pending, because we immediately complete pending operations for unified store. + } + } +} \ No newline at end of file diff --git a/libs/server/InputHeader.cs b/libs/server/InputHeader.cs index 81596288cb2..824623ed3a1 100644 --- a/libs/server/InputHeader.cs +++ b/libs/server/InputHeader.cs @@ -427,6 +427,127 @@ public unsafe int DeserializeFrom(byte* src) } } + /// + /// Header for Garnet Main Store inputs + /// + public struct UnifiedStoreInput : IStoreInput + { + /// + /// Common input header for Garnet + /// + public RespInputHeader header; + + /// + /// Argument for generic usage by command implementation + /// + public long arg1; + + /// + /// Session parse state + /// + public SessionParseState parseState; + + /// + /// Create a new instance of UnifiedStoreInput + /// + /// Command + /// Flags + /// General-purpose argument + public UnifiedStoreInput(RespCommand cmd, RespInputFlags flags = 0, long arg1 = 0) + { + this.header = new RespInputHeader(cmd, flags); + this.arg1 = arg1; + } + + /// + /// Create a new instance of UnifiedStoreInput + /// + /// Command + /// Flags + /// General-purpose argument + public UnifiedStoreInput(ushort cmd, byte flags = 0, long arg1 = 0) : + this((RespCommand)cmd, (RespInputFlags)flags, arg1) + + { + } + + /// + /// Create a new instance of UnifiedStoreInput + /// + /// Command + /// Parse state + /// General-purpose argument + /// Flags + public UnifiedStoreInput(RespCommand cmd, ref SessionParseState parseState, long arg1 = 0, RespInputFlags flags = 0) : this(cmd, flags, arg1) + { + this.parseState = parseState; + } + + /// + /// Create a new instance of UnifiedStoreInput + /// + /// Command + /// Parse state + /// First command argument index in parse state + /// General-purpose argument + /// Flags + public UnifiedStoreInput(RespCommand cmd, ref SessionParseState parseState, int startIdx, long arg1 = 0, RespInputFlags flags = 0) : this(cmd, flags, arg1) + { + this.parseState = parseState.Slice(startIdx); + } + + /// + public int SerializedLength => header.SpanByte.TotalSize + + sizeof(long) // arg1 + + parseState.GetSerializedLength(); + + /// + public unsafe int CopyTo(byte* dest, int length) + { + Debug.Assert(length >= this.SerializedLength); + + var curr = dest; + + // Serialize header + header.SpanByte.SerializeTo(curr); + curr += header.SpanByte.TotalSize; + + // Serialize arg1 + *(long*)curr = arg1; + curr += sizeof(long); + + // Serialize parse state + var remainingLength = length - (int)(curr - dest); + var len = parseState.SerializeTo(curr, remainingLength); + curr += len; + + // Serialize length + return (int)(curr - dest); + } + + /// + public unsafe int DeserializeFrom(byte* src) + { + var curr = src; + + // Deserialize header + var header = PinnedSpanByte.FromLengthPrefixedPinnedPointer(curr); + ref var h = ref Unsafe.AsRef(header.ToPointer()); + curr += header.TotalSize; + this.header = h; + + // Deserialize arg1 + arg1 = *(long*)curr; + curr += sizeof(long); + + // Deserialize parse state + var len = parseState.DeserializeFrom(curr); + curr += len; + + return (int)(curr - src); + } + } + /// /// Header for Garnet CustomProcedure inputs /// @@ -494,22 +615,4 @@ public unsafe int DeserializeFrom(byte* src) return len; } } - - /// - /// Object output header (sometimes used as footer) - /// - [StructLayout(LayoutKind.Explicit, Size = Size)] - public struct ObjectOutputHeader - { - /// - /// Expected size of this object - /// - public const int Size = 4; - - /// - /// Some result of operation (e.g., number of items added successfully) - /// - [FieldOffset(0)] - public int result1; - } } \ No newline at end of file diff --git a/libs/server/Lua/LuaRunner.Functions.cs b/libs/server/Lua/LuaRunner.Functions.cs index 03d9717113c..56246344555 100644 --- a/libs/server/Lua/LuaRunner.Functions.cs +++ b/libs/server/Lua/LuaRunner.Functions.cs @@ -227,9 +227,7 @@ internal int UnsafeRunPreambleForSession(nint luaStatePtr) if (txnMode) { - txnKeyEntries.AddKey(key, false, Tsavorite.core.LockType.Exclusive); - if (!respServerSession.storageSession.objectStoreTransactionalContext.IsNull) - txnKeyEntries.AddKey(key, true, Tsavorite.core.LockType.Exclusive); + txnKeyEntries.AddKey(key, LockType.Exclusive); } // Equivalent to KEYS[i+1] = key diff --git a/libs/server/Lua/LuaRunner.cs b/libs/server/Lua/LuaRunner.cs index 310f2ba6fe0..0a6c6ccbd31 100644 --- a/libs/server/Lua/LuaRunner.cs +++ b/libs/server/Lua/LuaRunner.cs @@ -263,7 +263,9 @@ public unsafe LuaRunner( delegate* unmanaged[Cdecl] garnetCall; if (txnMode) { - txnKeyEntries = new TxnKeyEntries(16, respServerSession.storageSession.transactionalContext, respServerSession.storageSession.objectStoreTransactionalContext); + txnKeyEntries = new TxnKeyEntries(16, respServerSession.storageSession.transactionalContext, + respServerSession.storageSession.objectStoreTransactionalContext, + respServerSession.storageSession.unifiedStoreTransactionalContext); garnetCall = &LuaRunnerTrampolines.GarnetCallWithTransaction; } @@ -1237,9 +1239,7 @@ public unsafe object RunForRunner(string[] keys = null, string[] argv = null) foreach (var key in keys) { var _key = scratchBufferBuilder.CreateArgSlice(key); - txnKeyEntries.AddKey(_key, false, Tsavorite.core.LockType.Exclusive); - if (!respServerSession.storageSession.objectStoreTransactionalContext.IsNull) - txnKeyEntries.AddKey(_key, true, Tsavorite.core.LockType.Exclusive); + txnKeyEntries.AddKey(_key, Tsavorite.core.LockType.Exclusive); } adapter = new(scratchBufferBuilder); diff --git a/libs/server/Metrics/Info/GarnetInfoMetrics.cs b/libs/server/Metrics/Info/GarnetInfoMetrics.cs index 59ea3a4150f..fec79cdeb57 100644 --- a/libs/server/Metrics/Info/GarnetInfoMetrics.cs +++ b/libs/server/Metrics/Info/GarnetInfoMetrics.cs @@ -18,9 +18,7 @@ class GarnetInfoMetrics .Where(e => e switch { InfoMetricsType.STOREHASHTABLE => false, - InfoMetricsType.OBJECTSTOREHASHTABLE => false, InfoMetricsType.STOREREVIV => false, - InfoMetricsType.OBJECTSTOREREVIV => false, InfoMetricsType.HLOGSCAN => false, _ => true })]; @@ -31,11 +29,8 @@ class GarnetInfoMetrics MetricsItem[] replicationInfo = null; MetricsItem[] statsInfo = null; MetricsItem[][] storeInfo = null; - MetricsItem[][] objectStoreInfo = null; MetricsItem[][] storeHashDistrInfo = null; - MetricsItem[][] objectStoreHashDistrInfo = null; MetricsItem[][] storeRevivInfo = null; - MetricsItem[][] objectStoreRevivInfo = null; MetricsItem[][] persistenceInfo = null; MetricsItem[] clientsInfo = null; MetricsItem[] keyspaceInfo = null; @@ -65,21 +60,13 @@ private void PopulateServerInfo(StoreWrapper storeWrapper) private void PopulateMemoryInfo(StoreWrapper storeWrapper) { - var main_store_index_size = 0L; - var main_store_log_memory_size = 0L; - var main_store_read_cache_size = 0L; - long total_main_store_size; - - var disableObj = storeWrapper.serverOptions.DisableObjects; - - var initialSize = disableObj ? -1L : 0L; - var object_store_index_size = initialSize; - var object_store_log_memory_size = initialSize; - var object_store_read_cache_log_memory_size = initialSize; - var object_store_heap_memory_target_size = initialSize; - var object_store_heap_memory_size = initialSize; - var object_store_read_cache_heap_memory_size = initialSize; - var total_object_store_size = initialSize; + var store_index_size = 0L; + var store_log_memory_size = 0L; + var store_read_cache_size = 0L; + var store_heap_memory_target_size = 0L; + var store_heap_memory_size = 0L; + var store_read_cache_heap_memory_size = 0L; + long total_store_size; var enableAof = storeWrapper.serverOptions.EnableAOF; var aof_log_memory_size = enableAof ? 0 : -1L; @@ -88,31 +75,19 @@ private void PopulateMemoryInfo(StoreWrapper storeWrapper) foreach (var db in databases) { - main_store_index_size += db.MainStore.IndexSize * 64; - main_store_log_memory_size += db.MainStore.Log.MemorySizeBytes; - main_store_read_cache_size += db.MainStore.ReadCache?.MemorySizeBytes ?? 0; + store_index_size += db.Store.IndexSize * 64; + store_log_memory_size += db.Store.Log.MemorySizeBytes; + store_read_cache_size += db.Store.ReadCache?.MemorySizeBytes ?? 0; aof_log_memory_size += db.AppendOnlyFile?.MemorySizeBytes ?? 0; - if (!disableObj) - { - object_store_index_size += db.ObjectStore.IndexSize * 64; - object_store_log_memory_size += db.ObjectStore.Log.MemorySizeBytes; - object_store_read_cache_log_memory_size += db.ObjectStore.ReadCache?.MemorySizeBytes ?? 0; - object_store_heap_memory_target_size += db.ObjectStoreSizeTracker?.mainLogTracker.TargetSize ?? 0; - object_store_heap_memory_size += db.ObjectStoreSizeTracker?.mainLogTracker.LogHeapSizeBytes ?? 0; - object_store_read_cache_heap_memory_size += db.ObjectStoreSizeTracker?.readCacheTracker?.LogHeapSizeBytes ?? 0; - } + store_heap_memory_target_size += db.SizeTracker?.mainLogTracker.TargetSize ?? 0; + store_heap_memory_size += db.SizeTracker?.mainLogTracker.LogHeapSizeBytes ?? 0; + store_read_cache_heap_memory_size += db.SizeTracker?.readCacheTracker?.LogHeapSizeBytes ?? 0; } - total_main_store_size = main_store_index_size + main_store_log_memory_size + main_store_read_cache_size; - - if (!disableObj) - { - total_object_store_size = object_store_index_size + object_store_log_memory_size + - object_store_read_cache_log_memory_size + object_store_heap_memory_size + - object_store_read_cache_heap_memory_size; - } + total_store_size = store_index_size + store_log_memory_size + store_read_cache_size + + store_heap_memory_size + store_read_cache_heap_memory_size; var gcMemoryInfo = GC.GetGCMemoryInfo(); var gcAvailableMemory = gcMemoryInfo.TotalCommittedBytes - gcMemoryInfo.HeapSizeBytes; @@ -144,17 +119,13 @@ private void PopulateMemoryInfo(StoreWrapper storeWrapper) new("gc_heap_bytes", gcMemoryInfo.HeapSizeBytes.ToString()), new("gc_managed_memory_bytes_excluding_heap", gcAvailableMemory.ToString()), new("gc_fragmented_bytes", gcMemoryInfo.FragmentedBytes.ToString()), - new("main_store_index_size", main_store_index_size.ToString()), - new("main_store_log_memory_size", main_store_log_memory_size.ToString()), - new("main_store_read_cache_size", main_store_read_cache_size.ToString()), - new("total_main_store_size", total_main_store_size.ToString()), - new("object_store_index_size", object_store_index_size.ToString()), - new("object_store_log_memory_size", object_store_log_memory_size.ToString()), - new("object_store_heap_memory_target_size", object_store_heap_memory_target_size.ToString()), - new("object_store_heap_memory_size", object_store_heap_memory_size.ToString()), - new("object_store_read_cache_log_memory_size", object_store_read_cache_log_memory_size.ToString()), - new("object_store_read_cache_heap_memory_size", object_store_read_cache_heap_memory_size.ToString()), - new("total_object_store_size", total_object_store_size.ToString()), + new("store_index_size", store_index_size.ToString()), + new("store_log_memory_size", store_log_memory_size.ToString()), + new("store_read_cache_size", store_read_cache_size.ToString()), + new("total_main_store_size", total_store_size.ToString()), + new("store_heap_memory_target_size", store_heap_memory_target_size.ToString()), + new("store_heap_memory_size", store_heap_memory_size.ToString()), + new("store_read_cache_heap_memory_size", store_read_cache_heap_memory_size.ToString()), new("aof_memory_size", aof_log_memory_size.ToString()) ]; } @@ -179,8 +150,6 @@ private void PopulateReplicationInfo(StoreWrapper storeWrapper) new("second_repl_offset", "N/A"), new("store_current_safe_aof_address", "N/A"), new("store_recovered_safe_aof_address", "N/A"), - new("object_store_current_safe_aof_address", "N/A"), - new("object_store_recovered_safe_aof_address", "N/A") ]; } else @@ -244,60 +213,25 @@ private void PopulateStoreStats(StoreWrapper storeWrapper) private MetricsItem[] GetDatabaseStoreStats(StoreWrapper storeWrapper, GarnetDatabase db) => [ - new($"CurrentVersion", db.MainStore.CurrentVersion.ToString()), - new($"LastCheckpointedVersion", db.MainStore.LastCheckpointedVersion.ToString()), - new($"SystemState", db.MainStore.SystemState.ToString()), - new($"IndexSize", db.MainStore.IndexSize.ToString()), - new($"LogDir", storeWrapper.serverOptions.LogDir), - new($"Log.BeginAddress", db.MainStore.Log.BeginAddress.ToString()), - new($"Log.BufferSize", db.MainStore.Log.BufferSize.ToString()), - new($"Log.EmptyPageCount", db.MainStore.Log.EmptyPageCount.ToString()), - new($"Log.MinEmptyPageCount", db.MainStore.Log.MinEmptyPageCount.ToString()), - new($"Log.HeadAddress", db.MainStore.Log.HeadAddress.ToString()), - new($"Log.MemorySizeBytes", db.MainStore.Log.MemorySizeBytes.ToString()), - new($"Log.SafeReadOnlyAddress", db.MainStore.Log.SafeReadOnlyAddress.ToString()), - new($"Log.TailAddress", db.MainStore.Log.TailAddress.ToString()), - new($"ReadCache.BeginAddress", db.MainStore.ReadCache?.BeginAddress.ToString() ?? "N/A"), - new($"ReadCache.BufferSize", db.MainStore.ReadCache?.BufferSize.ToString() ?? "N/A"), - new($"ReadCache.EmptyPageCount", db.MainStore.ReadCache?.EmptyPageCount.ToString() ?? "N/A"), - new($"ReadCache.HeadAddress", db.MainStore.ReadCache?.HeadAddress.ToString() ?? "N/A"), - new($"ReadCache.MemorySizeBytes", db.MainStore.ReadCache?.MemorySizeBytes.ToString() ?? "N/A"), - new($"ReadCache.TailAddress", db.MainStore.ReadCache?.TailAddress.ToString() ?? "N/A"), - ]; - - private void PopulateObjectStoreStats(StoreWrapper storeWrapper) - { - var databases = storeWrapper.GetDatabasesSnapshot(); - - objectStoreInfo = new MetricsItem[storeWrapper.MaxDatabaseId + 1][]; - foreach (var db in databases) - { - var storeStats = GetDatabaseObjectStoreStats(storeWrapper, db); - objectStoreInfo[db.Id] = storeStats; - } - } - - private MetricsItem[] GetDatabaseObjectStoreStats(StoreWrapper storeWrapper, GarnetDatabase db) => - [ - new($"CurrentVersion", db.ObjectStore.CurrentVersion.ToString()), - new($"LastCheckpointedVersion", db.ObjectStore.LastCheckpointedVersion.ToString()), - new($"SystemState", db.ObjectStore.SystemState.ToString()), - new($"IndexSize", db.ObjectStore.IndexSize.ToString()), + new($"CurrentVersion", db.Store.CurrentVersion.ToString()), + new($"LastCheckpointedVersion", db.Store.LastCheckpointedVersion.ToString()), + new($"SystemState", db.Store.SystemState.ToString()), + new($"IndexSize", db.Store.IndexSize.ToString()), new($"LogDir", storeWrapper.serverOptions.LogDir), - new($"Log.BeginAddress", db.ObjectStore.Log.BeginAddress.ToString()), - new($"Log.BufferSize", db.ObjectStore.Log.BufferSize.ToString()), - new($"Log.EmptyPageCount", db.ObjectStore.Log.EmptyPageCount.ToString()), - new($"Log.MinEmptyPageCount", db.ObjectStore.Log.MinEmptyPageCount.ToString()), - new($"Log.HeadAddress", db.ObjectStore.Log.HeadAddress.ToString()), - new($"Log.MemorySizeBytes", db.ObjectStore.Log.MemorySizeBytes.ToString()), - new($"Log.SafeReadOnlyAddress", db.ObjectStore.Log.SafeReadOnlyAddress.ToString()), - new($"Log.TailAddress", db.ObjectStore.Log.TailAddress.ToString()), - new($"ReadCache.BeginAddress", db.ObjectStore.ReadCache?.BeginAddress.ToString() ?? "N/A"), - new($"ReadCache.BufferSize", db.ObjectStore.ReadCache?.BufferSize.ToString() ?? "N/A"), - new($"ReadCache.EmptyPageCount", db.ObjectStore.ReadCache?.EmptyPageCount.ToString() ?? "N/A"), - new($"ReadCache.HeadAddress", db.ObjectStore.ReadCache?.HeadAddress.ToString() ?? "N/A"), - new($"ReadCache.MemorySizeBytes", db.ObjectStore.ReadCache?.MemorySizeBytes.ToString() ?? "N/A"), - new($"ReadCache.TailAddress", db.ObjectStore.ReadCache?.TailAddress.ToString() ?? "N/A"), + new($"Log.BeginAddress", db.Store.Log.BeginAddress.ToString()), + new($"Log.BufferSize", db.Store.Log.BufferSize.ToString()), + new($"Log.EmptyPageCount", db.Store.Log.EmptyPageCount.ToString()), + new($"Log.MinEmptyPageCount", db.Store.Log.MinEmptyPageCount.ToString()), + new($"Log.HeadAddress", db.Store.Log.HeadAddress.ToString()), + new($"Log.MemorySizeBytes", db.Store.Log.MemorySizeBytes.ToString()), + new($"Log.SafeReadOnlyAddress", db.Store.Log.SafeReadOnlyAddress.ToString()), + new($"Log.TailAddress", db.Store.Log.TailAddress.ToString()), + new($"ReadCache.BeginAddress", db.Store.ReadCache?.BeginAddress.ToString() ?? "N/A"), + new($"ReadCache.BufferSize", db.Store.ReadCache?.BufferSize.ToString() ?? "N/A"), + new($"ReadCache.EmptyPageCount", db.Store.ReadCache?.EmptyPageCount.ToString() ?? "N/A"), + new($"ReadCache.HeadAddress", db.Store.ReadCache?.HeadAddress.ToString() ?? "N/A"), + new($"ReadCache.MemorySizeBytes", db.Store.ReadCache?.MemorySizeBytes.ToString() ?? "N/A"), + new($"ReadCache.TailAddress", db.Store.ReadCache?.TailAddress.ToString() ?? "N/A"), ]; private void PopulateStoreHashDistribution(StoreWrapper storeWrapper) @@ -307,18 +241,7 @@ private void PopulateStoreHashDistribution(StoreWrapper storeWrapper) storeHashDistrInfo = new MetricsItem[storeWrapper.MaxDatabaseId + 1][]; foreach (var db in databases) { - storeHashDistrInfo[db.Id] = [new("", db.MainStore.DumpDistribution())]; - } - } - - private void PopulateObjectStoreHashDistribution(StoreWrapper storeWrapper) - { - var databases = storeWrapper.GetDatabasesSnapshot(); - - objectStoreHashDistrInfo = new MetricsItem[storeWrapper.MaxDatabaseId + 1][]; - foreach (var db in databases) - { - objectStoreHashDistrInfo[db.Id] = [new("", db.ObjectStore.DumpDistribution())]; + storeHashDistrInfo[db.Id] = [new("", db.Store.DumpDistribution())]; } } @@ -329,18 +252,7 @@ private void PopulateStoreRevivInfo(StoreWrapper storeWrapper) storeRevivInfo = new MetricsItem[storeWrapper.MaxDatabaseId + 1][]; foreach (var db in databases) { - storeRevivInfo[db.Id] = [new("", db.MainStore.DumpRevivificationStats())]; - } - } - - private void PopulateObjectStoreRevivInfo(StoreWrapper storeWrapper) - { - var databases = storeWrapper.GetDatabasesSnapshot(); - - objectStoreRevivInfo = new MetricsItem[storeWrapper.MaxDatabaseId + 1][]; - foreach (var db in databases) - { - objectStoreRevivInfo[db.Id] = [new("", db.ObjectStore.DumpRevivificationStats())]; + storeRevivInfo[db.Id] = [new("", db.Store.DumpRevivificationStats())]; } } @@ -430,11 +342,8 @@ public static string GetSectionHeader(InfoMetricsType infoType, int dbId) InfoMetricsType.REPLICATION => "Replication", InfoMetricsType.STATS => "Stats", InfoMetricsType.STORE => $"MainStore_DB_{dbId}", - InfoMetricsType.OBJECTSTORE => $"ObjectStore_DB_{dbId}", InfoMetricsType.STOREHASHTABLE => $"MainStoreHashTableDistribution_DB_{dbId}", - InfoMetricsType.OBJECTSTOREHASHTABLE => $"ObjectStoreHashTableDistribution_DB_{dbId}", InfoMetricsType.STOREREVIV => $"MainStoreDeletedRecordRevivification_DB_{dbId}", - InfoMetricsType.OBJECTSTOREREVIV => $"ObjectStoreDeletedRecordRevivification_DB_{dbId}", InfoMetricsType.PERSISTENCE => $"Persistence_DB_{dbId}", InfoMetricsType.CLIENTS => "Clients", InfoMetricsType.KEYSPACE => "Keyspace", @@ -495,32 +404,14 @@ private void GetRespInfo(InfoMetricsType section, int dbId, StoreWrapper storeWr PopulateStoreStats(storeWrapper); GetSectionRespInfo(header, storeInfo[dbId], sbResponse); return; - case InfoMetricsType.OBJECTSTORE: - if (storeWrapper.serverOptions.DisableObjects) - return; - PopulateObjectStoreStats(storeWrapper); - GetSectionRespInfo(header, objectStoreInfo[dbId], sbResponse); - return; case InfoMetricsType.STOREHASHTABLE: PopulateStoreHashDistribution(storeWrapper); GetSectionRespInfo(header, storeHashDistrInfo[dbId], sbResponse); return; - case InfoMetricsType.OBJECTSTOREHASHTABLE: - if (storeWrapper.serverOptions.DisableObjects) - return; - PopulateObjectStoreHashDistribution(storeWrapper); - GetSectionRespInfo(header, objectStoreHashDistrInfo[dbId], sbResponse); - return; case InfoMetricsType.STOREREVIV: PopulateStoreRevivInfo(storeWrapper); GetSectionRespInfo(header, storeRevivInfo[dbId], sbResponse); return; - case InfoMetricsType.OBJECTSTOREREVIV: - if (storeWrapper.serverOptions.DisableObjects) - return; - PopulateObjectStoreRevivInfo(storeWrapper); - GetSectionRespInfo(header, objectStoreRevivInfo[dbId], sbResponse); - return; case InfoMetricsType.PERSISTENCE: if (!storeWrapper.serverOptions.EnableAOF) return; @@ -591,27 +482,12 @@ private MetricsItem[] GetMetricInternal(InfoMetricsType section, int dbId, Store case InfoMetricsType.STORE: PopulateStoreStats(storeWrapper); return storeInfo[dbId]; - case InfoMetricsType.OBJECTSTORE: - if (storeWrapper.serverOptions.DisableObjects) - return null; - PopulateObjectStoreStats(storeWrapper); - return objectStoreInfo[dbId]; case InfoMetricsType.STOREHASHTABLE: PopulateStoreHashDistribution(storeWrapper); return storeHashDistrInfo[dbId]; - case InfoMetricsType.OBJECTSTOREHASHTABLE: - if (storeWrapper.serverOptions.DisableObjects) - return null; - PopulateObjectStoreHashDistribution(storeWrapper); - return objectStoreHashDistrInfo[dbId]; case InfoMetricsType.STOREREVIV: PopulateStoreRevivInfo(storeWrapper); return storeRevivInfo[dbId]; - case InfoMetricsType.OBJECTSTOREREVIV: - if (storeWrapper.serverOptions.DisableObjects) - return null; - PopulateObjectStoreRevivInfo(storeWrapper); - return objectStoreRevivInfo[dbId]; case InfoMetricsType.PERSISTENCE: if (!storeWrapper.serverOptions.EnableAOF) return null; diff --git a/libs/server/Metrics/Info/InfoHelp.cs b/libs/server/Metrics/Info/InfoHelp.cs index 47cc24a3460..656f71c5015 100644 --- a/libs/server/Metrics/Info/InfoHelp.cs +++ b/libs/server/Metrics/Info/InfoHelp.cs @@ -23,11 +23,8 @@ public static List GetInfoTypeHelpMessage() $"{nameof(InfoMetricsType.REPLICATION)}: Replication info.", $"{nameof(InfoMetricsType.STATS)}: General server operational stats.", $"{nameof(InfoMetricsType.STORE)}: Main store operational information.", - $"{nameof(InfoMetricsType.OBJECTSTORE)}: Object store operational information.", $"{nameof(InfoMetricsType.STOREHASHTABLE)}: Hash table distribution info for main store (expensive, not returned by default).", - $"{nameof(InfoMetricsType.OBJECTSTOREHASHTABLE)}: Hash table distribution info for object store (expensive, not returned by default).", $"{nameof(InfoMetricsType.STOREREVIV)}: Revivification info for deleted records in main store (not returned by default).", - $"{nameof(InfoMetricsType.OBJECTSTOREREVIV)}: Record revivification info for deleted records in object store (not returned by default).", $"{nameof(InfoMetricsType.PERSISTENCE)}: Persistence related information (i.e. Checkpoint and AOF).", $"{nameof(InfoMetricsType.CLIENTS)}: Information related to client connections.", $"{nameof(InfoMetricsType.KEYSPACE)}: Database related statistics.", diff --git a/libs/server/Objects/Hash/HashObject.cs b/libs/server/Objects/Hash/HashObject.cs index 1fdd4885095..0eaf71735b0 100644 --- a/libs/server/Objects/Hash/HashObject.cs +++ b/libs/server/Objects/Hash/HashObject.cs @@ -189,7 +189,7 @@ public override bool Operate(ref ObjectInput input, ref GarnetObjectStoreOutput if (input.header.type != GarnetObjectType.Hash) { //Indicates when there is an incorrect type - output.OutputFlags |= ObjectStoreOutputFlags.WrongType; + output.OutputFlags |= OutputFlags.WrongType; output.SpanByteAndMemory.Length = 0; return true; } @@ -264,7 +264,7 @@ public override bool Operate(ref ObjectInput input, ref GarnetObjectStoreOutput memorySizeChange = this.HeapMemorySize - previousMemorySize; if (hash.Count == 0) - output.OutputFlags |= ObjectStoreOutputFlags.RemoveKey; + output.OutputFlags |= OutputFlags.RemoveKey; return true; } diff --git a/libs/server/Objects/ItemBroker/CollectionItemBroker.cs b/libs/server/Objects/ItemBroker/CollectionItemBroker.cs index b7fbae5a2f5..78a8f0011e7 100644 --- a/libs/server/Objects/ItemBroker/CollectionItemBroker.cs +++ b/libs/server/Objects/ItemBroker/CollectionItemBroker.cs @@ -529,16 +529,17 @@ private unsafe bool TryGetResult(byte[] key, StorageSession storageSession, Resp { Debug.Assert(storageSession.txnManager.state == TxnState.None); createTransaction = true; - storageSession.txnManager.SaveKeyEntryToLock(asKey, true, LockType.Exclusive); + storageSession.txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object | TransactionStoreTypes.Unified); + storageSession.txnManager.SaveKeyEntryToLock(asKey, LockType.Exclusive); if (command == RespCommand.BLMOVE) - storageSession.txnManager.SaveKeyEntryToLock(dstKey, true, LockType.Exclusive); + storageSession.txnManager.SaveKeyEntryToLock(dstKey, LockType.Exclusive); _ = storageSession.txnManager.Run(true); } - var transactionalContext = storageSession.txnManager.TransactionalContext; var objectTransactionalContext = storageSession.txnManager.ObjectStoreTransactionalContext; + var unifiedTransactionalContext = storageSession.txnManager.UnifiedStoreTransactionalContext; try { @@ -641,8 +642,8 @@ private unsafe bool TryGetResult(byte[] key, StorageSession storageSession, Resp if (isSuccessful && listObj.LnkList.Count == 0) { - _ = storageSession.EXPIRE(asKey, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, - ref transactionalContext, ref objectTransactionalContext); + _ = storageSession.EXPIRE(asKey, TimeSpan.Zero, out _, ExpireOption.None, + ref unifiedTransactionalContext); } return isSuccessful; @@ -655,8 +656,8 @@ private unsafe bool TryGetResult(byte[] key, StorageSession storageSession, Resp if (isSuccessful && sortedSetObj.Count() == 0) { - _ = storageSession.EXPIRE(asKey, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, - ref transactionalContext, ref objectTransactionalContext); + _ = storageSession.EXPIRE(asKey, TimeSpan.Zero, out _, ExpireOption.None, + ref unifiedTransactionalContext); } return isSuccessful; diff --git a/libs/server/Objects/List/ListObject.cs b/libs/server/Objects/List/ListObject.cs index b3ed0878043..1a9b333d51c 100644 --- a/libs/server/Objects/List/ListObject.cs +++ b/libs/server/Objects/List/ListObject.cs @@ -136,7 +136,7 @@ public override bool Operate(ref ObjectInput input, ref GarnetObjectStoreOutput if (input.header.type != GarnetObjectType.List) { // Indicates an incorrect type of key - output.OutputFlags |= ObjectStoreOutputFlags.WrongType; + output.OutputFlags |= OutputFlags.WrongType; output.SpanByteAndMemory.Length = 0; return true; } @@ -190,7 +190,7 @@ public override bool Operate(ref ObjectInput input, ref GarnetObjectStoreOutput memorySizeChange = this.HeapMemorySize - previousMemorySize; if (list.Count == 0) - output.OutputFlags |= ObjectStoreOutputFlags.RemoveKey; + output.OutputFlags |= OutputFlags.RemoveKey; return true; } diff --git a/libs/server/Objects/Set/SetObject.cs b/libs/server/Objects/Set/SetObject.cs index 19c09061fe9..76a27c0959f 100644 --- a/libs/server/Objects/Set/SetObject.cs +++ b/libs/server/Objects/Set/SetObject.cs @@ -130,7 +130,7 @@ public override bool Operate(ref ObjectInput input, ref GarnetObjectStoreOutput if (input.header.type != GarnetObjectType.Set) { // Indicates an incorrect type of key - output.OutputFlags |= ObjectStoreOutputFlags.WrongType; + output.OutputFlags |= OutputFlags.WrongType; output.SpanByteAndMemory.Length = 0; return true; } @@ -172,7 +172,7 @@ public override bool Operate(ref ObjectInput input, ref GarnetObjectStoreOutput memorySizeChange = this.HeapMemorySize - prevMemorySize; if (Set.Count == 0) - output.OutputFlags |= ObjectStoreOutputFlags.RemoveKey; + output.OutputFlags |= OutputFlags.RemoveKey; return true; } diff --git a/libs/server/Objects/SortedSet/SortedSetObject.cs b/libs/server/Objects/SortedSet/SortedSetObject.cs index 21c5b4c16ce..8adda584c6b 100644 --- a/libs/server/Objects/SortedSet/SortedSetObject.cs +++ b/libs/server/Objects/SortedSet/SortedSetObject.cs @@ -327,7 +327,7 @@ public override bool Operate(ref ObjectInput input, ref GarnetObjectStoreOutput if (header.type != GarnetObjectType.SortedSet) { // Indicates an incorrect type of key - output.OutputFlags |= ObjectStoreOutputFlags.WrongType; + output.OutputFlags |= OutputFlags.WrongType; output.SpanByteAndMemory.Length = 0; return true; } @@ -421,7 +421,7 @@ public override bool Operate(ref ObjectInput input, ref GarnetObjectStoreOutput memorySizeChange = this.HeapMemorySize - prevMemorySize; if (sortedSetDict.Count == 0) - output.OutputFlags |= ObjectStoreOutputFlags.RemoveKey; + output.OutputFlags |= OutputFlags.RemoveKey; return true; } diff --git a/libs/server/Objects/Types/GarnetObject.cs b/libs/server/Objects/Types/GarnetObject.cs index b9fc4abf680..c99f91aa957 100644 --- a/libs/server/Objects/Types/GarnetObject.cs +++ b/libs/server/Objects/Types/GarnetObject.cs @@ -74,8 +74,6 @@ internal static bool NeedToCreate(RespInputHeader header) HashOperation.HCOLLECT => false, _ => true, }, - GarnetObjectType.Expire => false, - GarnetObjectType.Persist => false, GarnetObjectType.Migrate => false, _ => true, }; diff --git a/libs/server/Objects/Types/GarnetObjectStoreOutput.cs b/libs/server/Objects/Types/GarnetObjectStoreOutput.cs index 0547c6042b4..856fa5225b5 100644 --- a/libs/server/Objects/Types/GarnetObjectStoreOutput.cs +++ b/libs/server/Objects/Types/GarnetObjectStoreOutput.cs @@ -1,33 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System; using Tsavorite.core; namespace Garnet.server { - /// - /// Flags for object store outputs. - /// - [Flags] - public enum ObjectStoreOutputFlags : byte - { - /// - /// No flags set - /// - None = 0, - - /// - /// Remove key - /// - RemoveKey = 1, - - /// - /// Wrong type of object - /// - WrongType = 1 << 1, - } - /// /// Output type used by Garnet object store. /// Any field / property added to this struct must be set in the back-end (IFunctions) and used in the front-end (GarnetApi caller). @@ -46,28 +23,30 @@ public struct GarnetObjectStoreOutput public IGarnetObject GarnetObject; /// - /// Object header + /// Output header /// - public ObjectOutputHeader Header; + public OutputHeader Header; /// /// Output flags /// - public ObjectStoreOutputFlags OutputFlags; + public OutputFlags OutputFlags; /// /// True if output flag WrongType is set /// - public readonly bool HasWrongType => (OutputFlags & ObjectStoreOutputFlags.WrongType) == ObjectStoreOutputFlags.WrongType; + public readonly bool HasWrongType => + (OutputFlags & OutputFlags.WrongType) == OutputFlags.WrongType; /// /// True if output flag RemoveKey is set /// - public readonly bool HasRemoveKey => (OutputFlags & ObjectStoreOutputFlags.RemoveKey) == ObjectStoreOutputFlags.RemoveKey; + public readonly bool HasRemoveKey => + (OutputFlags & OutputFlags.RemoveKey) == OutputFlags.RemoveKey; public GarnetObjectStoreOutput() => SpanByteAndMemory = new(null); - public GarnetObjectStoreOutput(SpanByteAndMemory spam) => SpanByteAndMemory = spam; + public GarnetObjectStoreOutput(SpanByteAndMemory span) => SpanByteAndMemory = span; public static unsafe GarnetObjectStoreOutput FromPinnedPointer(byte* pointer, int length) => new(new SpanByteAndMemory() { SpanByte = PinnedSpanByte.FromPinnedPointer(pointer, length) }); diff --git a/libs/server/Objects/Types/GarnetObjectType.cs b/libs/server/Objects/Types/GarnetObjectType.cs index 2960e75a5d3..df5d77bcb4a 100644 --- a/libs/server/Objects/Types/GarnetObjectType.cs +++ b/libs/server/Objects/Types/GarnetObjectType.cs @@ -43,21 +43,6 @@ public enum GarnetObjectType : byte /// DelIfExpIm = 0xf7, - /// - /// Special type indicating PEXPIRE command - /// - PExpire = 0xf8, - - /// - /// Special type indicating EXPIRETIME command - /// - ExpireTime = 0xf9, - - /// - /// Special type indicating PEXPIRETIME command - /// - PExpireTime = 0xfa, - /// /// Indicating a Custom Object command /// @@ -68,20 +53,10 @@ public enum GarnetObjectType : byte /// PTtl = 0xfc, - /// - /// Special type indicating PERSIST command - /// - Persist = 0xfd, - /// /// Special type indicating TTL command /// Ttl = 0xfe, - - /// - /// Special type indicating EXPIRE command - /// - Expire = 0xff, } public static class GarnetObjectTypeExtensions diff --git a/libs/server/OutputHeader.cs b/libs/server/OutputHeader.cs new file mode 100644 index 00000000000..bb45baf3ba8 --- /dev/null +++ b/libs/server/OutputHeader.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace Garnet.server +{ + /// + /// Flags for store outputs. + /// + [Flags] + public enum OutputFlags : byte + { + /// + /// No flags set + /// + None = 0, + + /// + /// Remove key + /// + RemoveKey = 1, + + /// + /// Wrong type of value + /// + WrongType = 1 << 1, + } + + /// + /// Object output header (sometimes used as footer) + /// + [StructLayout(LayoutKind.Explicit, Size = Size)] + public struct OutputHeader + { + /// + /// Expected size of this struct + /// + public const int Size = 4; + + /// + /// Some result of operation (e.g., number of items added successfully) + /// + [FieldOffset(0)] + public int result1; + } +} \ No newline at end of file diff --git a/libs/server/Providers/GarnetProvider.cs b/libs/server/Providers/GarnetProvider.cs index e8b53202805..b72d4160f01 100644 --- a/libs/server/Providers/GarnetProvider.cs +++ b/libs/server/Providers/GarnetProvider.cs @@ -8,13 +8,13 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Session provider for Garnet /// - public sealed class GarnetProvider : TsavoriteKVProviderBase + public sealed class GarnetProvider : TsavoriteKVProviderBase { readonly StoreWrapper storeWrapper; diff --git a/libs/server/Resp/ArrayCommands.cs b/libs/server/Resp/ArrayCommands.cs index 8d6dda4dc75..4dab5fca5a0 100644 --- a/libs/server/Resp/ArrayCommands.cs +++ b/libs/server/Resp/ArrayCommands.cs @@ -194,10 +194,11 @@ private bool NetworkDEL(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { int keysDeleted = 0; + for (int c = 0; c < parseState.Count; c++) { var key = parseState.GetArgSliceByRef(c); - var status = storageApi.DELETE(key, StoreType.All); + var status = storageApi.DELETE(key); // This is only an approximate count because the deletion of a key on disk is performed as a blind tombstone append if (status == GarnetStatus.OK) @@ -438,16 +439,22 @@ private bool NetworkTYPE(ref TGarnetApi storageApi) // TYPE key var keySlice = parseState.GetArgSliceByRef(0); - var status = storageApi.GetKeyType(keySlice, out var typeName); + // Prepare input + var input = new UnifiedStoreInput(RespCommand.TYPE); + + // Prepare GarnetUnifiedStoreOutput output + var output = GarnetUnifiedStoreOutput.FromPinnedPointer(dcurr, (int)(dend - dcurr)); + + + var status = storageApi.TYPE(keySlice, ref input, ref output); if (status == GarnetStatus.OK) { - while (!RespWriteUtils.TryWriteSimpleString(typeName, ref dcurr, dend)) - SendAndReset(); + ProcessOutput(output.SpanByteAndMemory); } else { - while (!RespWriteUtils.TryWriteSimpleString("none"u8, ref dcurr, dend)) + while (!RespWriteUtils.TryWriteSimpleString(CmdStrings.none, ref dcurr, dend)) SendAndReset(); } diff --git a/libs/server/Resp/BasicCommands.cs b/libs/server/Resp/BasicCommands.cs index 5accfc9f569..922b58b7214 100644 --- a/libs/server/Resp/BasicCommands.cs +++ b/libs/server/Resp/BasicCommands.cs @@ -1160,23 +1160,21 @@ private bool NetworkCOMMAND_GETKEYS() } var cmdName = parseState.GetString(0); - bool cmdFound = RespCommandsInfo.TryGetRespCommandInfo(cmdName, out var cmdInfo, true, true, logger) || - storeWrapper.customCommandManager.TryGetCustomCommandInfo(cmdName, out cmdInfo); - if (!cmdFound) - { + // Try to parse command and get its simplified info + if (!TryGetSimpleCommandInfo(cmdName, out var simpleCmdInfo)) return AbortWithErrorMessage(CmdStrings.RESP_INVALID_COMMAND_SPECIFIED); - } - if (cmdInfo.KeySpecifications == null || cmdInfo.KeySpecifications.Length == 0) - { + // If command has no key specifications, abort with error + if (simpleCmdInfo.KeySpecs == null || simpleCmdInfo.KeySpecs.Length == 0) return AbortWithErrorMessage(CmdStrings.RESP_COMMAND_HAS_NO_KEY_ARGS); - } - parseState.TryExtractKeysFromSpecs(cmdInfo.KeySpecifications, out var keys); + // Extract command keys from parse state and key specification + // An offset is applied to the parse state, as the command (and possibly subcommand) are included in the parse state. + var slicedParseState = parseState.Slice(simpleCmdInfo.IsSubCommand ? 2 : 1); + var keys = slicedParseState.ExtractCommandKeys(simpleCmdInfo); - - while (!RespWriteUtils.TryWriteArrayLength(keys.Count, ref dcurr, dend)) + while (!RespWriteUtils.TryWriteArrayLength(keys.Length, ref dcurr, dend)) SendAndReset(); foreach (var key in keys) @@ -1199,35 +1197,35 @@ private bool NetworkCOMMAND_GETKEYSANDFLAGS() } var cmdName = parseState.GetString(0); - bool cmdFound = RespCommandsInfo.TryGetRespCommandInfo(cmdName, out var cmdInfo, true, true, logger) || - storeWrapper.customCommandManager.TryGetCustomCommandInfo(cmdName, out cmdInfo); - if (!cmdFound) - { + // Try to parse command and get its simplified info + if (!TryGetSimpleCommandInfo(cmdName, out var simpleCmdInfo)) return AbortWithErrorMessage(CmdStrings.RESP_INVALID_COMMAND_SPECIFIED); - } - if (cmdInfo.KeySpecifications == null || cmdInfo.KeySpecifications.Length == 0) - { + // If command has no key specifications, abort with error + if (simpleCmdInfo.KeySpecs == null || simpleCmdInfo.KeySpecs.Length == 0) return AbortWithErrorMessage(CmdStrings.RESP_COMMAND_HAS_NO_KEY_ARGS); - } - parseState.TryExtractKeysAndFlagsFromSpecs(cmdInfo.KeySpecifications, out var keys, out var flags); + // Extract command keys from parse state and key specification + // An offset is applied to the parse state, as the command (and possibly subcommand) are included in the parse state. + var slicedParseState = parseState.Slice(simpleCmdInfo.IsSubCommand ? 2 : 1); + var keysAndFlags = slicedParseState.ExtractCommandKeysAndFlags(simpleCmdInfo); - while (!RespWriteUtils.TryWriteArrayLength(keys.Count, ref dcurr, dend)) + while (!RespWriteUtils.TryWriteArrayLength(keysAndFlags.Length, ref dcurr, dend)) SendAndReset(); - for (int i = 0; i < keys.Count; i++) + for (var i = 0; i < keysAndFlags.Length; i++) { while (!RespWriteUtils.TryWriteArrayLength(2, ref dcurr, dend)) SendAndReset(); - while (!RespWriteUtils.TryWriteBulkString(keys[i].Span, ref dcurr, dend)) + while (!RespWriteUtils.TryWriteBulkString(keysAndFlags[i].Item1.Span, ref dcurr, dend)) SendAndReset(); - WriteSetLength(flags[i].Length); + var flags = EnumUtils.GetEnumDescriptions(keysAndFlags[i].Item2); + WriteSetLength(flags.Length); - foreach (var flag in flags[i]) + foreach (var flag in flags) { while (!RespWriteUtils.TryWriteBulkString(Encoding.ASCII.GetBytes(flag), ref dcurr, dend)) SendAndReset(); @@ -1433,12 +1431,17 @@ private bool NetworkMemoryUsage(ref TGarnetApi storageApi) } } - var status = storageApi.MemoryUsageForKey(key, out var memoryUsage); + // Prepare input + var input = new UnifiedStoreInput(RespCommand.MEMORY_USAGE); + + // Prepare GarnetUnifiedStoreOutput output + var output = GarnetUnifiedStoreOutput.FromPinnedPointer(dcurr, (int)(dend - dcurr)); + + var status = storageApi.MEMORYUSAGE(key, ref input, ref output); if (status == GarnetStatus.OK) { - while (!RespWriteUtils.TryWriteInt32((int)memoryUsage, ref dcurr, dend)) - SendAndReset(); + ProcessOutput(output.SpanByteAndMemory); } else { @@ -1747,6 +1750,29 @@ bool ParseGETAndKey(ref PinnedSpanByte key) return true; } + private bool TryGetSimpleCommandInfo(string cmdName, out SimpleRespCommandInfo simpleCmdInfo) + { + simpleCmdInfo = SimpleRespCommandInfo.Default; + + // Try to parse known command from name and obtain its command info + if (!Enum.TryParse(cmdName, true, out var cmd) || + !RespCommandsInfo.TryGetSimpleRespCommandInfo(cmd, out simpleCmdInfo, logger)) + { + // If we no known command or info was found, attempt to find custom command + if (storeWrapper.customCommandManager.TryGetCustomCommandInfo(cmdName, out var cmdInfo)) + { + cmdInfo.PopulateSimpleCommandInfo(ref simpleCmdInfo); + } + else + { + // No matching command was found + return false; + } + } + + return true; + } + static void SetResult(int c, ref int firstPending, ref (GarnetStatus, SpanByteAndMemory)[] outputArr, GarnetStatus status, SpanByteAndMemory output) { diff --git a/libs/server/Resp/CmdStrings.cs b/libs/server/Resp/CmdStrings.cs index e0a4a29eb77..426f5677c65 100644 --- a/libs/server/Resp/CmdStrings.cs +++ b/libs/server/Resp/CmdStrings.cs @@ -357,6 +357,7 @@ static partial class CmdStrings public static ReadOnlySpan hash => "hash"u8; public static ReadOnlySpan STRING => "STRING"u8; public static ReadOnlySpan stringt => "string"u8; + public static ReadOnlySpan none => "none"u8; /// /// Register object types diff --git a/libs/server/Resp/GarnetDatabaseSession.cs b/libs/server/Resp/GarnetDatabaseSession.cs index eebe240656b..bb56abebf7b 100644 --- a/libs/server/Resp/GarnetDatabaseSession.cs +++ b/libs/server/Resp/GarnetDatabaseSession.cs @@ -4,16 +4,22 @@ namespace Garnet.server { using BasicGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, BasicContext, + ObjectAllocator>>, + BasicContext, ObjectAllocator>>>; using TransactionalGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, TransactionalContext, + ObjectAllocator>>, + TransactionalContext, ObjectAllocator>>>; /// diff --git a/libs/server/Resp/KeyAdminCommands.cs b/libs/server/Resp/KeyAdminCommands.cs index 930147f663e..80fb33e7c3a 100644 --- a/libs/server/Resp/KeyAdminCommands.cs +++ b/libs/server/Resp/KeyAdminCommands.cs @@ -358,10 +358,15 @@ private bool NetworkEXISTS(ref TGarnetApi storageApi) var exists = 0; + // Prepare input + var input = new UnifiedStoreInput(RespCommand.EXISTS); + + var output = new GarnetUnifiedStoreOutput(); + for (var i = 0; i < parseState.Count; i++) { var key = parseState.GetArgSliceByRef(i); - var status = storageApi.EXISTS(key); + var status = storageApi.EXISTS(key, ref input, ref output); if (status == GarnetStatus.OK) exists++; } @@ -454,10 +459,14 @@ private bool NetworkEXPIRE(RespCommand command, ref TGarnetApi stora // Encode expiration time and expiration option and pass them into the input object var expirationWithOption = new ExpirationWithOption(expirationTimeInTicks, expireOption); - var input = new RawStringInput(RespCommand.EXPIRE, arg1: expirationWithOption.Word); - var status = storageApi.EXPIRE(key, ref input, out var timeoutSet); + var input = new UnifiedStoreInput(RespCommand.EXPIRE, arg1: expirationWithOption.Word); + + // Prepare GarnetUnifiedStoreOutput output + var output = GarnetUnifiedStoreOutput.FromPinnedPointer(dcurr, (int)(dend - dcurr)); + + var status = storageApi.EXPIRE(key, ref input, ref output); - if (status == GarnetStatus.OK && timeoutSet) + if (status == GarnetStatus.OK && ((OutputHeader*)output.SpanByteAndMemory.SpanByte.ToPointer())->result1 == 1) { while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_RETURN_VAL_1, ref dcurr, dend)) SendAndReset(); @@ -486,12 +495,18 @@ private bool NetworkPERSIST(ref TGarnetApi storageApi) } var key = parseState.GetArgSliceByRef(0); - var status = storageApi.PERSIST(key); + + // Prepare input + var input = new UnifiedStoreInput(RespCommand.PERSIST); + + // Prepare GarnetUnifiedStoreOutput output + var output = GarnetUnifiedStoreOutput.FromPinnedPointer(dcurr, (int)(dend - dcurr)); + + var status = storageApi.PERSIST(key, ref input, ref output); if (status == GarnetStatus.OK) { - while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_RETURN_VAL_1, ref dcurr, dend)) - SendAndReset(); + ProcessOutput(output.SpanByteAndMemory); } else { @@ -513,21 +528,22 @@ private bool NetworkTTL(RespCommand command, ref TGarnetApi storageA { if (parseState.Count != 1) { - return AbortWithWrongNumberOfArguments(nameof(RespCommand.PERSIST)); + return AbortWithWrongNumberOfArguments(command.ToString()); } var key = parseState.GetArgSliceByRef(0); - var o = SpanByteAndMemory.FromPinnedPointer(dcurr, (int)(dend - dcurr)); - var status = command == RespCommand.TTL ? - storageApi.TTL(key, StoreType.All, ref o) : - storageApi.PTTL(key, StoreType.All, ref o); + + // Prepare input + var input = new UnifiedStoreInput(command); + + // Prepare GarnetUnifiedStoreOutput output + var output = GarnetUnifiedStoreOutput.FromPinnedPointer(dcurr, (int)(dend - dcurr)); + + var status = storageApi.TTL(key, ref input, ref output); if (status == GarnetStatus.OK) { - if (!o.IsSpanByte) - SendAndReset(o.Memory, o.Length); - else - dcurr += o.Length; + ProcessOutput(output.SpanByteAndMemory); } else { @@ -553,17 +569,18 @@ private bool NetworkEXPIRETIME(RespCommand command, ref TGarnetApi s } var key = parseState.GetArgSliceByRef(0); - var o = SpanByteAndMemory.FromPinnedPointer(dcurr, (int)(dend - dcurr)); - var status = command == RespCommand.EXPIRETIME ? - storageApi.EXPIRETIME(key, StoreType.All, ref o) : - storageApi.PEXPIRETIME(key, StoreType.All, ref o); + + // Prepare input + var input = new UnifiedStoreInput(command); + + // Prepare GarnetUnifiedStoreOutput output + var output = GarnetUnifiedStoreOutput.FromPinnedPointer(dcurr, (int)(dend - dcurr)); + + var status = storageApi.EXPIRETIME(key, ref input, ref output); if (status == GarnetStatus.OK) { - if (!o.IsSpanByte) - SendAndReset(o.Memory, o.Length); - else - dcurr += o.Length; + ProcessOutput(output.SpanByteAndMemory); } else { diff --git a/libs/server/Resp/LocalServerSession.cs b/libs/server/Resp/LocalServerSession.cs index 7a0b30c1c4f..2b990828131 100644 --- a/libs/server/Resp/LocalServerSession.cs +++ b/libs/server/Resp/LocalServerSession.cs @@ -8,10 +8,13 @@ namespace Garnet.server { using BasicGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, BasicContext, + ObjectAllocator>>, + BasicContext, ObjectAllocator>>>; /// @@ -50,7 +53,7 @@ public LocalServerSession(StoreWrapper storeWrapper) // Create storage session and API this.storageSession = new StorageSession(storeWrapper, scratchBufferBuilder, sessionMetrics, LatencyMetrics, dbId: 0, logger); - this.BasicGarnetApi = new BasicGarnetApi(storageSession, storageSession.basicContext, storageSession.objectStoreBasicContext); + this.BasicGarnetApi = new BasicGarnetApi(storageSession, storageSession.basicContext, storageSession.objectStoreBasicContext, storageSession.unifiedStoreBasicContext); } /// diff --git a/libs/server/Resp/Objects/HashCommands.cs b/libs/server/Resp/Objects/HashCommands.cs index 030ed392805..f140a706785 100644 --- a/libs/server/Resp/Objects/HashCommands.cs +++ b/libs/server/Resp/Objects/HashCommands.cs @@ -573,9 +573,6 @@ private unsafe bool HashIncrement(RespCommand command, ref TGarnetAp private unsafe bool HashExpire(RespCommand command, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - if (storeWrapper.objectStore == null) - throw new GarnetException("Object store is disabled"); - if (parseState.Count <= 4) { return AbortWithWrongNumberOfArguments(command.ToString()); @@ -669,9 +666,6 @@ private unsafe bool HashExpire(RespCommand command, ref TGarnetApi s private unsafe bool HashTimeToLive(RespCommand command, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - if (storeWrapper.objectStore == null) - throw new GarnetException("Object store is disabled"); - if (parseState.Count <= 3) { return AbortWithWrongNumberOfArguments(command.ToString()); @@ -751,9 +745,6 @@ private unsafe bool HashTimeToLive(RespCommand command, ref TGarnetA private unsafe bool HashPersist(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - if (storeWrapper.objectStore == null) - throw new GarnetException("Object store is disabled"); - if (parseState.Count <= 3) { return AbortWithWrongNumberOfArguments(nameof(RespCommand.HPERSIST)); diff --git a/libs/server/Resp/Objects/ListCommands.cs b/libs/server/Resp/Objects/ListCommands.cs index 1ca2ffc134b..63bae823c14 100644 --- a/libs/server/Resp/Objects/ListCommands.cs +++ b/libs/server/Resp/Objects/ListCommands.cs @@ -279,9 +279,6 @@ private bool ListBlockingPop(RespCommand command) if (!parseState.TryGetTimeout(parseState.Count - 1, out var timeout, out var error)) return AbortWithErrorMessage(error); - if (storeWrapper.objectStore == null) - throw new GarnetException("Object store is disabled"); - var result = storeWrapper.itemBroker.GetCollectionItemAsync(command, keysBytes, this, timeout).Result; if (result.IsForceUnblocked) @@ -370,9 +367,6 @@ private bool ListBlockingMove(PinnedSpanByte srcKey, PinnedSpanByte dstKey, cmdArgs[1] = PinnedSpanByte.FromPinnedPointer(pSrcDir, 1); cmdArgs[2] = PinnedSpanByte.FromPinnedPointer(pDstDir, 1); - if (storeWrapper.objectStore == null) - throw new GarnetException("Object store is disabled"); - var result = storeWrapper.itemBroker.MoveCollectionItemAsync(RespCommand.BLMOVE, srcKey.ToArray(), this, timeout, cmdArgs).Result; @@ -907,9 +901,6 @@ private unsafe bool ListBlockingPopMultiple() cmdArgs[1] = PinnedSpanByte.FromPinnedPointer((byte*)&popCount, sizeof(int)); - if (storeWrapper.objectStore == null) - throw new GarnetException("Object store is disabled"); - var result = storeWrapper.itemBroker.GetCollectionItemAsync(RespCommand.BLMPOP, keysBytes, this, timeout, cmdArgs).Result; if (result.IsForceUnblocked) diff --git a/libs/server/Resp/Objects/SortedSetCommands.cs b/libs/server/Resp/Objects/SortedSetCommands.cs index 520604d86cd..f6761ab0b8b 100644 --- a/libs/server/Resp/Objects/SortedSetCommands.cs +++ b/libs/server/Resp/Objects/SortedSetCommands.cs @@ -1539,9 +1539,6 @@ private unsafe bool SortedSetUnionStore(ref TGarnetApi storageApi) /// private unsafe bool SortedSetBlockingPop(RespCommand command) { - if (storeWrapper.objectStore == null) - throw new GarnetException("Object store is disabled"); - if (parseState.Count < 2) { return AbortWithWrongNumberOfArguments(command.ToString()); @@ -1599,9 +1596,6 @@ private unsafe bool SortedSetBlockingPop(RespCommand command) /// private unsafe bool SortedSetBlockingMPop() { - if (storeWrapper.objectStore == null) - throw new GarnetException("Object store is disabled"); - if (parseState.Count < 4) { return AbortWithWrongNumberOfArguments(nameof(RespCommand.BZMPOP)); @@ -1725,9 +1719,6 @@ private unsafe bool SortedSetBlockingMPop() private unsafe bool SortedSetExpire(RespCommand command, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - if (storeWrapper.objectStore == null) - throw new GarnetException("Object store is disabled"); - if (parseState.Count <= 4) { return AbortWithWrongNumberOfArguments(command.ToString()); @@ -1824,9 +1815,6 @@ private unsafe bool SortedSetExpire(RespCommand command, ref TGarnet private unsafe bool SortedSetTimeToLive(RespCommand command, ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - if (storeWrapper.objectStore == null) - throw new GarnetException("Object store is disabled"); - if (parseState.Count <= 3) { return AbortWithWrongNumberOfArguments(command.ToString()); @@ -1913,9 +1901,6 @@ private unsafe bool SortedSetTimeToLive(RespCommand command, ref TGa private unsafe bool SortedSetPersist(ref TGarnetApi storageApi) where TGarnetApi : IGarnetApi { - if (storeWrapper.objectStore == null) - throw new GarnetException("Object store is disabled"); - if (parseState.Count <= 3) { return AbortWithWrongNumberOfArguments(nameof(RespCommand.ZPERSIST)); diff --git a/libs/server/Resp/Parser/SessionParseState.cs b/libs/server/Resp/Parser/SessionParseState.cs index 8a406ab85cc..a8a0b4df7da 100644 --- a/libs/server/Resp/Parser/SessionParseState.cs +++ b/libs/server/Resp/Parser/SessionParseState.cs @@ -42,7 +42,7 @@ public unsafe struct SessionParseState PinnedSpanByte[] rootBuffer; /// - /// Get a Span of the parsed parameters in the form an ArgSlice + /// Get a Span of the parsed parameters in the form an PinnedSpanByte /// public ReadOnlySpan Parameters => new(bufferPtr, Count); diff --git a/libs/server/Resp/RespCommandInfoSimplifiedStructs.cs b/libs/server/Resp/RespCommandInfoSimplifiedStructs.cs new file mode 100644 index 00000000000..4cafd9515c7 --- /dev/null +++ b/libs/server/Resp/RespCommandInfoSimplifiedStructs.cs @@ -0,0 +1,264 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace Garnet.server +{ + /// + /// Represents a simplified version of RESP command's information + /// + public struct SimpleRespCommandInfo + { + /// + /// Command Arity + /// + public sbyte Arity; + + /// + /// True if command is allowed in a transaction context + /// + public bool AllowedInTxn; + + /// + /// True if command has sub-commands + /// + public bool IsParent; + + /// + /// True if command is a sub-command + /// + public bool IsSubCommand; + + /// + /// Simplified command key specifications + /// + public SimpleRespKeySpec[] KeySpecs; + + /// + /// Store type that the command operates on (None/Main/Object/All). Default: None for commands without key arguments. + /// + public StoreType StoreType; + + /// + /// Default SimpleRespCommandInfo + /// + public static SimpleRespCommandInfo Default = new(); + } + + /// + /// Represents a simplified version of a single key specification of a RESP command + /// + public struct SimpleRespKeySpecBeginSearch + { + /// + /// Keyword that precedes the keys in the command arguments + /// + public byte[] Keyword; + + /// + /// Index of first key or the index at which to start searching for keyword (if begin search is of keyword type) + /// + public int Index; + + /// + /// If true - begin search is of type index, otherwise begin search is of type keyword + /// + public bool IsIndexType; + + /// + /// Set begin search of type index + /// + /// Index of first key + public SimpleRespKeySpecBeginSearch(int index) + { + IsIndexType = true; + Index = index; + } + + /// + /// Set begin search of type keyword + /// + /// Keyword that precedes the keys in the command arguments + /// Index at which to start searching for keyword + public SimpleRespKeySpecBeginSearch(string keyword, int startIdx) + { + Index = startIdx; + Keyword = Encoding.UTF8.GetBytes(keyword); + } + } + + /// + /// Represents a simplified version of a single key specification of a RESP command + /// + [StructLayout(LayoutKind.Explicit, Size = Size)] + public struct SimpleRespKeySpecFindKeys + { + /// + /// Size of struct + /// + public const int Size = 10; + + /// + /// The index (relative to begin search) of the argument containing the number of keys + /// + [FieldOffset(0)] + public int KeyNumIndex; + + /// + /// the index (relative to begin search) of the first key + /// + [FieldOffset(4)] + public int FirstKey; + + /// + /// The index (relative to begin search) of the last key argument or limit - stops the key search by a factor. + /// 0 and 1 mean no limit. 2 means half of the remaining arguments, 3 means a third, and so on. + /// Limit is used if IsRangeLimitType is set. + /// + [FieldOffset(0)] + public int LastKeyOrLimit; + + /// + /// The number of arguments that should be skipped, after finding a key, to find the next one. + /// + [FieldOffset(4)] + public int KeyStep; + + /// + /// If true - find keys is of type range, otherwise find keys is of type keynum + /// + [FieldOffset(8)] + public bool IsRangeType; + + /// + /// If true - find keys is of type range and limit is used, otherwise find keys is of type range and last key is used. + /// + [FieldOffset(9)] + public bool IsRangeLimitType; + + /// + /// Set find keys of type range + /// + /// The number of arguments that should be skipped, after finding a key, to find the next one + /// The index of the last key argument or the limit + /// If preceding argument represents a limit + public SimpleRespKeySpecFindKeys(int keyStep, int lastKeyOrLimit, bool isLimit) + { + IsRangeType = true; + KeyStep = keyStep; + LastKeyOrLimit = lastKeyOrLimit; + IsRangeLimitType = isLimit; + } + + /// + /// Set find keys of type keynum + /// + /// The index of the argument containing the number of keys + /// The index of the first key + /// The number of arguments that should be skipped, after finding a key, to find the next one + public SimpleRespKeySpecFindKeys(int keyNumIndex, int firstKey, int keyStep) + { + KeyNumIndex = keyNumIndex; + FirstKey = firstKey; + KeyStep = keyStep; + } + } + + /// + /// Represents a simplified version of a single key specification of a RESP command + /// + public struct SimpleRespKeySpec + { + /// + /// Begin search specification + /// + public SimpleRespKeySpecBeginSearch BeginSearch; + + /// + /// Find keys specification + /// + public SimpleRespKeySpecFindKeys FindKeys; + + /// + /// Key specification flags + /// + public KeySpecificationFlags Flags; + } + + /// + /// Extension methods for obtaining simplified RESP command info structs + /// + public static class RespCommandInfoExtensions + { + /// + /// Populates a SimpleRespCommandInfo struct from a RespCommandsInfo instance + /// + /// The source RespCommandsInfo + /// The destination SimpleRespCommandInfo + public static void PopulateSimpleCommandInfo(this RespCommandsInfo cmdInfo, ref SimpleRespCommandInfo simpleCmdInfo) + { + var arity = cmdInfo.Arity; + + // Verify that arity is in the signed byte range (-128 to 127) + Debug.Assert(arity is <= sbyte.MaxValue and >= sbyte.MinValue); + + simpleCmdInfo.Arity = (sbyte)arity; + simpleCmdInfo.AllowedInTxn = (cmdInfo.Flags & RespCommandFlags.NoMulti) == 0; + simpleCmdInfo.IsParent = (cmdInfo.SubCommands?.Length ?? 0) > 0; + simpleCmdInfo.IsSubCommand = cmdInfo.Parent != null; + simpleCmdInfo.StoreType = cmdInfo.StoreType; + + if (cmdInfo.KeySpecifications != null) + { + var tmpSimpleKeySpecs = new List(); + + foreach (var keySpec in cmdInfo.KeySpecifications) + { + if (keySpec.TryGetSimpleKeySpec(out var simpleKeySpec)) + tmpSimpleKeySpecs.Add(simpleKeySpec); + } + + simpleCmdInfo.KeySpecs = tmpSimpleKeySpecs.ToArray(); + } + } + + /// + /// Tries to convert a RespCommandKeySpecification to a SimpleRespKeySpec + /// + /// The source RespCommandKeySpecification + /// The resulting SimpleRespKeySpec + /// True if successful + public static bool TryGetSimpleKeySpec(this RespCommandKeySpecification keySpec, out SimpleRespKeySpec simpleKeySpec) + { + simpleKeySpec = new SimpleRespKeySpec(); + + if (keySpec.BeginSearch is BeginSearchUnknown || keySpec.FindKeys is FindKeysUnknown) + return false; + + simpleKeySpec.BeginSearch = keySpec.BeginSearch switch + { + BeginSearchIndex bsi => new SimpleRespKeySpecBeginSearch(bsi.Index), + BeginSearchKeyword bsk => new SimpleRespKeySpecBeginSearch(bsk.Keyword, bsk.StartFrom), + _ => throw new NotSupportedException() + }; + + simpleKeySpec.FindKeys = keySpec.FindKeys switch + { + FindKeysRange fkr => fkr.LastKey == -1 + ? new SimpleRespKeySpecFindKeys(fkr.KeyStep, fkr.Limit, true) + : new SimpleRespKeySpecFindKeys(fkr.KeyStep, fkr.LastKey, false), + FindKeysKeyNum fkk => new SimpleRespKeySpecFindKeys(fkk.KeyNumIdx, fkk.FirstKey, fkk.KeyStep), + _ => throw new NotSupportedException() + }; + + simpleKeySpec.Flags = keySpec.Flags; + + return true; + } + } +} \ No newline at end of file diff --git a/libs/server/Resp/RespCommandsInfo.cs b/libs/server/Resp/RespCommandsInfo.cs index 1c936f9cdf0..db91b3532ba 100644 --- a/libs/server/Resp/RespCommandsInfo.cs +++ b/libs/server/Resp/RespCommandsInfo.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Linq; using System.Numerics; using System.Text.Json.Serialization; @@ -15,37 +14,6 @@ namespace Garnet.server { - /// - /// Represents a simplified version of RESP command's information - /// - public struct SimpleRespCommandInfo - { - /// - /// Command Arity - /// - public sbyte Arity; - - /// - /// If command is allowed in a transaction context - /// - public bool AllowedInTxn; - - /// - /// If command has sub-commands - /// - public bool IsParent; - - /// - /// If command is a sub-command - /// - public bool IsSubCommand; - - /// - /// Default SimpleRespCommandInfo - /// - public static SimpleRespCommandInfo Default = new(); - } - /// /// Represents a RESP command's information /// @@ -120,6 +88,11 @@ public RespAclCategories AclCategories /// public RespCommandKeySpecification[] KeySpecifications { get; init; } + /// + /// Store type that the command operates on (None/Main/Object/All). Default: None for commands without key arguments. + /// + public StoreType StoreType { get; set; } + /// public RespCommandsInfo[] SubCommands { get; init; } @@ -197,15 +170,7 @@ private static bool TryInitializeRespCommandsInfo(ILogger logger = null) continue; } - var arity = cmdInfo.Arity; - - // Verify that arity is in the signed byte range (-128 to 127) - Debug.Assert(arity <= sbyte.MaxValue && arity >= sbyte.MinValue); - - tmpSimpleRespCommandInfo[cmdId].Arity = (sbyte)arity; - tmpSimpleRespCommandInfo[cmdId].AllowedInTxn = (cmdInfo.Flags & RespCommandFlags.NoMulti) == 0; - tmpSimpleRespCommandInfo[cmdId].IsParent = (cmdInfo.SubCommands?.Length ?? 0) > 0; - tmpSimpleRespCommandInfo[cmdId].IsSubCommand = cmdInfo.Parent != null; + cmdInfo.PopulateSimpleCommandInfo(ref tmpSimpleRespCommandInfo[cmdId]); } var tmpAllSubCommandsInfo = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/libs/server/Resp/RespServerSession.cs b/libs/server/Resp/RespServerSession.cs index 2ba11ef9711..043c1860e98 100644 --- a/libs/server/Resp/RespServerSession.cs +++ b/libs/server/Resp/RespServerSession.cs @@ -21,16 +21,22 @@ namespace Garnet.server { using BasicGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, BasicContext, + ObjectAllocator>>, + BasicContext, ObjectAllocator>>>; using TransactionalGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, TransactionalContext, + ObjectAllocator>>, + TransactionalContext, ObjectAllocator>>>; /// @@ -1492,8 +1498,11 @@ private GarnetDatabaseSession TryGetOrSetDatabaseSession(int dbId, out bool succ private GarnetDatabaseSession CreateDatabaseSession(int dbId) { var dbStorageSession = new StorageSession(storeWrapper, scratchBufferBuilder, sessionMetrics, LatencyMetrics, dbId, logger, respProtocolVersion); - var dbGarnetApi = new BasicGarnetApi(dbStorageSession, dbStorageSession.basicContext, dbStorageSession.objectStoreBasicContext); - var dbLockableGarnetApi = new TransactionalGarnetApi(dbStorageSession, dbStorageSession.transactionalContext, dbStorageSession.objectStoreTransactionalContext); + var dbGarnetApi = new BasicGarnetApi(dbStorageSession, dbStorageSession.basicContext, + dbStorageSession.objectStoreBasicContext, dbStorageSession.unifiedStoreBasicContext); + var dbLockableGarnetApi = new TransactionalGarnetApi(dbStorageSession, + dbStorageSession.transactionalContext, dbStorageSession.objectStoreTransactionalContext, + dbStorageSession.unifiedStoreTransactionalContext); var transactionManager = new TransactionManager(storeWrapper, this, dbGarnetApi, dbLockableGarnetApi, dbStorageSession, scratchBufferAllocator, storeWrapper.serverOptions.EnableCluster, logger, dbId); diff --git a/libs/server/ServerConfig.cs b/libs/server/ServerConfig.cs index 707077d4d9f..ebc1dd7c143 100644 --- a/libs/server/ServerConfig.cs +++ b/libs/server/ServerConfig.cs @@ -131,10 +131,8 @@ private bool NetworkCONFIG_SET() string clusterUsername = null; string clusterPassword = null; string memorySize = null; - string objLogMemory = null; - string objHeapMemory = null; + string heapMemory = null; string index = null; - string objIndex = null; var unknownOption = false; var unknownKey = ""; @@ -146,14 +144,10 @@ private bool NetworkCONFIG_SET() if (key.EqualsLowerCaseSpanIgnoringCase(CmdStrings.Memory, allowNonAlphabeticChars: false)) memorySize = Encoding.ASCII.GetString(value); - else if (key.EqualsLowerCaseSpanIgnoringCase(CmdStrings.ObjLogMemory, allowNonAlphabeticChars: true)) - objLogMemory = Encoding.ASCII.GetString(value); else if (key.EqualsLowerCaseSpanIgnoringCase(CmdStrings.ObjHeapMemory, allowNonAlphabeticChars: true)) - objHeapMemory = Encoding.ASCII.GetString(value); + heapMemory = Encoding.ASCII.GetString(value); else if (key.EqualsLowerCaseSpanIgnoringCase(CmdStrings.Index, allowNonAlphabeticChars: false)) index = Encoding.ASCII.GetString(value); - else if (key.EqualsLowerCaseSpanIgnoringCase(CmdStrings.ObjIndex, allowNonAlphabeticChars: true)) - objIndex = Encoding.ASCII.GetString(value); else if (key.EqualsLowerCaseSpanIgnoringCase(CmdStrings.CertFileName, allowNonAlphabeticChars: true)) certFileName = Encoding.ASCII.GetString(value); else if (key.EqualsLowerCaseSpanIgnoringCase(CmdStrings.CertPassword, allowNonAlphabeticChars: true)) @@ -210,17 +204,11 @@ private bool NetworkCONFIG_SET() if (memorySize != null) HandleMemorySizeChange(memorySize, sbErrorMsg); - if (objLogMemory != null) - HandleMemorySizeChange(objLogMemory, sbErrorMsg, mainStore: false); - if (index != null) HandleIndexSizeChange(index, sbErrorMsg); - if (objIndex != null) - HandleIndexSizeChange(objIndex, sbErrorMsg, mainStore: false); - - if (objHeapMemory != null) - HandleObjHeapMemorySizeChange(objHeapMemory, sbErrorMsg); + if (heapMemory != null) + HandleHeapMemorySizeChange(heapMemory, sbErrorMsg); } if (sbErrorMsg.Length == 0) @@ -237,20 +225,16 @@ private bool NetworkCONFIG_SET() return true; } - private void HandleMemorySizeChange(string memorySize, StringBuilder sbErrorMsg, bool mainStore = true) + private void HandleMemorySizeChange(string memorySize, StringBuilder sbErrorMsg) { - var option = mainStore ? CmdStrings.Memory : CmdStrings.ObjLogMemory; - if (!ServerOptions.TryParseSize(memorySize, out var newMemorySize)) { - AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIncorrectSizeFormat, option); + AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIncorrectSizeFormat, CmdStrings.Memory); return; } // Parse the configured memory size - var confMemorySize = ServerOptions.ParseSize( - mainStore ? storeWrapper.serverOptions.MemorySize - : storeWrapper.serverOptions.ObjectStoreLogMemorySize, out _); + var confMemorySize = ServerOptions.ParseSize(storeWrapper.serverOptions.MemorySize, out _); // If the new memory size is the same as the configured memory size, nothing to do if (newMemorySize == confMemorySize) @@ -262,35 +246,24 @@ private void HandleMemorySizeChange(string memorySize, StringBuilder sbErrorMsg, // If the new memory size is greater than the configured memory size, return an error if (newMemorySize > confMemorySize) { - AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrMemorySizeGreaterThanBuffer, option); + AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrMemorySizeGreaterThanBuffer, CmdStrings.Memory); return; } // Parse & adjust the configured page size - var pageSize = ServerOptions.ParseSize( - mainStore ? storeWrapper.serverOptions.PageSize : storeWrapper.serverOptions.ObjectStorePageSize, - out _); + var pageSize = ServerOptions.ParseSize(storeWrapper.serverOptions.PageSize, out _); pageSize = ServerOptions.PreviousPowerOf2(pageSize); // Compute the new minimum empty page count and update the store's log accessor var newMinEmptyPageCount = (int)((confMemorySize - newMemorySize) / pageSize); - if (mainStore) - { - storeWrapper.store.Log.MinEmptyPageCount = newMinEmptyPageCount; - } - else - { - storeWrapper.objectStore.Log.MinEmptyPageCount = newMinEmptyPageCount; - } + storeWrapper.store.Log.MinEmptyPageCount = newMinEmptyPageCount; } - private void HandleIndexSizeChange(string indexSize, StringBuilder sbErrorMsg, bool mainStore = true) + private void HandleIndexSizeChange(string indexSize, StringBuilder sbErrorMsg) { - var option = mainStore ? CmdStrings.Index : CmdStrings.ObjIndex; - if (!ServerOptions.TryParseSize(indexSize, out var newIndexSize)) { - AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIncorrectSizeFormat, option); + AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIncorrectSizeFormat, CmdStrings.Index); return; } @@ -298,19 +271,18 @@ private void HandleIndexSizeChange(string indexSize, StringBuilder sbErrorMsg, b var adjNewIndexSize = ServerOptions.PreviousPowerOf2(newIndexSize); if (adjNewIndexSize != newIndexSize) { - AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIndexSizePowerOfTwo, option); + AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIndexSizePowerOfTwo, CmdStrings.Index); return; } // Check if the index auto-grow task is running. If so - return an error. - if ((mainStore && storeWrapper.serverOptions.AdjustedIndexMaxCacheLines > 0) || - (!mainStore && storeWrapper.serverOptions.AdjustedObjectStoreIndexMaxCacheLines > 0)) + if (storeWrapper.serverOptions.AdjustedIndexMaxCacheLines > 0) { - AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIndexSizeAutoGrow, option); + AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIndexSizeAutoGrow, CmdStrings.Index); return; } - var currIndexSize = mainStore ? storeWrapper.store.IndexSize : storeWrapper.objectStore.IndexSize; + var currIndexSize = storeWrapper.store.IndexSize; // Convert new index size to cache lines adjNewIndexSize /= 64; @@ -322,20 +294,18 @@ private void HandleIndexSizeChange(string indexSize, StringBuilder sbErrorMsg, b // If the new index size is smaller than the current index size, return an error if (currIndexSize > adjNewIndexSize) { - AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIndexSizeSmallerThanCurrent, option); + AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIndexSizeSmallerThanCurrent, CmdStrings.Index); return; } // Try to grow the index size by doubling it until it reaches the new size while (currIndexSize < adjNewIndexSize) { - var isSuccessful = mainStore - ? storeWrapper.store.GrowIndexAsync().ConfigureAwait(false).GetAwaiter().GetResult() - : storeWrapper.objectStore.GrowIndexAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + var isSuccessful = storeWrapper.store.GrowIndexAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (!isSuccessful) { - AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIndexSizeGrowFailed, option); + AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrIndexSizeGrowFailed, CmdStrings.Index); return; } @@ -343,7 +313,7 @@ private void HandleIndexSizeChange(string indexSize, StringBuilder sbErrorMsg, b } } - private void HandleObjHeapMemorySizeChange(string heapMemorySize, StringBuilder sbErrorMsg) + private void HandleHeapMemorySizeChange(string heapMemorySize, StringBuilder sbErrorMsg) { if (!ServerOptions.TryParseSize(heapMemorySize, out var newHeapMemorySize)) { @@ -352,14 +322,14 @@ private void HandleObjHeapMemorySizeChange(string heapMemorySize, StringBuilder } // If the object store size tracker is not running, return an error - if (storeWrapper.objectStoreSizeTracker == null) + if (storeWrapper.sizeTracker == null) { AppendErrorWithTemplate(sbErrorMsg, CmdStrings.GenericErrHeapMemorySizeTrackerNotRunning, CmdStrings.ObjHeapMemory); return; } // Set the new target size for the object store size tracker - storeWrapper.objectStoreSizeTracker.TargetSize = newHeapMemorySize; + storeWrapper.sizeTracker.TargetSize = newHeapMemorySize; } private static void AppendError(StringBuilder sbErrorMsg, string error) diff --git a/libs/server/Servers/GarnetServerOptions.cs b/libs/server/Servers/GarnetServerOptions.cs index 86b9446dabb..36b6308756a 100644 --- a/libs/server/Servers/GarnetServerOptions.cs +++ b/libs/server/Servers/GarnetServerOptions.cs @@ -16,46 +16,10 @@ namespace Garnet.server /// public class GarnetServerOptions : ServerOptions { - /// - /// Support data structure objects. - /// - public bool DisableObjects = false; - /// /// Heap memory size limit of object store. /// - public string ObjectStoreHeapMemorySize = ""; - - /// - /// Object store log memory used in bytes excluding heap memory. - /// - public string ObjectStoreLogMemorySize = "32m"; - - /// - /// Size of each object store page in bytes (rounds down to power of 2). - /// - public string ObjectStorePageSize = "4k"; - - /// - /// Size of each object store log segment in bytes on disk (rounds down to power of 2). - /// - public string ObjectStoreSegmentSize = "32m"; - - /// - /// Size of object store hash index in bytes (rounds down to power of 2). - /// - public string ObjectStoreIndexSize = "16m"; - - /// - /// Max size of object store hash index in bytes (rounds down to power of 2). - /// If unspecified, index size doesn't grow (default behavior). - /// - public string ObjectStoreIndexMaxSize = string.Empty; - - /// - /// Percentage of object store log memory that is kept mutable. - /// - public int ObjectStoreMutablePercent = 90; + public string HeapMemorySize = ""; /// /// Enable cluster. @@ -176,11 +140,6 @@ public class GarnetServerOptions : ServerOptions /// public int CompactionMaxSegments = 32; - /// - /// Number of object store log segments created on disk before compaction triggers. - /// - public int ObjectStoreCompactionMaxSegments = 32; - /// /// Percent of cluster nodes to gossip with at each gossip iteration. /// @@ -426,11 +385,6 @@ public class GarnetServerOptions : ServerOptions /// public bool RevivInChainOnly; - /// - /// Number of records in the single free record bin for the object store. - /// - public int RevivObjBinRecordCount; - /// Max size of hash index (cache lines) after rounding down size in bytes to power of 2. public int AdjustedIndexMaxCacheLines; @@ -466,13 +420,7 @@ public class GarnetServerOptions : ServerOptions public string ReadCachePageSize = "32m"; - public string ObjectStoreReadCachePageSize = "1m"; - - public string ObjectStoreReadCacheLogMemorySize = "32m"; - - public string ObjectStoreReadCacheHeapMemorySize = ""; - - public bool EnableObjectStoreReadCache = false; + public string ReadCacheHeapMemorySize = ""; public LuaOptions LuaOptions; @@ -504,12 +452,7 @@ public class GarnetServerOptions : ServerOptions /// /// Gets the base directory for storing main-store checkpoints /// - public string MainStoreCheckpointBaseDirectory => Path.Combine(CheckpointBaseDirectory, "Store"); - - /// - /// Gets the base directory for storing object-store checkpoints - /// - public string ObjectStoreCheckpointBaseDirectory => Path.Combine(CheckpointBaseDirectory, "ObjectStore"); + public string StoreCheckpointBaseDirectory => Path.Combine(CheckpointBaseDirectory, "Store"); /// /// Seconds between attempts to re-establish replication between a Primary and Replica if the replication connection @@ -535,20 +478,12 @@ public class GarnetServerOptions : ServerOptions public string GetCheckpointDirectoryName(int dbId) => $"checkpoints{(dbId == 0 ? string.Empty : $"_{dbId}")}"; /// - /// Get the directory for main-store database checkpoints - /// - /// Database Id - /// Directory - public string GetMainStoreCheckpointDirectory(int dbId) => - Path.Combine(MainStoreCheckpointBaseDirectory, GetCheckpointDirectoryName(dbId)); - - /// - /// Get the directory for object-store database checkpoints + /// Get the directory for database checkpoints /// /// Database Id /// Directory - public string GetObjectStoreCheckpointDirectory(int dbId) => - Path.Combine(ObjectStoreCheckpointBaseDirectory, GetCheckpointDirectoryName(dbId)); + public string GetStoreCheckpointDirectory(int dbId) => + Path.Combine(StoreCheckpointBaseDirectory, GetCheckpointDirectoryName(dbId)); /// /// Gets the base directory for storing AOF commits @@ -593,11 +528,15 @@ public void Initialize(ILoggerFactory loggerFactory = null) /// Epoch instance used by server /// Common state machine driver used by Garnet /// Tsavorite Log factory instance + /// Heap memory size + /// Read cache heap memory size /// /// public KVSettings GetSettings(ILoggerFactory loggerFactory, LightEpoch epoch, StateMachineDriver stateMachineDriver, - out INamedDeviceFactory logFactory) + out INamedDeviceFactory logFactory, out long heapMemorySize, out long readCacheHeapMemorySize) { + readCacheHeapMemorySize = 0; + if (MutablePercent is < 10 or > 95) throw new Exception("MutablePercent must be between 10 and 95"); @@ -616,6 +555,7 @@ public KVSettings GetSettings(ILoggerFactory loggerFactory, LightEpoch epoch, St }; logger?.LogInformation("[Store] Using page size of {PageSize}", PrettySize(kvSettings.PageSize)); + logger?.LogInformation("[Store] Each page can hold ~{PageSize} key-value pairs of objects", kvSettings.PageSize / 24); kvSettings.MemorySize = 1L << MemorySizeBits(MemorySize, PageSize, out var storeEmptyPageCount); kvSettings.MinEmptyPageCount = storeEmptyPageCount; @@ -651,6 +591,9 @@ public KVSettings GetSettings(ILoggerFactory loggerFactory, LightEpoch epoch, St if (LatencyMonitor && MetricsSamplingFrequency == 0) throw new Exception("LatencyMonitor requires MetricsSamplingFrequency to be set"); + heapMemorySize = ParseSize(HeapMemorySize, out _); + logger?.LogInformation("[Store] Heap memory size is {heapMemorySize}", heapMemorySize > 0 ? PrettySize(heapMemorySize) : "unlimited"); + // Read cache related settings if (EnableReadCache && !EnableStorageTier) { @@ -664,6 +607,9 @@ public KVSettings GetSettings(ILoggerFactory loggerFactory, LightEpoch epoch, St kvSettings.ReadCacheMemorySize = ParseSize(ReadCacheMemorySize, out _); logger?.LogInformation("[Store] Read cache enabled with page size of {ReadCachePageSize} and memory size of {ReadCacheMemorySize}", PrettySize(kvSettings.ReadCachePageSize), PrettySize(kvSettings.ReadCacheMemorySize)); + + readCacheHeapMemorySize = ParseSize(ReadCacheHeapMemorySize, out _); + logger?.LogInformation("[Store] Read cache heap memory size is {readCacheHeapMemorySize}", readCacheHeapMemorySize > 0 ? PrettySize(readCacheHeapMemorySize) : "unlimited"); } if (EnableStorageTier) @@ -751,121 +697,6 @@ public static int MemorySizeBits(string memorySize, string storePageSize, out in return (int)Math.Log(adjustedSize, 2); } - /// - /// Get KVSettings for the object store log - /// - public KVSettings GetObjectStoreSettings(ILoggerFactory loggerFactory, LightEpoch epoch, StateMachineDriver stateMachineDriver, - out long objHeapMemorySize, out long objReadCacheHeapMemorySize) - { - objReadCacheHeapMemorySize = default; - - if (ObjectStoreMutablePercent is < 10 or > 95) - throw new Exception("ObjectStoreMutablePercent must be between 10 and 95"); - - var indexCacheLines = IndexSizeCachelines("object store hash index size", ObjectStoreIndexSize); - KVSettings kvSettings = new() - { - IndexSize = indexCacheLines * 64L, - PreallocateLog = false, - MutableFraction = ObjectStoreMutablePercent / 100.0, - PageSize = 1L << ObjectStorePageSizeBits(), - Epoch = epoch, - StateMachineDriver = stateMachineDriver, - loggerFactory = loggerFactory, - logger = loggerFactory?.CreateLogger("TsavoriteKV [obj]") - }; - - logger?.LogInformation("[Object Store] Using page size of {PageSize}", PrettySize(kvSettings.PageSize)); - logger?.LogInformation("[Object Store] Each page can hold ~{PageSize} key-value pairs of objects", kvSettings.PageSize / 24); - - kvSettings.MemorySize = 1L << MemorySizeBits(ObjectStoreLogMemorySize, ObjectStorePageSize, out var objectStoreEmptyPageCount); - kvSettings.MinEmptyPageCount = objectStoreEmptyPageCount; - - long effectiveSize = kvSettings.MemorySize - objectStoreEmptyPageCount * kvSettings.PageSize; - if (objectStoreEmptyPageCount == 0) - logger?.LogInformation("[Object Store] Using log memory size of {MemorySize}", PrettySize(kvSettings.MemorySize)); - else - logger?.LogInformation("[Object Store] Using log memory size of {MemorySize}, with {objectStoreEmptyPageCount} empty pages, for effective size of {effectiveSize}", PrettySize(kvSettings.MemorySize), objectStoreEmptyPageCount, PrettySize(effectiveSize)); - - logger?.LogInformation("[Object Store] This can hold ~{PageSize} key-value pairs of objects in memory total", effectiveSize / 24); - - logger?.LogInformation("[Object Store] There are {LogPages} log pages in memory", PrettySize(kvSettings.MemorySize / kvSettings.PageSize)); - - kvSettings.SegmentSize = 1L << ObjectStoreSegmentSizeBits(); - logger?.LogInformation("[Object Store] Using disk segment size of {SegmentSize}", PrettySize(kvSettings.SegmentSize)); - - logger?.LogInformation("[Object Store] Using hash index size of {IndexSize} ({indexCacheLines} cache lines)", PrettySize(kvSettings.IndexSize), PrettySize(indexCacheLines)); - logger?.LogInformation("[Object Store] Hash index size is optimized for up to ~{distinctKeys} distinct keys", PrettySize(indexCacheLines * 4L)); - - AdjustedObjectStoreIndexMaxCacheLines = ObjectStoreIndexMaxSize == string.Empty ? 0 : IndexSizeCachelines("hash index max size", ObjectStoreIndexMaxSize); - if (AdjustedObjectStoreIndexMaxCacheLines != 0 && AdjustedObjectStoreIndexMaxCacheLines < indexCacheLines) - throw new Exception($"Index size {IndexSize} should not be less than index max size {IndexMaxSize}"); - - if (AdjustedObjectStoreIndexMaxCacheLines > 0) - { - logger?.LogInformation("[Object Store] Using hash index max size of {MaxSize}, ({CacheLines} cache lines)", PrettySize(AdjustedObjectStoreIndexMaxCacheLines * 64L), PrettySize(AdjustedObjectStoreIndexMaxCacheLines)); - logger?.LogInformation("[Object Store] Hash index max size is optimized for up to ~{distinctKeys} distinct keys", PrettySize(AdjustedObjectStoreIndexMaxCacheLines * 4L)); - } - logger?.LogInformation("[Object Store] Using log mutable percentage of {ObjectStoreMutablePercent}%", ObjectStoreMutablePercent); - - objHeapMemorySize = ParseSize(ObjectStoreHeapMemorySize, out _); - logger?.LogInformation("[Object Store] Heap memory size is {objHeapMemorySize}", objHeapMemorySize > 0 ? PrettySize(objHeapMemorySize) : "unlimited"); - - // Read cache related settings - if (EnableObjectStoreReadCache && !EnableStorageTier) - { - throw new Exception("Read cache requires storage tiering to be enabled"); - } - - if (EnableObjectStoreReadCache) - { - kvSettings.ReadCacheEnabled = true; - kvSettings.ReadCachePageSize = ParseSize(ObjectStoreReadCachePageSize, out _); - kvSettings.ReadCacheMemorySize = ParseSize(ObjectStoreReadCacheLogMemorySize, out _); - logger?.LogInformation("[Object Store] Read cache enabled with page size of {ReadCachePageSize} and memory size of {ReadCacheMemorySize}", - PrettySize(kvSettings.ReadCachePageSize), PrettySize(kvSettings.ReadCacheMemorySize)); - - objReadCacheHeapMemorySize = ParseSize(ObjectStoreReadCacheHeapMemorySize, out _); - logger?.LogInformation("[Object Store] Read cache heap memory size is {objReadCacheHeapMemorySize}", objReadCacheHeapMemorySize > 0 ? PrettySize(objReadCacheHeapMemorySize) : "unlimited"); - } - - if (EnableStorageTier) - { - if (LogDir is null or "") - LogDir = Directory.GetCurrentDirectory(); - kvSettings.LogDevice = GetInitializedDeviceFactory(LogDir).Get(new FileDescriptor("ObjectStore", "hlog")); - kvSettings.ObjectLogDevice = GetInitializedDeviceFactory(LogDir).Get(new FileDescriptor("ObjectStore", "hlog.obj")); - } - else - { - if (LogDir != null) - throw new Exception("LogDir specified without enabling tiered storage (UseStorage)"); - kvSettings.LogDevice = kvSettings.ObjectLogDevice = new NullDevice(); - } - - if (ObjectStoreCopyReadsToTail) - kvSettings.ReadCopyOptions = new(ReadCopyFrom.AllImmutable, ReadCopyTo.MainLog); - - if (RevivInChainOnly) - { - logger?.LogInformation("[Object Store] Using Revivification in-chain only"); - kvSettings.RevivificationSettings = RevivificationSettings.InChainOnly.Clone(); - } - else if (UseRevivBinsPowerOf2 || RevivBinRecordSizes?.Length > 0) - { - logger?.LogInformation("[Store] Using Revivification with power-of-2 bins"); - kvSettings.RevivificationSettings = RevivificationSettings.PowerOf2Bins.Clone(); - kvSettings.RevivificationSettings.NumberOfBinsToSearch = RevivNumberOfBinsToSearch; - kvSettings.RevivificationSettings.RevivifiableFraction = RevivifiableFraction; - } - else - { - logger?.LogInformation("[Object Store] Not using Revivification"); - } - - return kvSettings; - } - /// /// Get AOF settings /// @@ -948,19 +779,6 @@ public int AofSizeLimitSizeBits() return (int)Math.Log(adjustedSize, 2); } - /// - /// Get object store page size - /// - /// - public int ObjectStorePageSizeBits() - { - long size = ParseSize(ObjectStorePageSize, out _); - long adjustedSize = PreviousPowerOf2(size); - if (size != adjustedSize) - logger?.LogInformation("Warning: using lower object store page size than specified (power of 2)"); - return (int)Math.Log(adjustedSize, 2); - } - /// /// Get integer value of ReplicaDisklessSyncFullSyncAofThreshold /// @@ -968,19 +786,6 @@ public int ObjectStorePageSizeBits() public long ReplicaDisklessSyncFullSyncAofThresholdValue() => ParseSize(string.IsNullOrEmpty(ReplicaDisklessSyncFullSyncAofThreshold) ? AofMemorySize : ReplicaDisklessSyncFullSyncAofThreshold, out _); - /// - /// Get object store segment size - /// - /// - public int ObjectStoreSegmentSizeBits() - { - long size = ParseSize(ObjectStoreSegmentSize, out _); - long adjustedSize = PreviousPowerOf2(size); - if (size != adjustedSize) - logger?.LogInformation("Warning: using lower object store disk segment size than specified (power of 2)"); - return (int)Math.Log(adjustedSize, 2); - } - /// /// Get device for AOF /// diff --git a/libs/server/SessionParseStateExtensions.cs b/libs/server/SessionParseStateExtensions.cs index d2d06be8a60..20d0ea68b0b 100644 --- a/libs/server/SessionParseStateExtensions.cs +++ b/libs/server/SessionParseStateExtensions.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Text; using Garnet.common; using Tsavorite.core; @@ -38,16 +40,10 @@ public static bool TryGetInfoMetricsType(this SessionParseState parseState, int value = InfoMetricsType.STATS; else if (sbArg.EqualsUpperCaseSpanIgnoringCase("STORE"u8)) value = InfoMetricsType.STORE; - else if (sbArg.EqualsUpperCaseSpanIgnoringCase("OBJECTSTORE"u8)) - value = InfoMetricsType.OBJECTSTORE; else if (sbArg.EqualsUpperCaseSpanIgnoringCase("STOREHASHTABLE"u8)) value = InfoMetricsType.STOREHASHTABLE; - else if (sbArg.EqualsUpperCaseSpanIgnoringCase("OBJECTSTOREHASHTABLE"u8)) - value = InfoMetricsType.OBJECTSTOREHASHTABLE; else if (sbArg.EqualsUpperCaseSpanIgnoringCase("STOREREVIV"u8)) value = InfoMetricsType.STOREREVIV; - else if (sbArg.EqualsUpperCaseSpanIgnoringCase("OBJECTSTOREREVIV"u8)) - value = InfoMetricsType.OBJECTSTOREREVIV; else if (sbArg.EqualsUpperCaseSpanIgnoringCase("PERSISTENCE"u8)) value = InfoMetricsType.PERSISTENCE; else if (sbArg.EqualsUpperCaseSpanIgnoringCase("CLIENTS"u8)) @@ -846,50 +842,53 @@ internal static bool TryGetTimeout(this SessionParseState parseState, int idx, o /// Tries to extract keys from the key specifications in the given RespCommandsInfo. /// /// The SessionParseState instance. - /// The RespCommandKeySpecification array contains the key specification - /// The list to store extracted keys. - /// True if keys were successfully extracted, otherwise false. - internal static bool TryExtractKeysFromSpecs(this ref SessionParseState state, RespCommandKeySpecification[] keySpecs, out List keys) + /// The command's simplified info + /// The extracted keys + internal static PinnedSpanByte[] ExtractCommandKeys(this ref SessionParseState state, SimpleRespCommandInfo commandInfo) { - keys = new(); + var keysIndexes = new List<(PinnedSpanByte Key, int Index)>(); - foreach (var spec in keySpecs) - { - if (!ExtractKeysFromSpec(ref state, keys, spec)) - { - return false; - } - } + foreach (var spec in commandInfo.KeySpecs) + TryAppendKeysFromSpec(ref state, spec, commandInfo.IsSubCommand, keysIndexes); - return true; + return keysIndexes.OrderBy(k => k.Index).Select(k => k.Key).ToArray(); } /// /// Tries to extract keys and their associated flags from the key specifications in the given RespCommandsInfo. /// /// The SessionParseState instance. - /// The RespCommandKeySpecification array containing the key specifications. - /// The list to store extracted keys. - /// The list to store associated flags for each key. - /// True if keys and flags were successfully extracted, otherwise false. - internal static bool TryExtractKeysAndFlagsFromSpecs(this ref SessionParseState state, RespCommandKeySpecification[] keySpecs, out List keys, out List flags) + /// The command's simplified info + /// The extracted keys and flags + internal static (PinnedSpanByte, KeySpecificationFlags)[] ExtractCommandKeysAndFlags(this ref SessionParseState state, SimpleRespCommandInfo commandInfo) { - keys = new(); - flags = new(); + var keysFlagsIndexes = new List<(PinnedSpanByte Key, KeySpecificationFlags Flags, int Index)>(); + + foreach (var spec in commandInfo.KeySpecs) + TryAppendKeysAndFlagsFromSpec(ref state, spec, commandInfo.IsSubCommand, keysFlagsIndexes); + + return keysFlagsIndexes.OrderBy(k => k.Index).Select(k => (k.Key, k.Flags)).ToArray(); + } + + /// + /// Extracts keys from the given key specification in the provided SessionParseState. + /// + /// The SessionParseState instance. + /// The key specification to use for extraction. + /// True if command is a sub-command + /// The list to store extracted keys and their matching indexes + private static bool TryAppendKeysFromSpec(ref SessionParseState parseState, SimpleRespKeySpec keySpec, bool isSubCommand, List<(PinnedSpanByte Key, int Index)> keysToIndexes) + { + if (!parseState.TryGetKeySearchArgsFromSimpleKeySpec(keySpec, isSubCommand, out var searchArgs)) + return false; - foreach (var spec in keySpecs) + for (var i = searchArgs.firstIdx; i <= searchArgs.lastIdx; i += searchArgs.step) { - var prevKeyCount = keys.Count; - if (!ExtractKeysFromSpec(ref state, keys, spec)) - { - return false; - } + var key = parseState.GetArgSliceByRef(i); + if (key.Length == 0) + continue; - var keyFlags = spec.RespFormatFlags; - for (int i = prevKeyCount; i < keys.Count; i++) - { - flags.Add(keyFlags); - } + keysToIndexes.Add((key, i)); } return true; @@ -898,30 +897,108 @@ internal static bool TryExtractKeysAndFlagsFromSpecs(this ref SessionParseState /// /// Extracts keys from the given key specification in the provided SessionParseState. /// - /// The SessionParseState instance. - /// The list to store extracted keys. - /// The key specification to use for extraction. - /// True if keys were successfully extracted, otherwise false. - private static bool ExtractKeysFromSpec(ref SessionParseState state, List keys, RespCommandKeySpecification spec) + /// The SessionParseState instance. + /// The key specification to use for extraction. + /// True if command is a sub-command + /// The list to store extracted keys and flags and their indexes + private static bool TryAppendKeysAndFlagsFromSpec(ref SessionParseState parseState, SimpleRespKeySpec keySpec, bool isSubCommand, List<(PinnedSpanByte Key, KeySpecificationFlags Flags, int Index)> keysAndFlags) { - int startIndex = 0; + if (!parseState.TryGetKeySearchArgsFromSimpleKeySpec(keySpec, isSubCommand, out var searchArgs)) + return false; - if (spec.BeginSearch is BeginSearchKeySpecMethodBase bsKeyword) + for (var i = searchArgs.firstIdx; i <= searchArgs.lastIdx; i += searchArgs.step) { - if (!bsKeyword.TryGetStartIndex(ref state, out startIndex)) + var key = parseState.GetArgSliceByRef(i); + if (key.Length == 0) + continue; + + keysAndFlags.Add((key, keySpec.Flags, i)); + } + + return true; + } + + /// + /// Extracts the first, last, and step arguments for key searching based on a simplified RESP key specification and the current parse state. + /// + /// The current parse state + /// The simplified key specification + /// True if command is a sub-command + /// First, last, and step arguments for key searching + /// + internal static bool TryGetKeySearchArgsFromSimpleKeySpec(this ref SessionParseState parseState, SimpleRespKeySpec keySpec, bool isSubCommand, out (int firstIdx, int lastIdx, int step) searchArgs) + { + searchArgs = (-1, -1, -1); + + // Determine the starting index for searching keys + var beginSearchIdx = keySpec.BeginSearch.Index < 0 + ? parseState.Count + keySpec.BeginSearch.Index + : keySpec.BeginSearch.Index - (isSubCommand ? 2 : 1); + + if (beginSearchIdx < 0 || beginSearchIdx >= parseState.Count) + return false; + + var firstKeyIdx = -1; + + // If the begin search is an index type - use the specified index as a constant + if (keySpec.BeginSearch.IsIndexType) + { + firstKeyIdx = beginSearchIdx; + } + // If the begin search is a keyword type - search for the keyword in the parse state, starting at the specified index + else + { + var step = keySpec.BeginSearch.Index < 0 ? -1 : 1; + for (var i = beginSearchIdx; i < parseState.Count; i += step) { - return false; + if (parseState.GetArgSliceByRef(i).ReadOnlySpan + .EqualsUpperCaseSpanIgnoringCase(keySpec.BeginSearch.Keyword)) + { + // The begin search index is the argument immediately after the keyword + firstKeyIdx = i + 1; + break; + } } } - if (startIndex < 0 || startIndex >= state.Count) - return false; + // Next, determine the first, last, and step arguments for key searching based on the find keys specification + var keyStep = keySpec.FindKeys.KeyStep; + int lastKeyIdx; - if (spec.FindKeys is FindKeysKeySpecMethodBase findKey) + if (keySpec.FindKeys.IsRangeType) + { + // If the find keys is of type range with limit, the last key index is determined by the limit factor + // 0 and 1 mean no limit, 2 means half of the remaining arguments, 3 means a third, and so on. + if (keySpec.FindKeys.IsRangeLimitType) + { + var limit = keySpec.FindKeys.LastKeyOrLimit; + var keyNum = 1 + ((parseState.Count - 1 - firstKeyIdx) / keyStep); + lastKeyIdx = limit is 0 or 1 ? firstKeyIdx + ((keyNum - 1) * keyStep) + : firstKeyIdx + (((keyNum / limit) - 1) * keyStep); + } + // If the find keys is of type range with last key, the last key index is determined by the specified last key index relative to the begin search index + else + { + lastKeyIdx = keySpec.FindKeys.LastKeyOrLimit; + lastKeyIdx = lastKeyIdx < 0 ? lastKeyIdx + parseState.Count : firstKeyIdx + lastKeyIdx; + } + } + // If the find keys is of type keynum, the last key index is determined by the number of keys specified at the key number index relative to the begin search index + else { - findKey.ExtractKeys(ref state, startIndex, keys); + var keyNumIdx = beginSearchIdx + keySpec.FindKeys.KeyNumIndex; + Debug.Assert(keyNumIdx >= 0 && keyNumIdx < parseState.Count); + + var keyNumFound = parseState.TryGetInt(keyNumIdx, out var keyNum); + Debug.Assert(keyNumFound); + + firstKeyIdx += keySpec.FindKeys.FirstKey; + lastKeyIdx = firstKeyIdx + ((keyNum - 1) * keyStep); } + Debug.Assert(lastKeyIdx < parseState.Count); + + searchArgs = (firstKeyIdx, lastKeyIdx, keyStep); return true; } } diff --git a/libs/server/Storage/Functions/FunctionsState.cs b/libs/server/Storage/Functions/FunctionsState.cs index bf20c1ea563..1cb2877151e 100644 --- a/libs/server/Storage/Functions/FunctionsState.cs +++ b/libs/server/Storage/Functions/FunctionsState.cs @@ -39,7 +39,7 @@ public FunctionsState(TsavoriteLog appendOnlyFile, WatchVersionMap watchVersionM this.memoryPool = memoryPool ?? MemoryPool.Shared; this.objectStoreSizeTracker = objectStoreSizeTracker; this.garnetObjectSerializer = storeWrapper.GarnetObjectSerializer; - storeFunctions = storeWrapper.objectStoreFunctions; + storeFunctions = storeWrapper.storeFunctions; this.etagState = new ETagState(); this.logger = logger; diff --git a/libs/server/Storage/Functions/MainStore/PrivateMethods.cs b/libs/server/Storage/Functions/MainStore/PrivateMethods.cs index 89794d95d73..fd9ca13350f 100644 --- a/libs/server/Storage/Functions/MainStore/PrivateMethods.cs +++ b/libs/server/Storage/Functions/MainStore/PrivateMethods.cs @@ -303,7 +303,7 @@ void CopyRespToWithInput(in TSourceLogRecord srcLogRecord, ref bool EvaluateExpireInPlace(ref LogRecord logRecord, ExpireOption optionType, long newExpiry, ref SpanByteAndMemory output) { - var o = (ObjectOutputHeader*)output.SpanByte.ToPointer(); + var o = (OutputHeader*)output.SpanByte.ToPointer(); o->result1 = 0; if (logRecord.Info.HasExpiration) { @@ -360,7 +360,7 @@ bool EvaluateExpireInPlace(ref LogRecord logRecord, ExpireOption optionType, lon bool EvaluateExpireCopyUpdate(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ExpireOption optionType, long newExpiry, ReadOnlySpan newValue, ref SpanByteAndMemory output) { var expiryExists = logRecord.Info.HasExpiration; - var o = (ObjectOutputHeader*)output.SpanByte.ToPointer(); + var o = (OutputHeader*)output.SpanByte.ToPointer(); o->result1 = 0; // TODO ETag? diff --git a/libs/server/Storage/Functions/MainStore/RMWMethods.cs b/libs/server/Storage/Functions/MainStore/RMWMethods.cs index b393825a506..9c99dc522e6 100644 --- a/libs/server/Storage/Functions/MainStore/RMWMethods.cs +++ b/libs/server/Storage/Functions/MainStore/RMWMethods.cs @@ -371,6 +371,12 @@ public readonly void PostInitialUpdater(ref LogRecord logRecord, in RecordSizeIn /// public readonly bool InPlaceUpdater(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref RawStringInput input, ref SpanByteAndMemory output, ref RMWInfo rmwInfo) { + if (logRecord.Info.ValueIsObject) + { + rmwInfo.Action = RMWAction.WrongType; + return false; + } + if (InPlaceUpdaterWorker(ref logRecord, in sizeInfo, ref input, ref output, ref rmwInfo)) { if (!logRecord.Info.Modified) @@ -756,8 +762,8 @@ private readonly bool InPlaceUpdaterWorker(ref LogRecord logRecord, in RecordSiz // If both EX and PERSIST were specified, EX wins if (input.arg1 > 0) { - var pbOutput = stackalloc byte[ObjectOutputHeader.Size]; - var _output = new SpanByteAndMemory(PinnedSpanByte.FromPinnedPointer(pbOutput, ObjectOutputHeader.Size)); + var pbOutput = stackalloc byte[OutputHeader.Size]; + var _output = new SpanByteAndMemory(PinnedSpanByte.FromPinnedPointer(pbOutput, OutputHeader.Size)); var newExpiry = input.arg1; if (!EvaluateExpireInPlace(ref logRecord, ExpireOption.None, newExpiry, ref _output)) @@ -1412,8 +1418,8 @@ public readonly bool CopyUpdater(in TSourceLogRecord srcLogRec Debug.Assert(newValue.Length == oldValue.Length); if (input.arg1 > 0) { - var pbOutput = stackalloc byte[ObjectOutputHeader.Size]; - var _output = new SpanByteAndMemory(PinnedSpanByte.FromPinnedPointer(pbOutput, ObjectOutputHeader.Size)); + var pbOutput = stackalloc byte[OutputHeader.Size]; + var _output = new SpanByteAndMemory(PinnedSpanByte.FromPinnedPointer(pbOutput, OutputHeader.Size)); var newExpiry = input.arg1; if (!EvaluateExpireCopyUpdate(ref dstLogRecord, in sizeInfo, ExpireOption.None, newExpiry, newValue, ref _output)) return false; diff --git a/libs/server/Storage/Functions/MainStore/ReadMethods.cs b/libs/server/Storage/Functions/MainStore/ReadMethods.cs index c222cde3fe9..d44601e6782 100644 --- a/libs/server/Storage/Functions/MainStore/ReadMethods.cs +++ b/libs/server/Storage/Functions/MainStore/ReadMethods.cs @@ -16,6 +16,12 @@ namespace Garnet.server public bool Reader(in TSourceLogRecord srcLogRecord, ref RawStringInput input, ref SpanByteAndMemory output, ref ReadInfo readInfo) where TSourceLogRecord : ISourceLogRecord { + if (srcLogRecord.Info.ValueIsObject) + { + readInfo.Action = ReadAction.WrongType; + return false; + } + if (CheckExpiry(in srcLogRecord)) return false; diff --git a/libs/server/Storage/Functions/MainStore/VarLenInputMethods.cs b/libs/server/Storage/Functions/MainStore/VarLenInputMethods.cs index 6a101163fad..7f331435e5c 100644 --- a/libs/server/Storage/Functions/MainStore/VarLenInputMethods.cs +++ b/libs/server/Storage/Functions/MainStore/VarLenInputMethods.cs @@ -215,7 +215,7 @@ public RecordFieldInfo GetRMWModifiedFieldInfo(in TSourceLogRe var value = srcLogRecord.ValueSpan; fieldInfo.ValueSize = 2; // # of digits in "-1", in case of invalid number (which may throw instead) - // TODO set error as in PrivateMethods.IsValidNumber and test in caller, to avoid the log record allocation. This would require 'output' + // TODO set error as in PrivateMethods.IsValidNumber and test in caller, to avoid the log record allocation. This would require 'output' if (srcLogRecord.IsPinnedValue ? IsValidNumber(srcLogRecord.PinnedValuePointer, value.Length, out _) : IsValidNumber(value, out _)) { // TODO Consider adding a way to cache curr for the IPU call diff --git a/libs/server/Storage/Functions/ObjectStore/PrivateMethods.cs b/libs/server/Storage/Functions/ObjectStore/PrivateMethods.cs index 5e8c937ec39..debde0ea049 100644 --- a/libs/server/Storage/Functions/ObjectStore/PrivateMethods.cs +++ b/libs/server/Storage/Functions/ObjectStore/PrivateMethods.cs @@ -82,7 +82,7 @@ void WriteLogDelete(ReadOnlySpan key, long version, int sessionID) static bool EvaluateObjectExpireInPlace(ref LogRecord logRecord, ExpireOption optionType, long newExpiry, ref GarnetObjectStoreOutput output) { Debug.Assert(output.SpanByteAndMemory.IsSpanByte, "This code assumes it is called in-place and did not go pending"); - var o = (ObjectOutputHeader*)output.SpanByteAndMemory.SpanByte.ToPointer(); + var o = (OutputHeader*)output.SpanByteAndMemory.SpanByte.ToPointer(); o->result1 = 0; if (logRecord.Info.HasExpiration) { diff --git a/libs/server/Storage/Functions/ObjectStore/RMWMethods.cs b/libs/server/Storage/Functions/ObjectStore/RMWMethods.cs index fe68c8e8186..9a165656fbf 100644 --- a/libs/server/Storage/Functions/ObjectStore/RMWMethods.cs +++ b/libs/server/Storage/Functions/ObjectStore/RMWMethods.cs @@ -21,8 +21,6 @@ public bool NeedInitialUpdate(ReadOnlySpan key, ref ObjectInput input, ref switch (type) { - case GarnetObjectType.Expire: - case GarnetObjectType.Persist: case GarnetObjectType.DelIfExpIm: return false; default: @@ -60,10 +58,6 @@ public bool InitialUpdater(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, _ = logRecord.TrySetValueObject(value, in sizeInfo); return true; } - else - Debug.Assert(type != GarnetObjectType.Expire && type != GarnetObjectType.Persist, "Expire and Persist commands should have been handled already by NeedInitialUpdate."); - - Debug.Assert(type is not GarnetObjectType.Expire and not GarnetObjectType.PExpire and not GarnetObjectType.Persist, "Expire and Persist commands should have returned false from NeedInitialUpdate."); var customObjectCommand = GetCustomObjectCommand(ref input, type); value = functionsState.GetCustomObjectFactory((byte)type).Create((byte)type); @@ -99,6 +93,13 @@ public void PostInitialUpdater(ref LogRecord dstLogRecord, in RecordSizeInfo siz /// public bool InPlaceUpdater(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref RMWInfo rmwInfo) { + if (!logRecord.Info.ValueIsObject) + { + rmwInfo.Action = RMWAction.WrongType; + output.OutputFlags |= OutputFlags.WrongType; + return true; + } + if (InPlaceUpdaterWorker(ref logRecord, in sizeInfo, ref input, ref output, ref rmwInfo, out long sizeChange)) { if (!logRecord.Info.Modified) @@ -129,20 +130,6 @@ bool InPlaceUpdaterWorker(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, r switch (input.header.type) { - case GarnetObjectType.Expire: - var expirationWithOption = new ExpirationWithOption(input.arg1, input.arg2); - if (!EvaluateObjectExpireInPlace(ref logRecord, expirationWithOption.ExpireOption, expirationWithOption.ExpirationTimeInTicks, ref output)) - return false; - return true; // The options may or may not produce a result that matches up with what sizeInfo has, so return rather than drop down to AssertOptionals - case GarnetObjectType.Persist: - if (logRecord.Info.HasExpiration) - { - logRecord.RemoveExpiration(); - functionsState.CopyDefaultResp(CmdStrings.RESP_RETURN_VAL_1, ref output.SpanByteAndMemory); - } - else - functionsState.CopyDefaultResp(CmdStrings.RESP_RETURN_VAL_0, ref output.SpanByteAndMemory); - return true; case GarnetObjectType.DelIfExpIm: return true; default: @@ -170,7 +157,7 @@ bool InPlaceUpdaterWorker(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, r var garnetValueObject = Unsafe.As(logRecord.ValueObject); if (IncorrectObjectType(ref input, garnetValueObject, ref output.SpanByteAndMemory)) { - output.OutputFlags |= ObjectStoreOutputFlags.WrongType; + output.OutputFlags |= OutputFlags.WrongType; return true; } @@ -239,27 +226,6 @@ public bool PostCopyUpdater(in TSourceLogRecord srcLogRecord, switch (input.header.type) { - case GarnetObjectType.Expire: - var expirationWithOption = new ExpirationWithOption(input.arg1, input.arg2); - - // Expire will have allocated space for the expiration, so copy it over and do the "in-place" logic to replace it in the new record - if (srcLogRecord.Info.HasExpiration) - dstLogRecord.TrySetExpiration(srcLogRecord.Expiration); - if (!EvaluateObjectExpireInPlace(ref dstLogRecord, expirationWithOption.ExpireOption, expirationWithOption.ExpirationTimeInTicks, ref output)) - return false; - break; - - case GarnetObjectType.Persist: - if (!dstLogRecord.TryCopyFrom(in srcLogRecord, in sizeInfo)) - return false; - if (srcLogRecord.Info.HasExpiration) - { - dstLogRecord.RemoveExpiration(); - functionsState.CopyDefaultResp(CmdStrings.RESP_RETURN_VAL_1, ref output.SpanByteAndMemory); - } - else - functionsState.CopyDefaultResp(CmdStrings.RESP_RETURN_VAL_0, ref output.SpanByteAndMemory); - break; case GarnetObjectType.DelIfExpIm: break; default: @@ -281,7 +247,7 @@ public bool PostCopyUpdater(in TSourceLogRecord srcLogRecord, // using Clone. Currently, expire and persist commands are performed on the new copy of the object. if (IncorrectObjectType(ref input, value, ref output.SpanByteAndMemory)) { - output.OutputFlags |= ObjectStoreOutputFlags.WrongType; + output.OutputFlags |= OutputFlags.WrongType; return true; } diff --git a/libs/server/Storage/Functions/ObjectStore/ReadMethods.cs b/libs/server/Storage/Functions/ObjectStore/ReadMethods.cs index 4d603408e68..be42a750f09 100644 --- a/libs/server/Storage/Functions/ObjectStore/ReadMethods.cs +++ b/libs/server/Storage/Functions/ObjectStore/ReadMethods.cs @@ -17,6 +17,12 @@ namespace Garnet.server public bool Reader(in TSourceLogRecord srcLogRecord, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref ReadInfo readInfo) where TSourceLogRecord : ISourceLogRecord { + if (!srcLogRecord.Info.ValueIsObject) + { + readInfo.Action = ReadAction.WrongType; + return false; + } + if (srcLogRecord.Info.HasExpiration && srcLogRecord.Expiration < DateTimeOffset.Now.UtcTicks) { // Do not set 'value = null' or otherwise mark this; Reads should not update the database. We rely on consistently checking for expiration everywhere. @@ -40,15 +46,6 @@ public bool Reader(in TSourceLogRecord srcLogRecord, ref Objec functionsState.CopyRespNumber(ttlValue, ref output.SpanByteAndMemory); return true; - case GarnetObjectType.ExpireTime: - var expireTime = ConvertUtils.UnixTimeInSecondsFromTicks(srcLogRecord.Info.HasExpiration ? srcLogRecord.Expiration : -1); - functionsState.CopyRespNumber(expireTime, ref output.SpanByteAndMemory); - return true; - case GarnetObjectType.PExpireTime: - expireTime = ConvertUtils.UnixTimeInMillisecondsFromTicks(srcLogRecord.Info.HasExpiration ? srcLogRecord.Expiration : -1); - functionsState.CopyRespNumber(expireTime, ref output.SpanByteAndMemory); - return true; - default: if ((byte)input.header.type < CustomCommandManager.CustomTypeIdStartOffset) { @@ -61,7 +58,7 @@ public bool Reader(in TSourceLogRecord srcLogRecord, ref Objec if (IncorrectObjectType(ref input, (IGarnetObject)srcLogRecord.ValueObject, ref output.SpanByteAndMemory)) { - output.OutputFlags |= ObjectStoreOutputFlags.WrongType; + output.OutputFlags |= OutputFlags.WrongType; return true; } diff --git a/libs/server/Storage/Functions/ObjectStore/VarLenInputMethods.cs b/libs/server/Storage/Functions/ObjectStore/VarLenInputMethods.cs index 0ab55185b30..92f38a1bc57 100644 --- a/libs/server/Storage/Functions/ObjectStore/VarLenInputMethods.cs +++ b/libs/server/Storage/Functions/ObjectStore/VarLenInputMethods.cs @@ -28,7 +28,7 @@ public RecordFieldInfo GetRMWInitialFieldInfo(ReadOnlySpan key, ref Object public RecordFieldInfo GetRMWModifiedFieldInfo(in TSourceLogRecord srcLogRecord, ref ObjectInput input) where TSourceLogRecord : ISourceLogRecord { - var fieldInfo = new RecordFieldInfo() + return new RecordFieldInfo() { KeySize = srcLogRecord.Key.Length, ValueSize = ObjectIdMap.ObjectIdSize, @@ -36,21 +36,6 @@ public RecordFieldInfo GetRMWModifiedFieldInfo(in TSourceLogRe HasETag = false, // TODO ETag not supported in Object store yet: input.header.CheckWithETagFlag(), HasExpiration = srcLogRecord.Info.HasExpiration }; - - switch (input.header.type) - { - case GarnetObjectType.Expire: - case GarnetObjectType.PExpire: - fieldInfo.HasExpiration = true; - return fieldInfo; - - case GarnetObjectType.Persist: - fieldInfo.HasExpiration = false; - return fieldInfo; - - default: - return fieldInfo; - } } public RecordFieldInfo GetUpsertFieldInfo(ReadOnlySpan key, ReadOnlySpan value, ref ObjectInput input) diff --git a/libs/server/Storage/Functions/UnifiedStore/CallbackMethods.cs b/libs/server/Storage/Functions/UnifiedStore/CallbackMethods.cs new file mode 100644 index 00000000000..6938cebc2d7 --- /dev/null +++ b/libs/server/Storage/Functions/UnifiedStore/CallbackMethods.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Tsavorite.core; + +namespace Garnet.server +{ + /// + /// Unified store functions + /// + public readonly unsafe partial struct UnifiedSessionFunctions : ISessionFunctions + { + public void ReadCompletionCallback(ref DiskLogRecord diskLogRecord, ref UnifiedStoreInput input, + ref GarnetUnifiedStoreOutput output, long ctx, Status status, RecordMetadata recordMetadata) + { + } + + public void RMWCompletionCallback(ref DiskLogRecord diskLogRecord, ref UnifiedStoreInput input, + ref GarnetUnifiedStoreOutput output, long ctx, Status status, RecordMetadata recordMetadata) + { + } + } +} \ No newline at end of file diff --git a/libs/server/Storage/Functions/UnifiedStore/DeleteMethods.cs b/libs/server/Storage/Functions/UnifiedStore/DeleteMethods.cs new file mode 100644 index 00000000000..223bb1f8a37 --- /dev/null +++ b/libs/server/Storage/Functions/UnifiedStore/DeleteMethods.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Tsavorite.core; + +namespace Garnet.server +{ + /// + /// Unified store functions + /// + public readonly unsafe partial struct UnifiedSessionFunctions : ISessionFunctions + { + public bool InitialDeleter(ref LogRecord logRecord, ref DeleteInfo deleteInfo) + { + logRecord.InfoRef.ClearHasETag(); + functionsState.watchVersionMap.IncrementVersion(deleteInfo.KeyHash); + return true; + } + + public void PostInitialDeleter(ref LogRecord logRecord, ref DeleteInfo deleteInfo) + { + if (functionsState.appendOnlyFile != null) + WriteLogDelete(logRecord.Key, deleteInfo.Version, deleteInfo.SessionID); + } + + public bool InPlaceDeleter(ref LogRecord logRecord, ref DeleteInfo deleteInfo) + { + logRecord.ClearOptionals(); + if (!logRecord.Info.Modified) + functionsState.watchVersionMap.IncrementVersion(deleteInfo.KeyHash); + + if (functionsState.appendOnlyFile != null) + WriteLogDelete(logRecord.Key, deleteInfo.Version, deleteInfo.SessionID); + + if (logRecord.Info.ValueIsObject) + { + // Can't access 'this' in a lambda so dispose directly and pass a no-op lambda. + functionsState.storeFunctions.DisposeValueObject(logRecord.ValueObject, DisposeReason.Deleted); + logRecord.ClearValueIfHeap(obj => { }); + } + + return true; + } + } +} \ No newline at end of file diff --git a/libs/server/Storage/Functions/UnifiedStore/PrivateMethods.cs b/libs/server/Storage/Functions/UnifiedStore/PrivateMethods.cs new file mode 100644 index 00000000000..1d2a155d832 --- /dev/null +++ b/libs/server/Storage/Functions/UnifiedStore/PrivateMethods.cs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; +using Garnet.common; +using Microsoft.Extensions.Logging; +using Tsavorite.core; + +namespace Garnet.server +{ + /// + /// Unified store functions + /// + public readonly unsafe partial struct UnifiedSessionFunctions : ISessionFunctions + { + /// + /// Logging upsert from + /// a. InPlaceWriter + /// b. PostInitialWriter + /// + void WriteLogUpsert(ReadOnlySpan key, ref UnifiedStoreInput input, ReadOnlySpan value, long version, int sessionID) + { + if (functionsState.StoredProcMode) + return; + + input.header.flags |= RespInputFlags.Deterministic; + + functionsState.appendOnlyFile.Enqueue( + new AofHeader { opType = AofEntryType.UnifiedStoreStringUpsert, storeVersion = version, sessionID = sessionID }, + key, value, out _); + } + + /// + /// Logging upsert from + /// a. InPlaceWriter + /// b. PostInitialWriter + /// + void WriteLogUpsert(ReadOnlySpan key, ref UnifiedStoreInput input, IGarnetObject value, long version, int sessionID) + { + if (functionsState.StoredProcMode) + return; + + input.header.flags |= RespInputFlags.Deterministic; + + GarnetObjectSerializer.Serialize(value, out var valueBytes); + fixed (byte* valPtr = valueBytes) + { + functionsState.appendOnlyFile.Enqueue( + new AofHeader { opType = AofEntryType.UnifiedStoreObjectUpsert, storeVersion = version, sessionID = sessionID }, + key, new ReadOnlySpan(valPtr, valueBytes.Length), out _); + } + } + + /// + /// Logging Delete from + /// a. InPlaceDeleter + /// b. PostInitialDeleter + /// + void WriteLogDelete(ReadOnlySpan key, long version, int sessionID) + { + if (functionsState.StoredProcMode) + return; + + functionsState.appendOnlyFile.Enqueue(new AofHeader { opType = AofEntryType.UnifiedStoreDelete, storeVersion = version, sessionID = sessionID }, key, item2: default, out _); + } + + /// + /// Logging RMW from + /// a. PostInitialUpdater + /// b. InPlaceUpdater + /// c. PostCopyUpdater + /// + void WriteLogRMW(ReadOnlySpan key, ref UnifiedStoreInput input, long version, int sessionId) + { + if (functionsState.StoredProcMode) return; + input.header.flags |= RespInputFlags.Deterministic; + + functionsState.appendOnlyFile.Enqueue( + new AofHeader { opType = AofEntryType.UnifiedStoreRMW, storeVersion = version, sessionID = sessionId }, + key, ref input, out _); + } + + bool EvaluateExpireCopyUpdate(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ExpireOption optionType, long newExpiry, ReadOnlySpan newValue, ref GarnetUnifiedStoreOutput output) + { + // TODO ETag? + if (!logRecord.TrySetValueSpan(newValue, in sizeInfo)) + { + functionsState.logger?.LogError("Failed to set value in {methodName}", "EvaluateExpireCopyUpdate"); + return false; + } + + return TrySetRecordExpiration(ref logRecord, optionType, newExpiry, ref output); + } + + bool EvaluateExpireInPlace(ref LogRecord logRecord, ExpireOption optionType, long newExpiry, ref GarnetUnifiedStoreOutput output) + { + Debug.Assert(output.SpanByteAndMemory.IsSpanByte, "This code assumes it is called in-place and did not go pending"); + + return TrySetRecordExpiration(ref logRecord, optionType, newExpiry, ref output); + } + + bool TrySetRecordExpiration(ref LogRecord logRecord, ExpireOption optionType, long newExpiry, ref GarnetUnifiedStoreOutput output) + { + var o = (OutputHeader*)output.SpanByteAndMemory.SpanByte.ToPointer(); + o->result1 = 0; + var expiryExists = logRecord.Info.HasExpiration; + + if (expiryExists) + { + // Expiration already exists so there is no need to check for space (i.e. failure of TrySetExpiration) + switch (optionType) + { + case ExpireOption.NX: + return true; + case ExpireOption.XX: + case ExpireOption.None: + _ = logRecord.TrySetExpiration(newExpiry); + o->result1 = 1; + return true; + case ExpireOption.GT: + case ExpireOption.XXGT: + if (newExpiry > logRecord.Expiration) + { + _ = logRecord.TrySetExpiration(newExpiry); + o->result1 = 1; + } + return true; + case ExpireOption.LT: + case ExpireOption.XXLT: + if (newExpiry < logRecord.Expiration) + { + _ = logRecord.TrySetExpiration(newExpiry); + o->result1 = 1; + } + return true; + default: + throw new GarnetException($"EvaluateExpireCopyUpdate exception when expiryExists is false: optionType{optionType}"); + } + } + + // No expiration yet. Because this is CopyUpdate we should already have verified the space, but check anyway + switch (optionType) + { + case ExpireOption.NX: + case ExpireOption.None: + case ExpireOption.LT: // If expiry doesn't exist, LT should treat the current expiration as infinite + if (!logRecord.TrySetExpiration(newExpiry)) + { + functionsState.logger?.LogError("Failed to add expiration in {methodName}.{caseName}", "EvaluateExpireCopyUpdate", "LT"); + return false; + } + o->result1 = 1; + return true; + case ExpireOption.XX: + case ExpireOption.GT: + case ExpireOption.XXGT: + case ExpireOption.XXLT: + return true; + default: + throw new GarnetException($"EvaluateExpireCopyUpdate exception when expiryExists is true: optionType{optionType}"); + } + } + } +} \ No newline at end of file diff --git a/libs/server/Storage/Functions/UnifiedStore/RMWMethods.cs b/libs/server/Storage/Functions/UnifiedStore/RMWMethods.cs new file mode 100644 index 00000000000..186b7c51f5a --- /dev/null +++ b/libs/server/Storage/Functions/UnifiedStore/RMWMethods.cs @@ -0,0 +1,326 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Tsavorite.core; + +namespace Garnet.server +{ + /// + /// Unified store functions + /// + public readonly unsafe partial struct UnifiedSessionFunctions : ISessionFunctions + { + public bool NeedInitialUpdate(ReadOnlySpan key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output, + ref RMWInfo rmwInfo) + { + return input.header.cmd switch + { + RespCommand.PERSIST or + RespCommand.EXPIRE or + RespCommand.EXPIREAT or + RespCommand.PEXPIRE or + RespCommand.PEXPIREAT => false, + _ => true + }; + } + + public bool InitialUpdater(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, + ref GarnetUnifiedStoreOutput output, ref RMWInfo rmwInfo) + { + Debug.Assert(logRecord.Info.ValueIsObject || (!logRecord.Info.HasETag && !logRecord.Info.HasExpiration), + "Should not have Expiration or ETag on InitialUpdater log records"); + + return input.header.cmd switch + { + RespCommand.PERSIST or + RespCommand.EXPIRE or + RespCommand.EXPIREAT or + RespCommand.PEXPIRE or + RespCommand.PEXPIREAT => throw new Exception(), + _ => true + }; + } + + public void PostInitialUpdater(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, + ref GarnetUnifiedStoreOutput output, ref RMWInfo rmwInfo) + { + functionsState.watchVersionMap.IncrementVersion(rmwInfo.KeyHash); + if (functionsState.appendOnlyFile != null) + { + input.header.SetExpiredFlag(); + WriteLogRMW(logRecord.Key, ref input, rmwInfo.Version, rmwInfo.SessionID); + } + + if (logRecord.Info.ValueIsObject) + { + functionsState.objectStoreSizeTracker?.AddTrackedSize(logRecord.ValueObject.HeapMemorySize); + } + } + + public bool NeedCopyUpdate(in TSourceLogRecord srcLogRecord, ref UnifiedStoreInput input, + ref GarnetUnifiedStoreOutput output, ref RMWInfo rmwInfo) where TSourceLogRecord : ISourceLogRecord => true; + + public bool CopyUpdater(in TSourceLogRecord srcLogRecord, ref LogRecord dstLogRecord, + in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output, + ref RMWInfo rmwInfo) where TSourceLogRecord : ISourceLogRecord + { + + if (srcLogRecord.Info.HasExpiration && input.header.CheckExpiry(srcLogRecord.Expiration)) + { + if (!srcLogRecord.Info.ValueIsObject) + { + _ = dstLogRecord.RemoveETag(); + // reset etag state that may have been initialized earlier + ETagState.ResetState(ref functionsState.etagState); + } + + rmwInfo.Action = RMWAction.ExpireAndResume; + return false; + } + + if (srcLogRecord.Info.ValueIsObject) + { + // Defer the actual copying of data to PostCopyUpdater, so we know the record has been successfully CASed into the hash chain before we potentially + // create large allocations (e.g. if srcLogRecord is from disk, we would have to allocate the overflow byte[]). Because we are doing an update we have + // and XLock, so nobody will see the unset data even after the CAS. Tsavorite will handle cloning the ValueObject and caching serialized data as needed, + // based on whether srcLogRecord is in-memory or a DiskLogRecord. + return true; + } + + var recordHadEtagPreMutation = srcLogRecord.Info.HasETag; + var shouldUpdateEtag = recordHadEtagPreMutation; + if (shouldUpdateEtag) + { + // during checkpointing we might skip the inplace calls and go directly to copy update so we need to initialize here if needed + ETagState.SetValsForRecordWithEtag(ref functionsState.etagState, in srcLogRecord); + } + + var cmd = input.header.cmd; + + var result = cmd switch + { + RespCommand.EXPIRE => HandleExpire(srcLogRecord, ref dstLogRecord, in sizeInfo, ref shouldUpdateEtag, ref input, ref output), + RespCommand.PERSIST => HandlePersist(srcLogRecord, ref dstLogRecord, in sizeInfo, ref shouldUpdateEtag, ref output), + _ => throw new NotImplementedException() + }; + + if (!result) + return false; + + if (shouldUpdateEtag) + { + dstLogRecord.TrySetETag(functionsState.etagState.ETag + 1); + ETagState.ResetState(ref functionsState.etagState); + } + else if (recordHadEtagPreMutation) + { + // reset etag state that may have been initialized earlier + ETagState.ResetState(ref functionsState.etagState); + } + + sizeInfo.AssertOptionals(dstLogRecord.Info); + return true; + } + + public bool PostCopyUpdater(in TSourceLogRecord srcLogRecord, ref LogRecord dstLogRecord, + in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output, + ref RMWInfo rmwInfo) where TSourceLogRecord : ISourceLogRecord + { + functionsState.watchVersionMap.IncrementVersion(rmwInfo.KeyHash); + + if (srcLogRecord.Info.ValueIsObject) + { + // We're performing the object update here (and not in CopyUpdater) so that we are guaranteed that + // the record was CASed into the hash chain before it gets modified + var value = Unsafe.As(srcLogRecord.ValueObject.Clone()); + var oldValueSize = srcLogRecord.ValueObject.HeapMemorySize; + _ = dstLogRecord.TrySetValueObject(value); + + // First copy the new Value and optionals to the new record. This will also ensure space for expiration if it's present. + // Do not set actually set dstLogRecord.Expiration until we know it is a command for which we allocated length in the LogRecord for it. + if (!dstLogRecord.TrySetValueObject(value, in sizeInfo)) + return false; + + var cmd = input.header.cmd; + switch (cmd) + { + case RespCommand.EXPIRE: + var expirationWithOption = new ExpirationWithOption(input.arg1); + + // Expire will have allocated space for the expiration, so copy it over and do the "in-place" logic to replace it in the new record + if (srcLogRecord.Info.HasExpiration) + dstLogRecord.TrySetExpiration(srcLogRecord.Expiration); + if (!EvaluateExpireInPlace(ref dstLogRecord, expirationWithOption.ExpireOption, + expirationWithOption.ExpirationTimeInTicks, ref output)) + return false; + break; + + case RespCommand.PERSIST: + if (!dstLogRecord.TryCopyFrom(in srcLogRecord, in sizeInfo)) + return false; + + if (srcLogRecord.Info.HasExpiration) + { + dstLogRecord.RemoveExpiration(); + functionsState.CopyDefaultResp(CmdStrings.RESP_RETURN_VAL_1, ref output.SpanByteAndMemory); + } + else + functionsState.CopyDefaultResp(CmdStrings.RESP_RETURN_VAL_0, ref output.SpanByteAndMemory); + + break; + } + + sizeInfo.AssertOptionals(dstLogRecord.Info); + + // If oldValue has been set to null, subtract its size from the tracked heap size + var sizeAdjustment = rmwInfo.ClearSourceValueObject ? value.HeapMemorySize - oldValueSize : value.HeapMemorySize; + functionsState.objectStoreSizeTracker?.AddTrackedSize(sizeAdjustment); + } + + if (functionsState.appendOnlyFile != null) + WriteLogRMW(dstLogRecord.Key, ref input, rmwInfo.Version, rmwInfo.SessionID); + + return true; + } + + public bool InPlaceUpdater(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, + ref GarnetUnifiedStoreOutput output, ref RMWInfo rmwInfo) + { + if (InPlaceUpdaterWorker(ref logRecord, in sizeInfo, ref input, ref output, ref rmwInfo, out var sizeChange)) + { + if (!logRecord.Info.Modified) + functionsState.watchVersionMap.IncrementVersion(rmwInfo.KeyHash); + if (functionsState.appendOnlyFile != null) + WriteLogRMW(logRecord.Key, ref input, rmwInfo.Version, rmwInfo.SessionID); + + if (logRecord.Info.ValueIsObject) + functionsState.objectStoreSizeTracker?.AddTrackedSize(sizeChange); + return true; + } + return false; + } + + bool InPlaceUpdaterWorker(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output, ref RMWInfo rmwInfo, out long sizeChange) + { + sizeChange = 0; + + // Expired data + if (logRecord.Info.HasExpiration && input.header.CheckExpiry(logRecord.Expiration)) + { + if (logRecord.Info.ValueIsObject) + { + functionsState.objectStoreSizeTracker?.AddTrackedSize(-logRecord.ValueObject.HeapMemorySize); + + // Can't access 'this' in a lambda so dispose directly and pass a no-op lambda. + functionsState.storeFunctions.DisposeValueObject(logRecord.ValueObject, DisposeReason.Deleted); + logRecord.ClearValueIfHeap(_ => { }); + } + else + { + logRecord.RemoveETag(); + } + + rmwInfo.Action = RMWAction.ExpireAndResume; + + return false; + } + + var hadETagPreMutation = logRecord.Info.HasETag; + var shouldUpdateEtag = hadETagPreMutation; + if (shouldUpdateEtag) + ETagState.SetValsForRecordWithEtag(ref functionsState.etagState, in logRecord); + var shouldCheckExpiration = true; + + var cmd = input.header.cmd; + switch (cmd) + { + case RespCommand.EXPIRE: + var expirationWithOption = new ExpirationWithOption(input.arg1); + + if (!logRecord.Info.ValueIsObject) + { + // reset etag state that may have been initialized earlier, but don't update etag because only the expiration was updated + ETagState.ResetState(ref functionsState.etagState); + } + + return EvaluateExpireInPlace(ref logRecord, expirationWithOption.ExpireOption, + expirationWithOption.ExpirationTimeInTicks, ref output); + case RespCommand.PERSIST: + if (logRecord.Info.HasExpiration) + { + logRecord.RemoveExpiration(); + functionsState.CopyDefaultResp(CmdStrings.RESP_RETURN_VAL_1, ref output.SpanByteAndMemory); + } + else + functionsState.CopyDefaultResp(CmdStrings.RESP_RETURN_VAL_0, ref output.SpanByteAndMemory); + + if (!logRecord.Info.ValueIsObject) + { + // reset etag state that may have been initialized earlier, but don't update etag because only the metadata was updated + ETagState.ResetState(ref functionsState.etagState); + shouldUpdateEtag = false; + } + else + { + return true; + } + break; + default: + throw new NotImplementedException(); + } + + if (!logRecord.Info.ValueIsObject) + { + // increment the Etag transparently if in place update happened + if (shouldUpdateEtag) + { + logRecord.TrySetETag(this.functionsState.etagState.ETag + 1); + ETagState.ResetState(ref functionsState.etagState); + } + else if (hadETagPreMutation) + { + // reset etag state that may have been initialized earlier + ETagState.ResetState(ref functionsState.etagState); + } + } + + sizeInfo.AssertOptionals(logRecord.Info, checkExpiration: shouldCheckExpiration); + return true; + } + + private bool HandleExpire(in TSourceLogRecord srcLogRecord, ref LogRecord dstLogRecord, + in RecordSizeInfo sizeInfo, ref bool shouldUpdateEtag, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output) where TSourceLogRecord : ISourceLogRecord + { + shouldUpdateEtag = false; + var expirationWithOption = new ExpirationWithOption(input.arg1); + + // First copy the old Value and non-Expiration optionals to the new record. This will also ensure space for expiration. + if (!dstLogRecord.TryCopyFrom(in srcLogRecord, in sizeInfo)) + return false; + + return EvaluateExpireCopyUpdate(ref dstLogRecord, in sizeInfo, expirationWithOption.ExpireOption, + expirationWithOption.ExpirationTimeInTicks, dstLogRecord.ValueSpan, ref output); + } + + private bool HandlePersist(in TSourceLogRecord srcLogRecord, ref LogRecord dstLogRecord, + in RecordSizeInfo sizeInfo, ref bool shouldUpdateEtag, ref GarnetUnifiedStoreOutput output) where TSourceLogRecord : ISourceLogRecord + { + shouldUpdateEtag = false; + if (!dstLogRecord.TryCopyFrom(in srcLogRecord, in sizeInfo)) + return false; + + if (srcLogRecord.Info.HasExpiration) + { + dstLogRecord.RemoveExpiration(); + functionsState.CopyDefaultResp(CmdStrings.RESP_RETURN_VAL_1, ref output.SpanByteAndMemory); + } + else + functionsState.CopyDefaultResp(CmdStrings.RESP_RETURN_VAL_0, ref output.SpanByteAndMemory); + + return true; + } + } +} \ No newline at end of file diff --git a/libs/server/Storage/Functions/UnifiedStore/ReadMethods.cs b/libs/server/Storage/Functions/UnifiedStore/ReadMethods.cs new file mode 100644 index 00000000000..479bbff9c74 --- /dev/null +++ b/libs/server/Storage/Functions/UnifiedStore/ReadMethods.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using Garnet.common; +using Tsavorite.core; + +namespace Garnet.server +{ +#pragma warning disable IDE0005 // Using directive is unnecessary. + using static LogRecordUtils; + + /// + /// Unified store functions + /// + public readonly unsafe partial struct UnifiedSessionFunctions : ISessionFunctions + { + public bool Reader(in TSourceLogRecord srcLogRecord, ref UnifiedStoreInput input, + ref GarnetUnifiedStoreOutput output, ref ReadInfo readInfo) where TSourceLogRecord : ISourceLogRecord + { + if (CheckExpiry(in srcLogRecord)) + { + readInfo.Action = ReadAction.Expire; + return false; + } + + var cmd = input.header.cmd; + return cmd switch + { + RespCommand.EXISTS => true, + RespCommand.MEMORY_USAGE => HandleMemoryUsage(in srcLogRecord, ref input, ref output, ref readInfo), + RespCommand.TYPE => HandleType(in srcLogRecord, ref input, ref output, ref readInfo), + RespCommand.TTL or + RespCommand.PTTL => HandleTtl(in srcLogRecord, ref input, ref output, ref readInfo, cmd == RespCommand.PTTL), + RespCommand.EXPIRETIME or + RespCommand.PEXPIRETIME => HandleExpireTime(in srcLogRecord, ref input, ref output, ref readInfo, cmd == RespCommand.PEXPIRETIME), + _ => throw new NotImplementedException(), + }; + } + + private bool HandleMemoryUsage(in TSourceLogRecord srcLogRecord, ref UnifiedStoreInput input, + ref GarnetUnifiedStoreOutput output, ref ReadInfo readInfo) where TSourceLogRecord : ISourceLogRecord + { + long memoryUsage; + if (srcLogRecord.Info.ValueIsObject) + { + memoryUsage = RecordInfo.Size + (2 * IntPtr.Size) + // Log record length + Utility.RoundUp(srcLogRecord.Key.Length, IntPtr.Size) + MemoryUtils.ByteArrayOverhead + // Key allocation in heap with overhead + srcLogRecord.ValueObject.SerializedSize; // Value allocation in heap + } + else + { + memoryUsage = RecordInfo.Size + + Utility.RoundUp(srcLogRecord.Key.TotalSize(), RecordInfo.Size) + + Utility.RoundUp(srcLogRecord.ValueSpan.TotalSize(), RecordInfo.Size); + } + + using var writer = new RespMemoryWriter(functionsState.respProtocolVersion, ref output.SpanByteAndMemory); + writer.WriteInt64(memoryUsage); + + return true; + } + + private bool HandleType(in TSourceLogRecord srcLogRecord, ref UnifiedStoreInput input, + ref GarnetUnifiedStoreOutput output, ref ReadInfo readInfo) where TSourceLogRecord : ISourceLogRecord + { + using var writer = new RespMemoryWriter(functionsState.respProtocolVersion, ref output.SpanByteAndMemory); + + if (srcLogRecord.Info.ValueIsObject) + { + switch (srcLogRecord.ValueObject) + { + case SortedSetObject: + writer.WriteSimpleString(CmdStrings.zset); + break; + case ListObject: + writer.WriteSimpleString(CmdStrings.list); + break; + case SetObject: + writer.WriteSimpleString(CmdStrings.set); + break; + case HashObject: + writer.WriteSimpleString(CmdStrings.hash); + break; + } + } + else + { + writer.WriteSimpleString(CmdStrings.stringt); + } + + return true; + } + + private bool HandleTtl(in TSourceLogRecord srcLogRecord, ref UnifiedStoreInput input, + ref GarnetUnifiedStoreOutput output, ref ReadInfo readInfo, bool milliseconds) where TSourceLogRecord : ISourceLogRecord + { + using var writer = new RespMemoryWriter(functionsState.respProtocolVersion, ref output.SpanByteAndMemory); + + var expiration = srcLogRecord.Info.HasExpiration ? srcLogRecord.Expiration : -1; + var ttlValue = milliseconds + ? ConvertUtils.MillisecondsFromDiffUtcNowTicks(expiration) + : ConvertUtils.SecondsFromDiffUtcNowTicks(expiration); + + writer.WriteInt64(ttlValue); + return true; + } + + private bool HandleExpireTime(in TSourceLogRecord srcLogRecord, ref UnifiedStoreInput input, + ref GarnetUnifiedStoreOutput output, ref ReadInfo readInfo, bool milliseconds) where TSourceLogRecord : ISourceLogRecord + { + using var writer = new RespMemoryWriter(functionsState.respProtocolVersion, ref output.SpanByteAndMemory); + + var expiration = srcLogRecord.Info.HasExpiration ? srcLogRecord.Expiration : -1; + var expireTime = milliseconds + ? ConvertUtils.UnixTimeInMillisecondsFromTicks(expiration) + : ConvertUtils.UnixTimeInSecondsFromTicks(expiration); + + writer.WriteInt64(expireTime); + return true; + } + } +} \ No newline at end of file diff --git a/libs/server/Storage/Functions/UnifiedStore/UnifiedSessionFunctions.cs b/libs/server/Storage/Functions/UnifiedStore/UnifiedSessionFunctions.cs new file mode 100644 index 00000000000..733c037cb65 --- /dev/null +++ b/libs/server/Storage/Functions/UnifiedStore/UnifiedSessionFunctions.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Tsavorite.core; + +namespace Garnet.server +{ + /// + /// Unified store functions + /// + public readonly unsafe partial struct UnifiedSessionFunctions : ISessionFunctions + { + readonly FunctionsState functionsState; + + /// + /// Constructor + /// + internal UnifiedSessionFunctions(FunctionsState functionsState) + { + this.functionsState = functionsState; + } + + public void ConvertOutputToHeap(ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output) + { + // TODO: Inspect input to determine whether we're in a context requiring ConvertToHeap. + //output.ConvertToHeap(); + } + } +} \ No newline at end of file diff --git a/libs/server/Storage/Functions/UnifiedStore/UpsertMethods.cs b/libs/server/Storage/Functions/UnifiedStore/UpsertMethods.cs new file mode 100644 index 00000000000..40f09882516 --- /dev/null +++ b/libs/server/Storage/Functions/UnifiedStore/UpsertMethods.cs @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using Garnet.common; +using Tsavorite.core; + +namespace Garnet.server +{ + /// + /// Unified store functions + /// + public readonly unsafe partial struct UnifiedSessionFunctions : ISessionFunctions + { + public bool InitialWriter(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, + ReadOnlySpan srcValue, ref GarnetUnifiedStoreOutput output, ref UpsertInfo upsertInfo) + { + if (!logRecord.TrySetValueSpan(srcValue, in sizeInfo)) + return false; + if (input.arg1 != 0 && !logRecord.TrySetExpiration(input.arg1)) + return false; + sizeInfo.AssertOptionals(logRecord.Info); + return true; + } + + public bool InitialWriter(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, + IHeapObject srcValue, ref GarnetUnifiedStoreOutput output, ref UpsertInfo upsertInfo) + { + if (!logRecord.TrySetValueObject(srcValue, in sizeInfo)) + return false; + // TODO ETag + if (input.arg1 != 0 && !logRecord.TrySetExpiration(input.arg1)) + return false; + sizeInfo.AssertOptionals(logRecord.Info); + return true; + } + + public bool InitialWriter(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, + ref UnifiedStoreInput input, + in TSourceLogRecord inputLogRecord, ref GarnetUnifiedStoreOutput output, ref UpsertInfo upsertInfo) + where TSourceLogRecord : ISourceLogRecord + { + if (!logRecord.Info.ValueIsObject) + throw new GarnetException("Unified store should not be called with IHeapObject"); + + return logRecord.TryCopyFrom(in inputLogRecord, in sizeInfo); + } + + public void PostInitialWriter(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, + ReadOnlySpan srcValue, ref GarnetUnifiedStoreOutput output, ref UpsertInfo upsertInfo) + { + if (logRecord.Info.ValueIsObject) + { + // TODO: This is called by readcache directly, but is the only ISessionFunctions call for that; the rest is internal. Clean this up, maybe as a new PostReadCacheInsert method. + if (upsertInfo.Address == LogAddress.kInvalidAddress) + { + functionsState.objectStoreSizeTracker?.AddReadCacheTrackedSize( + MemoryUtils.CalculateHeapMemorySize(in logRecord)); + return; + } + } + + functionsState.watchVersionMap.IncrementVersion(upsertInfo.KeyHash); + if (functionsState.appendOnlyFile != null) + WriteLogUpsert(logRecord.Key, ref input, srcValue, upsertInfo.Version, upsertInfo.SessionID); + + if (logRecord.Info.ValueIsObject) + { + // TODO: Need to track original length as well, if it was overflow, and add overflow here as well as object size + // TODO: Need to track lengths written to readcache, which is now internal in Tsavorite + functionsState.objectStoreSizeTracker?.AddTrackedSize( + MemoryUtils.CalculateHeapMemorySize(in logRecord)); + } + } + + public void PostInitialWriter(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, + IHeapObject srcValue, ref GarnetUnifiedStoreOutput output, ref UpsertInfo upsertInfo) + { + var garnetObject = (IGarnetObject)srcValue; + functionsState.watchVersionMap.IncrementVersion(upsertInfo.KeyHash); + if (functionsState.appendOnlyFile != null) + WriteLogUpsert(logRecord.Key, ref input, garnetObject, upsertInfo.Version, upsertInfo.SessionID); + + // TODO: Need to track original length as well, if it was overflow, and add overflow here as well as object size + functionsState.objectStoreSizeTracker?.AddTrackedSize(MemoryUtils.CalculateHeapMemorySize(in logRecord)); + } + + public void PostInitialWriter(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, + ref UnifiedStoreInput input, in TSourceLogRecord inputLogRecord, ref GarnetUnifiedStoreOutput output, + ref UpsertInfo upsertInfo) where TSourceLogRecord : ISourceLogRecord + { + functionsState.watchVersionMap.IncrementVersion(upsertInfo.KeyHash); + if (functionsState.appendOnlyFile != null) + { + WriteLogUpsert(logRecord.Key, ref input, inputLogRecord.ValueSpan, upsertInfo.Version, + upsertInfo.SessionID); + } + + if (logRecord.Info.ValueIsObject) + { + // TODO: Need to track original length as well, if it was overflow, and add overflow here as well as object size + functionsState.objectStoreSizeTracker?.AddTrackedSize(MemoryUtils.CalculateHeapMemorySize(in logRecord)); + } + } + + public bool InPlaceWriter(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, + ReadOnlySpan newValue, ref GarnetUnifiedStoreOutput output, ref UpsertInfo upsertInfo) + + { + if (logRecord.Info.ValueIsObject) + { + var oldSize = logRecord.Info.ValueIsInline + ? 0 + : (!logRecord.Info.ValueIsObject ? logRecord.ValueSpan.Length : logRecord.ValueObject.HeapMemorySize); + + _ = logRecord.TrySetValueSpan(newValue, in sizeInfo); + if (!(input.arg1 == 0 ? logRecord.RemoveExpiration() : logRecord.TrySetExpiration(input.arg1))) + return false; + sizeInfo.AssertOptionals(logRecord.Info); + + if (!logRecord.Info.Modified) + functionsState.watchVersionMap.IncrementVersion(upsertInfo.KeyHash); + if (functionsState.appendOnlyFile != null) + WriteLogUpsert(logRecord.Key, ref input, newValue, upsertInfo.Version, upsertInfo.SessionID); + + // TODO: Need to track original length as well, if it was overflow, and add overflow here as well as object size + if (logRecord.Info.ValueIsOverflow) + functionsState.objectStoreSizeTracker?.AddTrackedSize(newValue.Length - oldSize); + return true; + } + + if (!logRecord.TrySetValueSpan(newValue, in sizeInfo)) + return false; + var ok = input.arg1 == 0 ? logRecord.RemoveExpiration() : logRecord.TrySetExpiration(input.arg1); + if (ok) + { + if (input.header.CheckWithETagFlag()) + { + var newETag = functionsState.etagState.ETag + 1; + ok = logRecord.TrySetETag(newETag); + if (ok) + { + functionsState.CopyRespNumber(newETag, ref output.SpanByteAndMemory); + } + } + else + ok = logRecord.RemoveETag(); + } + if (ok) + { + sizeInfo.AssertOptionals(logRecord.Info); + if (!logRecord.Info.Modified) + functionsState.watchVersionMap.IncrementVersion(upsertInfo.KeyHash); + if (functionsState.appendOnlyFile != null) + WriteLogUpsert(logRecord.Key, ref input, newValue, upsertInfo.Version, upsertInfo.SessionID); + return true; + } + return false; + } + + public bool InPlaceWriter(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, ref UnifiedStoreInput input, + IHeapObject newValue, ref GarnetUnifiedStoreOutput output, ref UpsertInfo upsertInfo) + { + var garnetObject = (IGarnetObject)newValue; + + var oldSize = logRecord.Info.ValueIsInline + ? 0 + : (!logRecord.Info.ValueIsObject ? logRecord.ValueSpan.Length : logRecord.ValueObject.HeapMemorySize); + + _ = logRecord.TrySetValueObject(newValue, in sizeInfo); + if (!(input.arg1 == 0 ? logRecord.RemoveExpiration() : logRecord.TrySetExpiration(input.arg1))) + return false; + sizeInfo.AssertOptionals(logRecord.Info); + + if (!logRecord.Info.Modified) + functionsState.watchVersionMap.IncrementVersion(upsertInfo.KeyHash); + if (functionsState.appendOnlyFile != null) + WriteLogUpsert(logRecord.Key, ref input, garnetObject, upsertInfo.Version, upsertInfo.SessionID); + + functionsState.objectStoreSizeTracker?.AddTrackedSize(newValue.HeapMemorySize - oldSize); + return true; + } + + public bool InPlaceWriter(ref LogRecord logRecord, in RecordSizeInfo sizeInfo, + ref UnifiedStoreInput input, in TSourceLogRecord inputLogRecord, ref GarnetUnifiedStoreOutput output, ref UpsertInfo upsertInfo) + where TSourceLogRecord : ISourceLogRecord + { + var oldSize = logRecord.Info.ValueIsInline + ? 0 + : (!logRecord.Info.ValueIsObject ? logRecord.ValueSpan.Length : logRecord.ValueObject.HeapMemorySize); + + _ = logRecord.TryCopyFrom(in inputLogRecord, in sizeInfo); + if (!(input.arg1 == 0 ? logRecord.RemoveExpiration() : logRecord.TrySetExpiration(input.arg1))) + return false; + sizeInfo.AssertOptionals(logRecord.Info); + + if (!logRecord.Info.Modified) + functionsState.watchVersionMap.IncrementVersion(upsertInfo.KeyHash); + if (functionsState.appendOnlyFile != null) + { + if (!inputLogRecord.Info.ValueIsObject) + WriteLogUpsert(logRecord.Key, ref input, logRecord.ValueSpan, upsertInfo.Version, upsertInfo.SessionID); + else + WriteLogUpsert(logRecord.Key, ref input, (IGarnetObject)logRecord.ValueObject, upsertInfo.Version, upsertInfo.SessionID); + } + + var newSize = logRecord.Info.ValueIsInline + ? 0 + : (!logRecord.Info.ValueIsObject ? logRecord.ValueSpan.Length : logRecord.ValueObject.HeapMemorySize); + functionsState.objectStoreSizeTracker?.AddTrackedSize(newSize - oldSize); + return true; + } + } +} \ No newline at end of file diff --git a/libs/server/Storage/Functions/UnifiedStore/VarLenInputMethods.cs b/libs/server/Storage/Functions/UnifiedStore/VarLenInputMethods.cs new file mode 100644 index 00000000000..95be17d3517 --- /dev/null +++ b/libs/server/Storage/Functions/UnifiedStore/VarLenInputMethods.cs @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using Tsavorite.core; + +namespace Garnet.server +{ + /// + /// Unified store functions + /// + public readonly unsafe partial struct UnifiedSessionFunctions : ISessionFunctions + { + public RecordFieldInfo GetRMWModifiedFieldInfo(in TSourceLogRecord srcLogRecord, + ref UnifiedStoreInput input) where TSourceLogRecord : ISourceLogRecord + { + var fieldInfo = new RecordFieldInfo + { + KeySize = srcLogRecord.Key.Length, + ValueSize = srcLogRecord.Info.ValueIsObject ? ObjectIdMap.ObjectIdSize : 0, + ValueIsObject = srcLogRecord.Info.ValueIsObject, + HasETag = !srcLogRecord.Info.ValueIsObject && (input.header.CheckWithETagFlag() || srcLogRecord.Info.HasETag), + HasExpiration = srcLogRecord.Info.HasExpiration + }; + + if (input.header.cmd != RespCommand.NONE) + { + var cmd = input.header.cmd; + + switch (cmd) + { + case RespCommand.EXPIRE: + { + // Set HasExpiration to match with EvaluateExpireInPlace. + if (srcLogRecord.Info.HasExpiration) + { + // case ExpireOption.NX: // HasExpiration is true so we will retain it + // case ExpireOption.XX: + // case ExpireOption.None: + // case ExpireOption.GT: + // case ExpireOption.XXGT: + // case ExpireOption.LT: + // case ExpireOption.XXLT: + fieldInfo.HasExpiration = true; // Will update or retain + } + else + { + var expirationWithOption = new ExpirationWithOption(input.arg1); + switch (expirationWithOption.ExpireOption) + { + case ExpireOption.NX: + case ExpireOption.None: + case ExpireOption.LT + : // If expiry doesn't exist, LT should treat the current expiration as infinite, so the new value must be less + fieldInfo.HasExpiration = true; // Will update or retain + break; + default: + // case ExpireOption.XX: + // case ExpireOption.GT: // If expiry doesn't exist, GT should treat the current expiration as infinite, so the new value cannot be greater + // case ExpireOption.XXGT: + // case ExpireOption.XXLT: + fieldInfo.HasExpiration = + false; // Will not add one and there is not one there now + break; + } + } + } + + if (!srcLogRecord.Info.ValueIsObject) + fieldInfo.ValueSize = srcLogRecord.ValueSpan.Length; + return fieldInfo; + case RespCommand.PERSIST: + fieldInfo.HasExpiration = false; + if (!srcLogRecord.Info.ValueIsObject) + fieldInfo.ValueSize = srcLogRecord.ValueSpan.Length; + return fieldInfo; + default: + throw new NotImplementedException(); + } + } + + fieldInfo.ValueSize = input.parseState.GetArgSliceByRef(0).Length; + fieldInfo.HasExpiration = input.arg1 != 0; + return fieldInfo; + } + + public RecordFieldInfo GetRMWInitialFieldInfo(ReadOnlySpan key, ref UnifiedStoreInput input) + { + return new RecordFieldInfo + { + KeySize = key.Length, + ValueSize = 0, + HasETag = input.header.CheckWithETagFlag() + }; + } + + public RecordFieldInfo GetUpsertFieldInfo(ReadOnlySpan key, ReadOnlySpan value, + ref UnifiedStoreInput input) + { + return new RecordFieldInfo + { + KeySize = key.Length, + ValueSize = value.Length, + ValueIsObject = false, + HasETag = input.header.CheckWithETagFlag() + }; + } + + public RecordFieldInfo GetUpsertFieldInfo(ReadOnlySpan key, IHeapObject value, ref UnifiedStoreInput input) + { + return new RecordFieldInfo + { + KeySize = key.Length, + ValueSize = ObjectIdMap.ObjectIdSize, + ValueIsObject = true, + HasETag = false + }; + } + + public RecordFieldInfo GetUpsertFieldInfo(ReadOnlySpan key, + in TSourceLogRecord inputLogRecord, + ref UnifiedStoreInput input) where TSourceLogRecord : ISourceLogRecord + { + return new RecordFieldInfo + { + KeySize = key.Length, + ValueSize = inputLogRecord.Info.ValueIsObject ? ObjectIdMap.ObjectIdSize : inputLogRecord.ValueSpan.Length, + ValueIsObject = inputLogRecord.Info.ValueIsObject, + HasETag = !inputLogRecord.Info.ValueIsObject && input.header.CheckWithETagFlag() + }; + } + } +} \ No newline at end of file diff --git a/libs/server/Storage/Session/Common/ArrayKeyIterationFunctions.cs b/libs/server/Storage/Session/Common/ArrayKeyIterationFunctions.cs index 1019608a042..61761358f72 100644 --- a/libs/server/Storage/Session/Common/ArrayKeyIterationFunctions.cs +++ b/libs/server/Storage/Session/Common/ArrayKeyIterationFunctions.cs @@ -13,23 +13,18 @@ namespace Garnet.server sealed partial class StorageSession : IDisposable { // These contain classes so instantiate once and re-initialize - private ArrayKeyIterationFunctions.MainStoreGetDBSize mainStoreDbSizeFuncs; - private ArrayKeyIterationFunctions.ObjectStoreGetDBSize objectStoreDbSizeFuncs; + private ArrayKeyIterationFunctions.UnifiedStoreGetDBSize unifiedStoreDbSizeFuncs; - // Iterators for SCAN command - private ArrayKeyIterationFunctions.MainStoreGetDBKeys mainStoreDbScanFuncs; - private ArrayKeyIterationFunctions.ObjectStoreGetDBKeys objStoreDbScanFuncs; + // Iterator for SCAN command + private ArrayKeyIterationFunctions.UnifiedStoreGetDBKeys unifiedStoreDbScanFuncs; - // Iterators for expired key deletion - private ArrayKeyIterationFunctions.MainStoreExpiredKeyDeletionScan mainStoreExpiredKeyDeletionScanFuncs; - private ArrayKeyIterationFunctions.ObjectStoreExpiredKeyDeletionScan objectStoreExpiredKeyDeletionScanFuncs; + // Iterator for expired key deletion + private ArrayKeyIterationFunctions.MainStoreExpiredKeyDeletionScan expiredKeyDeletionScanFuncs; - // Iterators for KEYS command - private ArrayKeyIterationFunctions.MainStoreGetDBKeys mainStoreDbKeysFuncs; - private ArrayKeyIterationFunctions.ObjectStoreGetDBKeys objStoreDbKeysFuncs; + // Iterator for KEYS command + private ArrayKeyIterationFunctions.UnifiedStoreGetDBKeys unifiedStoreDbKeysFuncs; long lastScanCursor; - List objStoreKeys; List Keys; /// @@ -46,13 +41,9 @@ sealed partial class StorageSession : IDisposable /// internal unsafe bool DbScan(PinnedSpanByte patternB, bool allKeys, long cursor, out long storeCursor, out List keys, long count = 10, ReadOnlySpan typeObject = default) { - const long IsObjectStoreCursor = 1L << LogAddress.kAddressBits; Keys ??= new(); Keys.Clear(); - objStoreKeys ??= new(); - objStoreKeys.Clear(); - keys = Keys; Type matchType = null; @@ -74,6 +65,10 @@ internal unsafe bool DbScan(PinnedSpanByte patternB, bool allKeys, long cursor, { matchType = typeof(HashObject); } + else if (typeObject.SequenceEqual(CmdStrings.STRING) || typeObject.SequenceEqual(CmdStrings.stringt)) + { + matchType = typeof(string); + } else if (!typeObject.SequenceEqual(CmdStrings.STRING) && !typeObject.SequenceEqual(CmdStrings.stringt)) { // Unexpected typeObject type @@ -82,60 +77,30 @@ internal unsafe bool DbScan(PinnedSpanByte patternB, bool allKeys, long cursor, } } - byte* patternPtr = patternB.ToPointer(); + var patternPtr = patternB.ToPointer(); - mainStoreDbScanFuncs ??= new(); - mainStoreDbScanFuncs.Initialize(Keys, allKeys ? null : patternPtr, patternB.Length); - objStoreDbScanFuncs ??= new(); - objStoreDbScanFuncs.Initialize(objStoreKeys, allKeys ? null : patternPtr, patternB.Length, matchType); + unifiedStoreDbScanFuncs ??= new(); + unifiedStoreDbScanFuncs.Initialize(Keys, allKeys ? null : patternPtr, patternB.Length, matchType); storeCursor = cursor; - long remainingCount = count; + var remainingCount = count; - // Cursor is zero or not an object store address - // Scan main store only for string or default key type - if ((cursor & IsObjectStoreCursor) == 0 && (typeObject.IsEmpty || typeObject.SequenceEqual(CmdStrings.STRING) || typeObject.SequenceEqual(CmdStrings.stringt))) - { - basicContext.Session.ScanCursor(ref storeCursor, count, mainStoreDbScanFuncs, validateCursor: cursor != 0 && cursor != lastScanCursor); - remainingCount -= Keys.Count; - } - - // Scan object store with the type parameter - // Check the cursor value corresponds to the object store - if (!objectStoreBasicContext.IsNull && remainingCount > 0 && (typeObject.IsEmpty || (!typeObject.SequenceEqual(CmdStrings.STRING) && !typeObject.SequenceEqual(CmdStrings.stringt)))) - { - var validateCursor = storeCursor != 0 && storeCursor != lastScanCursor; - storeCursor &= ~IsObjectStoreCursor; - objectStoreBasicContext.Session.ScanCursor(ref storeCursor, remainingCount, objStoreDbScanFuncs, validateCursor: validateCursor); - if (storeCursor != 0) - storeCursor |= IsObjectStoreCursor; - Keys.AddRange(objStoreKeys); - } + unifiedStoreBasicContext.Session.ScanCursor(ref storeCursor, count, unifiedStoreDbScanFuncs, validateCursor: cursor != 0 && cursor != lastScanCursor); + remainingCount -= Keys.Count; remainingCount -= Keys.Count; lastScanCursor = storeCursor; return true; } /// - /// Iterates over main store memory collecting expired records. + /// Iterates over store memory collecting expired records. /// - internal (long, long) MainStoreExpiredKeyDeletionScan(long fromAddress, long untilAddress) + internal (long, long) ExpiredKeyDeletionScan(long fromAddress, long untilAddress) { - mainStoreExpiredKeyDeletionScanFuncs ??= new(); - mainStoreExpiredKeyDeletionScanFuncs.Initialize(this); - _ = basicContext.Session.ScanCursor(ref fromAddress, untilAddress, mainStoreExpiredKeyDeletionScanFuncs); - return (mainStoreExpiredKeyDeletionScanFuncs.deletedCount, mainStoreExpiredKeyDeletionScanFuncs.totalCount); - } - - /// - /// Iterates over object store memory collecting expired records. - /// - internal (long, long) ObjectStoreExpiredKeyDeletionScan(long fromAddress, long untilAddress) - { - objectStoreExpiredKeyDeletionScanFuncs ??= new(); - objectStoreExpiredKeyDeletionScanFuncs.Initialize(this); - _ = objectStoreBasicContext.Session.ScanCursor(ref fromAddress, untilAddress, objectStoreExpiredKeyDeletionScanFuncs); - return (objectStoreExpiredKeyDeletionScanFuncs.deletedCount, objectStoreExpiredKeyDeletionScanFuncs.totalCount); + expiredKeyDeletionScanFuncs ??= new(); + expiredKeyDeletionScanFuncs.Initialize(this); + _ = unifiedStoreBasicContext.Session.ScanCursor(ref fromAddress, untilAddress, expiredKeyDeletionScanFuncs); + return (expiredKeyDeletionScanFuncs.deletedCount, expiredKeyDeletionScanFuncs.totalCount); } /// @@ -171,7 +136,7 @@ internal ITsavoriteScanIterator IterateMainStore() /// /// internal bool IterateObjectStore(ref TScanFunctions scanFunctions, ref long cursor, long untilAddress = -1, long maxAddress = long.MaxValue, bool validateCursor = false, bool includeTombstones = false) - where TScanFunctions : IScanIteratorFunctions + where TScanFunctions : IScanIteratorFunctions => objectStoreBasicContext.Session.IterateLookup(ref scanFunctions, ref cursor, untilAddress, validateCursor: validateCursor, maxAddress: maxAddress, resetCursor: false, includeTombstones: includeTombstones); /// @@ -180,6 +145,27 @@ internal bool IterateObjectStore(ref TScanFunctions scanFunction internal ITsavoriteScanIterator IterateObjectStore() => objectStoreBasicContext.Session.Iterate(); + /// + /// Iterate the contents of the unified store (push-based) + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal bool IterateUnifiedStore(ref TScanFunctions scanFunctions, ref long cursor, long untilAddress = -1, long maxAddress = long.MaxValue, bool validateCursor = false, bool includeTombstones = false) + where TScanFunctions : IScanIteratorFunctions + => unifiedStoreBasicContext.Session.IterateLookup(ref scanFunctions, ref cursor, untilAddress, validateCursor: validateCursor, maxAddress: maxAddress, resetCursor: false, includeTombstones: includeTombstones); + + /// + /// Iterate the contents of the unified store (pull based) + /// + internal ITsavoriteScanIterator IterateUnifiedStore() + => unifiedStoreBasicContext.Session.Iterate(); + /// /// Get a list of the keys in the store and object store when using pattern /// @@ -191,16 +177,9 @@ internal unsafe List DBKeys(PinnedSpanByte pattern) var allKeys = *pattern.ToPointer() == '*' && pattern.Length == 1; - mainStoreDbKeysFuncs ??= new(); - mainStoreDbKeysFuncs.Initialize(Keys, allKeys ? null : pattern.ToPointer(), pattern.Length); - basicContext.Session.Iterate(ref mainStoreDbKeysFuncs); - - if (!objectStoreBasicContext.IsNull) - { - objStoreDbKeysFuncs ??= new(); - objStoreDbKeysFuncs.Initialize(Keys, allKeys ? null : pattern.ToPointer(), pattern.Length, matchType: null); - objectStoreBasicContext.Session.Iterate(ref objStoreDbKeysFuncs); - } + unifiedStoreDbKeysFuncs ??= new(); + unifiedStoreDbKeysFuncs.Initialize(Keys, allKeys ? null : pattern.ToPointer(), pattern.Length); + unifiedStoreBasicContext.Session.Iterate(ref unifiedStoreDbKeysFuncs); return Keys; } @@ -211,21 +190,12 @@ internal unsafe List DBKeys(PinnedSpanByte pattern) /// internal int DbSize() { - mainStoreDbSizeFuncs ??= new(); - mainStoreDbSizeFuncs.Initialize(); + unifiedStoreDbSizeFuncs ??= new(); + unifiedStoreDbSizeFuncs.Initialize(); long cursor = 0; - basicContext.Session.ScanCursor(ref cursor, long.MaxValue, mainStoreDbSizeFuncs); - int count = mainStoreDbSizeFuncs.Count; - if (objectStoreBasicContext.Session != null) - { - objectStoreDbSizeFuncs ??= new(); - objectStoreDbSizeFuncs.Initialize(); - cursor = 0; - _ = objectStoreBasicContext.Session.ScanCursor(ref cursor, long.MaxValue, objectStoreDbSizeFuncs); - count += objectStoreDbSizeFuncs.Count; - } + unifiedStoreBasicContext.Session.ScanCursor(ref cursor, long.MaxValue, unifiedStoreDbSizeFuncs); - return count; + return unifiedStoreDbSizeFuncs.Count; } internal static unsafe class ArrayKeyIterationFunctions @@ -247,20 +217,17 @@ internal void Initialize(List keys, byte* patternB, int length, Type mat } } - internal sealed class ObjectStoreExpiredKeyDeletionScan : ExpiredKeysBase - { - protected override bool DeleteIfExpiredInMemory(in TSourceLogRecord logRecord, RecordMetadata recordMetadata) - { - var input = new ObjectInput(new RespInputHeader(GarnetObjectType.DelIfExpIm)); - var output = new GarnetObjectStoreOutput(); - return GarnetStatus.OK == storageSession.RMW_ObjectStore(logRecord.Key, ref input, ref output, ref storageSession.objectStoreBasicContext); - } - } - internal sealed class MainStoreExpiredKeyDeletionScan : ExpiredKeysBase { protected override bool DeleteIfExpiredInMemory(in TSourceLogRecord logRecord, RecordMetadata recordMetadata) { + if (logRecord.Info.ValueIsObject) + { + var objInput = new ObjectInput(new RespInputHeader(GarnetObjectType.DelIfExpIm)); + var output = new GarnetObjectStoreOutput(); + return GarnetStatus.OK == storageSession.RMW_ObjectStore(logRecord.Key, ref objInput, ref output, ref storageSession.objectStoreBasicContext); + } + var input = new RawStringInput(RespCommand.DELIFEXPIM); return GarnetStatus.OK == storageSession.DEL_Conditional(PinnedSpanByte.FromPinnedSpan(logRecord.Key), ref input, ref storageSession.basicContext); } @@ -306,56 +273,11 @@ public void OnStop(bool completed, long numberOfRecords) { } public void OnException(Exception exception, long numberOfRecords) { } } - internal sealed class MainStoreGetDBKeys : IScanIteratorFunctions - { - private readonly GetDBKeysInfo info; - - internal MainStoreGetDBKeys() => info = new(); - - internal void Initialize(List keys, byte* patternB, int length) - => info.Initialize(keys, patternB, length); - - public bool Reader(in TSourceLogRecord logRecord, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) - where TSourceLogRecord : ISourceLogRecord - { - var key = logRecord.Key; - - if (CheckExpiry(in logRecord)) - { - cursorRecordResult = CursorRecordResult.Skip; - return true; - } - - if (info.patternB != null) - { - bool ok; - if (logRecord.IsPinnedKey) - ok = GlobUtils.Match(info.patternB, info.patternLength, logRecord.PinnedKeyPointer, key.Length, true); - else - fixed (byte* keyPtr = key) - ok = GlobUtils.Match(info.patternB, info.patternLength, keyPtr, key.Length, true); - if (!ok) - { - cursorRecordResult = CursorRecordResult.Skip; - return true; - } - } - - info.keys.Add(key.ToArray()); - cursorRecordResult = CursorRecordResult.Accept; - return true; - } - - public bool OnStart(long beginAddress, long endAddress) => true; - public void OnStop(bool completed, long numberOfRecords) { } - public void OnException(Exception exception, long numberOfRecords) { } - } - - internal sealed class ObjectStoreGetDBKeys : IScanIteratorFunctions + internal sealed class UnifiedStoreGetDBKeys : IScanIteratorFunctions { private readonly GetDBKeysInfo info; - internal ObjectStoreGetDBKeys() => info = new(); + internal UnifiedStoreGetDBKeys() => info = new(); internal void Initialize(List keys, byte* patternB, int length, Type matchType = null) => info.Initialize(keys, patternB, length, matchType); @@ -385,7 +307,9 @@ public bool Reader(in TSourceLogRecord logRecord, RecordMetada } } - if (info.matchType != null && logRecord.ValueObject.GetType() != info.matchType) + if (info.matchType != null && + ((logRecord.Info.ValueIsObject && (info.matchType == typeof(string) || info.matchType != logRecord.ValueObject.GetType())) || + (!logRecord.Info.ValueIsObject && info.matchType != typeof(string)))) { cursorRecordResult = CursorRecordResult.Skip; return true; @@ -409,37 +333,13 @@ internal class GetDBSizeInfo internal void Initialize() => count = 0; } - internal sealed class MainStoreGetDBSize : IScanIteratorFunctions - { - private readonly GetDBSizeInfo info; - - internal int Count => info.count; - - internal MainStoreGetDBSize() => info = new(); - - internal void Initialize() => info.Initialize(); - - public bool Reader(in TSourceLogRecord logRecord, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) - where TSourceLogRecord : ISourceLogRecord - { - cursorRecordResult = CursorRecordResult.Skip; - if (!CheckExpiry(in logRecord)) - ++info.count; - return true; - } - - public bool OnStart(long beginAddress, long endAddress) => true; - public void OnStop(bool completed, long numberOfRecords) { } - public void OnException(Exception exception, long numberOfRecords) { } - } - - internal sealed class ObjectStoreGetDBSize : IScanIteratorFunctions + internal sealed class UnifiedStoreGetDBSize : IScanIteratorFunctions { private readonly GetDBSizeInfo info; internal int Count => info.count; - internal ObjectStoreGetDBSize() => info = new(); + internal UnifiedStoreGetDBSize() => info = new(); internal void Initialize() => info.Initialize(); diff --git a/libs/server/Storage/Session/MainStore/AdvancedOps.cs b/libs/server/Storage/Session/MainStore/AdvancedOps.cs index 8782034934d..78c2c38bb45 100644 --- a/libs/server/Storage/Session/MainStore/AdvancedOps.cs +++ b/libs/server/Storage/Session/MainStore/AdvancedOps.cs @@ -8,13 +8,13 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; sealed partial class StorageSession : IDisposable { public GarnetStatus GET_WithPending(ReadOnlySpan key, ref RawStringInput input, ref SpanByteAndMemory output, long ctx, out bool pending, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var status = context.Read(key, ref input, ref output, ctx); @@ -39,7 +39,7 @@ public GarnetStatus GET_WithPending(ReadOnlySpan key, ref RawStr } public bool GET_CompletePending((GarnetStatus, SpanByteAndMemory)[] outputArr, bool wait, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { Debug.Assert(outputArr != null); @@ -63,7 +63,7 @@ public bool GET_CompletePending((GarnetStatus, SpanByteAndMemory)[] ou } public bool GET_CompletePending(out CompletedOutputIterator completedOutputs, bool wait, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { latencyMetrics?.Start(LatencyMetricsType.PENDING_LAT); var ret = context.CompletePendingWithOutputs(out completedOutputs, wait); @@ -72,7 +72,7 @@ public bool GET_CompletePending(out CompletedOutputIterator(ReadOnlySpan key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var status = context.RMW(key, ref input, ref output); @@ -86,7 +86,7 @@ public GarnetStatus RMW_MainStore(ReadOnlySpan key, ref RawStrin } public GarnetStatus Read_MainStore(ReadOnlySpan key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var status = context.Read(key, ref input, ref output); diff --git a/libs/server/Storage/Session/MainStore/BitmapOps.cs b/libs/server/Storage/Session/MainStore/BitmapOps.cs index b2223b4b485..8dc5f86c6f7 100644 --- a/libs/server/Storage/Session/MainStore/BitmapOps.cs +++ b/libs/server/Storage/Session/MainStore/BitmapOps.cs @@ -10,13 +10,13 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; sealed partial class StorageSession : IDisposable { public unsafe GarnetStatus StringSetBit(PinnedSpanByte key, PinnedSpanByte offset, bool bit, out bool previous, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { previous = false; @@ -38,7 +38,7 @@ public unsafe GarnetStatus StringSetBit(PinnedSpanByte key, PinnedSpan } public unsafe GarnetStatus StringGetBit(PinnedSpanByte key, PinnedSpanByte offset, out bool bValue, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { bValue = false; @@ -88,9 +88,10 @@ public unsafe GarnetStatus StringBitOperation(ref RawStringInput input, BitmapOp { createTransaction = true; Debug.Assert(txnManager.state == TxnState.None); - txnManager.SaveKeyEntryToLock(keys[0], false, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Main); + txnManager.SaveKeyEntryToLock(keys[0], LockType.Exclusive); for (var i = 1; i < keys.Length; i++) - txnManager.SaveKeyEntryToLock(keys[i], false, LockType.Shared); + txnManager.SaveKeyEntryToLock(keys[i], LockType.Shared); _ = txnManager.Run(true); } @@ -200,7 +201,7 @@ public GarnetStatus StringBitOperation(BitmapOperation bitOp, PinnedSpanByte des } public unsafe GarnetStatus StringBitCount(PinnedSpanByte key, long start, long end, bool useBitInterval, out long result, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { result = 0; @@ -265,7 +266,7 @@ public unsafe GarnetStatus StringBitCount(PinnedSpanByte key, long sta } public unsafe GarnetStatus StringBitField(PinnedSpanByte key, List commandArguments, out List result, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var input = new RawStringInput(RespCommand.BITFIELD); @@ -386,23 +387,23 @@ public unsafe GarnetStatus StringBitField(PinnedSpanByte key, List(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext => RMW_MainStore(key.ReadOnlySpan, ref input, ref output, ref context); public GarnetStatus StringGetBit(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext => Read_MainStore(key.ReadOnlySpan, ref input, ref output, ref context); public unsafe GarnetStatus StringBitCount(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext => Read_MainStore(key.ReadOnlySpan, ref input, ref output, ref context); public unsafe GarnetStatus StringBitPosition(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext => Read_MainStore(key.ReadOnlySpan, ref input, ref output, ref context); public unsafe GarnetStatus StringBitField(PinnedSpanByte key, ref RawStringInput input, RespCommand secondaryCommand, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { GarnetStatus status; if (secondaryCommand == RespCommand.GET) @@ -416,7 +417,7 @@ public unsafe GarnetStatus StringBitField(PinnedSpanByte key, ref RawS } public unsafe GarnetStatus StringBitFieldReadOnly(PinnedSpanByte key, ref RawStringInput input, RespCommand secondaryCommand, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var status = GarnetStatus.NOTFOUND; diff --git a/libs/server/Storage/Session/MainStore/CompletePending.cs b/libs/server/Storage/Session/MainStore/CompletePending.cs index 59625f713cf..bb264d5ebac 100644 --- a/libs/server/Storage/Session/MainStore/CompletePending.cs +++ b/libs/server/Storage/Session/MainStore/CompletePending.cs @@ -6,25 +6,30 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; sealed partial class StorageSession { /// /// Handles the complete pending status for Session Store /// - /// - /// - /// static void CompletePendingForSession(ref Status status, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext + => CompletePendingForSession(ref status, ref output, ref context, out _); + + /// + /// Handles the complete pending status for Session Store + /// + static void CompletePendingForSession(ref Status status, ref SpanByteAndMemory output, ref TContext context, out RecordMetadata recordMetadata) + where TContext : ITsavoriteContext { context.CompletePendingWithOutputs(out var completedOutputs, wait: true); var more = completedOutputs.Next(); Debug.Assert(more); status = completedOutputs.Current.Status; output = completedOutputs.Current.Output; + recordMetadata = completedOutputs.Current.RecordMetadata; more = completedOutputs.Next(); Debug.Assert(!more); completedOutputs.Dispose(); diff --git a/libs/server/Storage/Session/MainStore/HyperLogLogOps.cs b/libs/server/Storage/Session/MainStore/HyperLogLogOps.cs index 2568d074ede..27d1eb6d11c 100644 --- a/libs/server/Storage/Session/MainStore/HyperLogLogOps.cs +++ b/libs/server/Storage/Session/MainStore/HyperLogLogOps.cs @@ -7,8 +7,8 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; sealed partial class StorageSession : IDisposable { @@ -16,7 +16,7 @@ sealed partial class StorageSession : IDisposable /// Adds all the element arguments to the HyperLogLog data structure stored at the variable name specified as key. /// public unsafe GarnetStatus HyperLogLogAdd(PinnedSpanByte key, string[] elements, out bool updated, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { updated = false; @@ -60,11 +60,11 @@ public unsafe GarnetStatus HyperLogLogAdd(PinnedSpanByte key, string[] /// /// public GarnetStatus HyperLogLogAdd(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext => RMW_MainStore(key.ReadOnlySpan, ref input, ref output, ref context); public unsafe GarnetStatus HyperLogLogLength(Span keys, out long count, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { parseState.Initialize(keys.Length); for (var i = 0; i < keys.Length; i++) @@ -87,7 +87,7 @@ public unsafe GarnetStatus HyperLogLogLength(Span keys /// /// public unsafe GarnetStatus HyperLogLogLength(ref RawStringInput input, out long count, out bool error, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { error = false; count = default; @@ -102,11 +102,12 @@ public unsafe GarnetStatus HyperLogLogLength(ref RawStringInput input, Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; var dstKey = input.parseState.GetArgSliceByRef(0); - txnManager.SaveKeyEntryToLock(dstKey, false, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Main); + txnManager.SaveKeyEntryToLock(dstKey, LockType.Exclusive); for (var i = 1; i < input.parseState.Count; i++) { var currSrcKey = input.parseState.GetArgSliceByRef(i); - txnManager.SaveKeyEntryToLock(currSrcKey, false, LockType.Shared); + txnManager.SaveKeyEntryToLock(currSrcKey, LockType.Shared); } _ = txnManager.Run(true); } @@ -194,12 +195,13 @@ public unsafe GarnetStatus HyperLogLogMerge(ref RawStringInput input, out bool e { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Main); var dstKey = input.parseState.GetArgSliceByRef(0); - txnManager.SaveKeyEntryToLock(dstKey, false, LockType.Exclusive); + txnManager.SaveKeyEntryToLock(dstKey, LockType.Exclusive); for (var i = 1; i < input.parseState.Count; i++) { var currSrcKey = input.parseState.GetArgSliceByRef(i); - txnManager.SaveKeyEntryToLock(currSrcKey, false, LockType.Shared); + txnManager.SaveKeyEntryToLock(currSrcKey, LockType.Shared); } _ = txnManager.Run(true); } diff --git a/libs/server/Storage/Session/MainStore/MainStoreOps.cs b/libs/server/Storage/Session/MainStore/MainStoreOps.cs index 49cc9b94aea..d7ffb1a8137 100644 --- a/libs/server/Storage/Session/MainStore/MainStoreOps.cs +++ b/libs/server/Storage/Session/MainStore/MainStoreOps.cs @@ -10,16 +10,13 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; sealed partial class StorageSession : IDisposable { public GarnetStatus GET(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { long ctx = default; var status = context.Read(key.ReadOnlySpan, ref input, ref output, ctx); @@ -44,7 +41,7 @@ public GarnetStatus GET(PinnedSpanByte key, ref RawStringInput input, } public unsafe GarnetStatus ReadWithUnsafeContext(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output, long localHeadAddress, out bool epochChanged, ref TContext context) - where TContext : ITsavoriteContext, IUnsafeContext + where TContext : ITsavoriteContext, IUnsafeContext { epochChanged = false; var status = context.Read(key.ReadOnlySpan, ref Unsafe.AsRef(in input), ref output, userContext: default); @@ -77,7 +74,7 @@ public unsafe GarnetStatus ReadWithUnsafeContext(PinnedSpanByte key, r } public unsafe GarnetStatus GET(PinnedSpanByte key, out PinnedSpanByte value, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var input = new RawStringInput(RespCommand.GET); value = default; @@ -101,7 +98,7 @@ public unsafe GarnetStatus GET(PinnedSpanByte key, out PinnedSpanByte } public unsafe GarnetStatus GET(PinnedSpanByte key, out MemoryResult value, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var input = new RawStringInput(RespCommand.GET); @@ -113,7 +110,7 @@ public unsafe GarnetStatus GET(PinnedSpanByte key, out MemoryResult(PinnedSpanByte key, out GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { ObjectInput input = default; output = default; @@ -139,7 +136,7 @@ public GarnetStatus GET(PinnedSpanByte key, out GarnetObjectStor } public unsafe GarnetStatus GETEX(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var status = context.RMW(key.ReadOnlySpan, ref input, ref output); @@ -170,7 +167,7 @@ public unsafe GarnetStatus GETEX(PinnedSpanByte key, ref RawStringInpu /// Basic Context of the store /// Operation status public unsafe GarnetStatus GETDEL(PinnedSpanByte key, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var input = new RawStringInput(RespCommand.GETDEL); @@ -184,7 +181,7 @@ public unsafe GarnetStatus GETDEL(PinnedSpanByte key, ref SpanByteAndM } public unsafe GarnetStatus GETRANGE(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var status = context.Read(key.ReadOnlySpan, ref input, ref output); @@ -221,8 +218,8 @@ public unsafe GarnetStatus GETRANGE(PinnedSpanByte key, ref RawStringI /// when true the command to execute is PTTL. /// public unsafe GarnetStatus TTL(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output, ref TContext context, ref TObjectContext objectContext, bool milliseconds = false) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext + where TContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { var cmd = milliseconds ? RespCommand.PTTL : RespCommand.TTL; var input = new RawStringInput(cmd); @@ -262,68 +259,15 @@ public unsafe GarnetStatus TTL(PinnedSpanByte key, Sto return GarnetStatus.NOTFOUND; } - /// - /// Get the absolute Unix timestamp at which the given key will expire. - /// - /// - /// - /// The key to get the Unix timestamp. - /// The store to operate on - /// Span to allocate the output of the operation - /// Basic Context of the store - /// Object Context of the store - /// when true the command to execute is PEXPIRETIME. - /// Returns the absolute Unix timestamp (since January 1, 1970) in seconds or milliseconds at which the given key will expire. - public unsafe GarnetStatus EXPIRETIME(PinnedSpanByte key, StoreType storeType, ref SpanByteAndMemory output, ref TContext context, ref TObjectContext objectContext, bool milliseconds = false) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext - { - if (storeType == StoreType.Main || storeType == StoreType.All) - { - var cmd = milliseconds ? RespCommand.PEXPIRETIME : RespCommand.EXPIRETIME; - var input = new RawStringInput(cmd); - var status = context.Read(key.ReadOnlySpan, ref input, ref output); - - if (status.IsPending) - { - StartPendingMetrics(); - CompletePendingForSession(ref status, ref output, ref context); - StopPendingMetrics(); - } - - if (status.Found) return GarnetStatus.OK; - } - - if ((storeType == StoreType.Object || storeType == StoreType.All) && !objectStoreBasicContext.IsNull) - { - var type = milliseconds ? GarnetObjectType.PExpireTime : GarnetObjectType.ExpireTime; - var header = new RespInputHeader(type); - var input = new ObjectInput(header); - - var objO = new GarnetObjectStoreOutput(output); - var status = objectContext.Read(key.ReadOnlySpan, ref input, ref objO); - - if (status.IsPending) - CompletePendingForObjectStoreSession(ref status, ref objO, ref objectContext); - - if (status.Found) - { - output = objO.SpanByteAndMemory; - return GarnetStatus.OK; - } - } - return GarnetStatus.NOTFOUND; - } - public GarnetStatus SET(PinnedSpanByte key, PinnedSpanByte value, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { context.Upsert(key.ReadOnlySpan, value.ReadOnlySpan); return GarnetStatus.OK; } public GarnetStatus SET(PinnedSpanByte key, ref RawStringInput input, PinnedSpanByte value, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var output = new SpanByteAndMemory(); context.Upsert(key.ReadOnlySpan, ref input, value.ReadOnlySpan, ref output); @@ -331,7 +275,7 @@ public GarnetStatus SET(PinnedSpanByte key, ref RawStringInput input, } public unsafe GarnetStatus SET_Conditional(PinnedSpanByte key, ref RawStringInput input, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { byte* pbOutput = stackalloc byte[8]; var o = SpanByteAndMemory.FromPinnedPointer(pbOutput, 8); @@ -359,7 +303,7 @@ public unsafe GarnetStatus SET_Conditional(PinnedSpanByte key, ref Raw public unsafe GarnetStatus DEL_Conditional(PinnedSpanByte key, ref RawStringInput input, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { Debug.Assert(input.header.cmd is RespCommand.DELIFGREATER or RespCommand.DELIFEXPIM); @@ -390,7 +334,7 @@ public unsafe GarnetStatus DEL_Conditional(PinnedSpanByte key, ref Raw } public unsafe GarnetStatus SET_Conditional(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var status = context.RMW(key.ReadOnlySpan, ref input, ref output); @@ -414,7 +358,7 @@ public unsafe GarnetStatus SET_Conditional(PinnedSpanByte key, ref Raw } internal GarnetStatus MSET_Conditional(ref RawStringInput input, ref TContext ctx) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var error = false; var count = input.parseState.Count; @@ -426,21 +370,21 @@ internal GarnetStatus MSET_Conditional(ref RawStringInput input, ref T for (var i = 0; i < count; i += 2) { var srcKey = input.parseState.GetArgSliceByRef(i); - txnManager.SaveKeyEntryToLock(srcKey, false, LockType.Exclusive); - txnManager.SaveKeyEntryToLock(srcKey, true, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Main | TransactionStoreTypes.Unified); + txnManager.SaveKeyEntryToLock(srcKey, LockType.Exclusive); } txnManager.Run(true); } var context = txnManager.TransactionalContext; - var objContext = txnManager.ObjectStoreTransactionalContext; + var unifiedContext = txnManager.UnifiedStoreTransactionalContext; try { for (var i = 0; i < count; i += 2) { var srcKey = input.parseState.GetArgSliceByRef(i); - var status = EXISTS(srcKey, StoreType.All, ref context, ref objContext); + var status = EXISTS(srcKey, ref unifiedContext); if (status != GarnetStatus.NOTFOUND) { count = 0; @@ -465,14 +409,14 @@ internal GarnetStatus MSET_Conditional(ref RawStringInput input, ref T } public GarnetStatus SET(PinnedSpanByte key, IGarnetObject value, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { objectContext.Upsert(key.ReadOnlySpan, value); return GarnetStatus.OK; } public GarnetStatus SET(PinnedSpanByte key, Memory value, ref TContext context) // TODO are memory overloads needed? - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { unsafe { @@ -482,24 +426,28 @@ public GarnetStatus SET(PinnedSpanByte key, Memory value, ref TC return GarnetStatus.OK; } - public GarnetStatus SET(in TSourceLogRecord srcLogRecord, StoreType storeType, ref TContext context, ref TObjectContext objectContext) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext + public GarnetStatus SET_Main(in TSourceLogRecord srcLogRecord, ref TContext context) + where TContext : ITsavoriteContext where TSourceLogRecord : ISourceLogRecord { - if (storeType == StoreType.Main) - context.Upsert(in srcLogRecord); - else - objectContext.Upsert(in srcLogRecord); + context.Upsert(in srcLogRecord); + return GarnetStatus.OK; + } + + public GarnetStatus SET_Object(in TSourceLogRecord srcLogRecord, ref TObjectContext objectContext) + where TObjectContext : ITsavoriteContext + where TSourceLogRecord : ISourceLogRecord + { + objectContext.Upsert(in srcLogRecord); return GarnetStatus.OK; } public unsafe GarnetStatus SETEX(PinnedSpanByte key, PinnedSpanByte value, PinnedSpanByte expiryMs, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext => SETEX(key, value, TimeSpan.FromMilliseconds(NumUtils.ReadInt64(expiryMs.Length, expiryMs.ToPointer())), ref context); public GarnetStatus SETEX(PinnedSpanByte key, PinnedSpanByte value, TimeSpan expiry, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var input = new RawStringInput(RespCommand.APPEND, ref parseState, arg1: DateTimeOffset.UtcNow.Ticks + expiry.Ticks); return SET(key, ref input, value, ref context); @@ -515,7 +463,7 @@ public GarnetStatus SETEX(PinnedSpanByte key, PinnedSpanByte value, Ti /// Store context /// Operation status public unsafe GarnetStatus APPEND(PinnedSpanByte key, PinnedSpanByte value, ref PinnedSpanByte output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var _output = new SpanByteAndMemory(output); @@ -535,7 +483,7 @@ public unsafe GarnetStatus APPEND(PinnedSpanByte key, PinnedSpanByte v /// Store context /// Operation status public unsafe GarnetStatus APPEND(PinnedSpanByte key, ref RawStringInput input, ref SpanByteAndMemory output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var status = context.RMW(key.ReadOnlySpan, ref input, ref output); if (status.IsPending) @@ -550,26 +498,18 @@ public unsafe GarnetStatus APPEND(PinnedSpanByte key, ref RawStringInp return GarnetStatus.OK; } - public GarnetStatus DELETE(PinnedSpanByte key, StoreType storeType, ref TContext context, ref TObjectContext objectContext) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext + /// + /// Deletes a key from the main store context. + /// + /// The name of the key to use in the operation + /// Basic context for the main store. + /// + public GarnetStatus DELETE_MainStore(PinnedSpanByte key, ref TContext context) + where TContext : ITsavoriteContext { - var found = false; - - if (storeType == StoreType.Main || storeType == StoreType.All) - { - var status = context.Delete(key.ReadOnlySpan); - Debug.Assert(!status.IsPending); - if (status.Found) found = true; - } - - if (!objectStoreBasicContext.IsNull && (storeType == StoreType.Object || storeType == StoreType.All)) - { - var status = objectContext.Delete(key.ReadOnlySpan); - Debug.Assert(!status.IsPending); - if (status.Found) found = true; - } - return found ? GarnetStatus.OK : GarnetStatus.NOTFOUND; + var status = context.Delete(key.ReadOnlySpan); + Debug.Assert(!status.IsPending); + return status.Found ? GarnetStatus.OK : GarnetStatus.NOTFOUND; } public unsafe GarnetStatus RENAME(PinnedSpanByte oldKeySlice, PinnedSpanByte newKeySlice, StoreType storeType, bool withEtag) @@ -602,8 +542,9 @@ private unsafe GarnetStatus RENAME(PinnedSpanByte oldKeySlice, PinnedSpanByte ne if (txnManager.state != TxnState.Running) { createTransaction = true; - txnManager.SaveKeyEntryToLock(oldKeySlice, false, LockType.Exclusive); - txnManager.SaveKeyEntryToLock(newKeySlice, false, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Main | TransactionStoreTypes.Object); + txnManager.SaveKeyEntryToLock(oldKeySlice, LockType.Exclusive); + txnManager.SaveKeyEntryToLock(newKeySlice, LockType.Exclusive); _ = txnManager.Run(true); } @@ -703,12 +644,12 @@ private unsafe GarnetStatus RENAME(PinnedSpanByte oldKeySlice, PinnedSpanByte ne // Delete the old key only when SET NX succeeded if (isNX && result == 1) { - DELETE(oldKey, StoreType.Main, ref context, ref objectContext); + DELETE_MainStore(oldKey, ref context); } else if (!isNX) { // Delete the old key - DELETE(oldKey, StoreType.Main, ref context, ref objectContext); + DELETE_MainStore(oldKey, ref context); returnStatus = GarnetStatus.OK; } } @@ -726,8 +667,9 @@ private unsafe GarnetStatus RENAME(PinnedSpanByte oldKeySlice, PinnedSpanByte ne createTransaction = false; if (txnManager.state != TxnState.Running) { - txnManager.SaveKeyEntryToLock(oldKeySlice, true, LockType.Exclusive); - txnManager.SaveKeyEntryToLock(newKeySlice, true, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object); + txnManager.SaveKeyEntryToLock(oldKeySlice, LockType.Exclusive); + txnManager.SaveKeyEntryToLock(newKeySlice, LockType.Exclusive); txnManager.Run(true); createTransaction = true; } @@ -755,7 +697,7 @@ private unsafe GarnetStatus RENAME(PinnedSpanByte oldKeySlice, PinnedSpanByte ne SET(newKeySlice, valObj, ref objectContext); // Delete the old key - DELETE(oldKeySlice, StoreType.Object, ref context, ref objectContext); + DELETE_ObjectStore(oldKeySlice, ref objectContext); result = 1; } @@ -774,278 +716,6 @@ private unsafe GarnetStatus RENAME(PinnedSpanByte oldKeySlice, PinnedSpanByte ne return returnStatus; } - /// - /// Returns if key is an existing one in the store. - /// - /// - /// - /// The name of the key to use in the operation - /// The store to operate on. - /// Basic context for the main store. - /// Object context for the object store. - /// - public GarnetStatus EXISTS(PinnedSpanByte key, StoreType storeType, ref TContext context, ref TObjectContext objectContext) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext - { - var status = GarnetStatus.NOTFOUND; - RawStringInput input = default; - - if (storeType == StoreType.Main || storeType == StoreType.All) - { - var _output = new SpanByteAndMemory { SpanByte = scratchBufferBuilder.ViewRemainingArgSlice() }; - status = GET(key, ref input, ref _output, ref context); - - if (status == GarnetStatus.OK) - { - if (!_output.IsSpanByte) - _output.Memory.Dispose(); - return status; - } - } - - if ((storeType == StoreType.Object || storeType == StoreType.All) && !objectStoreBasicContext.IsNull) - { - status = GET(key, out _, ref objectContext); - } - - return status; - } - - /// - /// Set a timeout on key - /// - /// - /// - /// The key to set the timeout on. - /// Milliseconds value for the timeout. - /// True when the timeout was properly set. - /// The store to operate on. - /// >Flags to use for the operation. - /// Basic context for the main store. - /// Object context for the object store. - /// - public unsafe GarnetStatus EXPIRE(PinnedSpanByte key, PinnedSpanByte expiryMs, out bool timeoutSet, StoreType storeType, ExpireOption expireOption, ref TContext context, ref TObjectContext objectStoreContext) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext - => EXPIRE(key, TimeSpan.FromMilliseconds(NumUtils.ReadInt64(expiryMs.Length, expiryMs.ToPointer())), out timeoutSet, storeType, expireOption, ref context, ref objectStoreContext); - - /// - /// Set a timeout on key. - /// - /// - /// - /// The key to set the timeout on. - /// Input for the main store - /// True when the timeout was properly set. - /// The store to operate on. - /// Basic context for the main store - /// Object context for the object store - /// - public unsafe GarnetStatus EXPIRE(PinnedSpanByte key, ref RawStringInput input, out bool timeoutSet, StoreType storeType, ref TContext context, ref TObjectContext objectStoreContext) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext - { - Span rmwOutput = stackalloc byte[ObjectOutputHeader.Size]; - var output = SpanByteAndMemory.FromPinnedSpan(rmwOutput); - timeoutSet = false; - - var found = false; - - if (storeType == StoreType.Main || storeType == StoreType.All) - { - var status = context.RMW(key.ReadOnlySpan, ref input, ref output); - - if (status.IsPending) - CompletePendingForSession(ref status, ref output, ref context); - if (status.Found) found = true; - } - - if (!found && (storeType == StoreType.Object || storeType == StoreType.All) && - !objectStoreBasicContext.IsNull) - { - var header = new RespInputHeader(GarnetObjectType.Expire); - - // Re-encode expiration and expiration option as two integers instead of a long - var expirationWithOption = new ExpirationWithOption(input.arg1); - - var objInput = new ObjectInput(header, arg1: expirationWithOption.WordHead, arg2: expirationWithOption.WordTail); - - // Retry on object store - var objOutput = new GarnetObjectStoreOutput(output); - var status = objectStoreContext.RMW(key.ReadOnlySpan, ref objInput, ref objOutput); - - if (status.IsPending) - CompletePendingForObjectStoreSession(ref status, ref objOutput, ref objectStoreContext); - if (status.Found) found = true; - - output = objOutput.SpanByteAndMemory; - } - - Debug.Assert(output.IsSpanByte); - if (found) timeoutSet = ((ObjectOutputHeader*)output.SpanByte.ToPointer())->result1 == 1; - - return found ? GarnetStatus.OK : GarnetStatus.NOTFOUND; - } - - - - /// - /// Set a timeout on key using absolute Unix timestamp (seconds since January 1, 1970). - /// - /// - /// - /// The key to set the timeout on. - /// Absolute Unix timestamp - /// True when the timeout was properly set. - /// The store to operate on. - /// Flags to use for the operation. - /// Basic context for the main store - /// Object context for the object store - /// When true, is treated as milliseconds else seconds - /// Return GarnetStatus.OK when key found, else GarnetStatus.NOTFOUND - public unsafe GarnetStatus EXPIREAT(PinnedSpanByte key, long expiryTimestamp, out bool timeoutSet, StoreType storeType, ExpireOption expireOption, ref TContext context, ref TObjectContext objectStoreContext, bool milliseconds = false) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext - { - return EXPIRE(key, expiryTimestamp, out timeoutSet, storeType, expireOption, ref context, ref objectStoreContext, milliseconds ? RespCommand.PEXPIREAT : RespCommand.EXPIREAT); - } - - /// - /// Set a timeout on key. - /// - /// - /// - /// The key to set the timeout on. - /// The timespan value to set the expiration for. - /// True when the timeout was properly set. - /// The store to operate on. - /// Flags to use for the operation. - /// Basic context for the main store - /// Object context for the object store - /// When true the command executed is PEXPIRE, expire by default. - /// Return GarnetStatus.OK when key found, else GarnetStatus.NOTFOUND - public unsafe GarnetStatus EXPIRE(PinnedSpanByte key, TimeSpan expiry, out bool timeoutSet, StoreType storeType, ExpireOption expireOption, ref TContext context, ref TObjectContext objectStoreContext, bool milliseconds = false) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext - { - return EXPIRE(key, (long)(milliseconds ? expiry.TotalMilliseconds : expiry.TotalSeconds), out timeoutSet, storeType, expireOption, - ref context, ref objectStoreContext, milliseconds ? RespCommand.PEXPIRE : RespCommand.EXPIRE); - } - - /// - /// Set a timeout on key. - /// - /// - /// - /// The key to set the timeout on. - /// The timespan value to set the expiration for. - /// True when the timeout was properly set. - /// The store to operate on. - /// Flags to use for the operation. - /// Basic context for the main store - /// Object context for the object store - /// The current RESP command - /// - public unsafe GarnetStatus EXPIRE(PinnedSpanByte key, long expiration, out bool timeoutSet, StoreType storeType, ExpireOption expireOption, ref TContext context, ref TObjectContext objectStoreContext, RespCommand respCommand) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext - { - Span rmwOutput = stackalloc byte[ObjectOutputHeader.Size]; - var output = SpanByteAndMemory.FromPinnedSpan(rmwOutput); - timeoutSet = false; - var found = false; - - // Convert to expiration time in ticks - var expirationTimeInTicks = respCommand switch - { - RespCommand.EXPIRE => DateTimeOffset.UtcNow.AddSeconds(expiration).UtcTicks, - RespCommand.PEXPIRE => DateTimeOffset.UtcNow.AddMilliseconds(expiration).UtcTicks, - RespCommand.EXPIREAT => ConvertUtils.UnixTimestampInSecondsToTicks(expiration), - _ => ConvertUtils.UnixTimestampInMillisecondsToTicks(expiration) - }; - - var expirationWithOption = new ExpirationWithOption(expirationTimeInTicks, expireOption); - - if (storeType == StoreType.Main || storeType == StoreType.All) - { - var input = new RawStringInput(RespCommand.EXPIRE, arg1: expirationWithOption.Word); - var status = context.RMW(key.ReadOnlySpan, ref input, ref output); - - if (status.IsPending) - CompletePendingForSession(ref status, ref output, ref context); - if (status.Found) found = true; - } - - if (!found && (storeType == StoreType.Object || storeType == StoreType.All) && - !objectStoreBasicContext.IsNull) - { - var header = new RespInputHeader(GarnetObjectType.Expire); - var objInput = new ObjectInput(header, arg1: expirationWithOption.WordHead, arg2: expirationWithOption.WordTail); - - // Retry on object store - var objOutput = new GarnetObjectStoreOutput(output); - var keyBytes = key.ToArray(); - var status = objectStoreContext.RMW(key.ReadOnlySpan, ref objInput, ref objOutput); - - if (status.IsPending) - CompletePendingForObjectStoreSession(ref status, ref objOutput, ref objectStoreContext); - if (status.Found) found = true; - - output = objOutput.SpanByteAndMemory; - } - - Debug.Assert(output.IsSpanByte); - if (found) timeoutSet = ((ObjectOutputHeader*)output.SpanByte.ToPointer())->result1 == 1; - - return found ? GarnetStatus.OK : GarnetStatus.NOTFOUND; - } - - public unsafe GarnetStatus PERSIST(PinnedSpanByte key, StoreType storeType, ref TContext context, ref TObjectContext objectStoreContext) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext - { - GarnetStatus status = GarnetStatus.NOTFOUND; - - var inputHeader = new RawStringInput(RespCommand.PERSIST); - - var pbOutput = stackalloc byte[8]; - var o = SpanByteAndMemory.FromPinnedPointer(pbOutput, 8); - - if (storeType == StoreType.Main || storeType == StoreType.All) - { - var _status = context.RMW(key.ReadOnlySpan, ref inputHeader, ref o); - - if (_status.IsPending) - CompletePendingForSession(ref _status, ref o, ref context); - - Debug.Assert(o.IsSpanByte); - if (o.SpanByte.ReadOnlySpan[0] == 1) - status = GarnetStatus.OK; - } - - if (status == GarnetStatus.NOTFOUND && (storeType == StoreType.Object || storeType == StoreType.All) && !objectStoreBasicContext.IsNull) - { - // Retry on object store - var header = new RespInputHeader(GarnetObjectType.Persist); - var objInput = new ObjectInput(header); - - var objO = new GarnetObjectStoreOutput(o); - var _key = key.ToArray(); - var _status = objectStoreContext.RMW(key.ReadOnlySpan, ref objInput, ref objO); - - if (_status.IsPending) - CompletePendingForObjectStoreSession(ref _status, ref objO, ref objectStoreContext); - - Debug.Assert(o.IsSpanByte); - if (o.SpanByte.ReadOnlySpan.Slice(0, CmdStrings.RESP_RETURN_VAL_1.Length) - .SequenceEqual(CmdStrings.RESP_RETURN_VAL_1)) - status = GarnetStatus.OK; - } - - return status; - } - /// /// For existing keys - overwrites part of the value at a specified offset (in-place if possible) /// For non-existing keys - creates a new string with the value at a specified offset (padded with '\0's) @@ -1057,7 +727,7 @@ public unsafe GarnetStatus PERSIST(PinnedSpanByte key, /// Basic context for the main store /// public unsafe GarnetStatus SETRANGE(PinnedSpanByte key, ref RawStringInput input, ref PinnedSpanByte output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { SpanByteAndMemory sbmOut = new(output); @@ -1072,7 +742,7 @@ public unsafe GarnetStatus SETRANGE(PinnedSpanByte key, ref RawStringI } public GarnetStatus Increment(PinnedSpanByte key, ref RawStringInput input, ref PinnedSpanByte output, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { SpanByteAndMemory _output = new(output); @@ -1085,7 +755,7 @@ public GarnetStatus Increment(PinnedSpanByte key, ref RawStringInput i } public unsafe GarnetStatus Increment(PinnedSpanByte key, out long output, long increment, ref TContext context) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { var cmd = RespCommand.INCRBY; if (increment < 0) @@ -1111,76 +781,13 @@ public unsafe GarnetStatus Increment(PinnedSpanByte key, out long outp return GarnetStatus.OK; } - public void WATCH(PinnedSpanByte key, StoreType type) => txnManager.Watch(key, type); - - public unsafe GarnetStatus SCAN(long cursor, PinnedSpanByte match, long count, ref TContext context) => GarnetStatus.OK; - - public GarnetStatus GetKeyType(PinnedSpanByte key, out string keyType, ref TContext context, ref TObjectContext objectContext) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext + public void WATCH(PinnedSpanByte key, StoreType type) { - keyType = "string"; - // Check if key exists in Main store - var status = EXISTS(key, StoreType.Main, ref context, ref objectContext); - - // If key was not found in the main store then it is an object - if (status != GarnetStatus.OK && !objectStoreBasicContext.IsNull) - { - status = GET(key, out GarnetObjectStoreOutput output, ref objectContext); - if (status == GarnetStatus.OK) - { - if ((output.GarnetObject as SortedSetObject) != null) - { - keyType = "zset"; - } - else if ((output.GarnetObject as ListObject) != null) - { - keyType = "list"; - } - else if ((output.GarnetObject as SetObject) != null) - { - keyType = "set"; - } - else if ((output.GarnetObject as HashObject) != null) - { - keyType = "hash"; - } - } - else - { - keyType = "none"; - status = GarnetStatus.NOTFOUND; - } - } - return status; + txnManager.AddTransactionStoreType(type); + txnManager.Watch(key); } - public GarnetStatus MemoryUsageForKey(PinnedSpanByte key, out long memoryUsage, ref TContext context, ref TObjectContext objectContext, int samples = 0) - where TContext : ITsavoriteContext - where TObjectContext : ITsavoriteContext - { - memoryUsage = -1; - - // Check if key exists in Main store - var status = GET(key, out PinnedSpanByte keyValue, ref context); - - if (status == GarnetStatus.NOTFOUND) - { - status = GET(key, out GarnetObjectStoreOutput objectValue, ref objectContext); - if (status != GarnetStatus.NOTFOUND) - { - memoryUsage = RecordInfo.Size + (2 * IntPtr.Size) + // Log record length - Utility.RoundUp(key.Length, IntPtr.Size) + MemoryUtils.ByteArrayOverhead + // Key allocation in heap with overhead - objectValue.GarnetObject.HeapMemorySize; // Value allocation in heap - } - } - else - { - memoryUsage = RecordInfo.Size + Utility.RoundUp(key.TotalSize, RecordInfo.Size) + Utility.RoundUp(keyValue.TotalSize, RecordInfo.Size); - } - - return status; - } + public unsafe GarnetStatus SCAN(long cursor, PinnedSpanByte match, long count, ref TContext context) => GarnetStatus.OK; /// /// Computes the Longest Common Subsequence (LCS) of two keys. @@ -1198,8 +805,9 @@ public unsafe GarnetStatus LCS(PinnedSpanByte key1, PinnedSpanByte key2, ref Spa var createTransaction = false; if (txnManager.state != TxnState.Running) { - txnManager.SaveKeyEntryToLock(key1, false, LockType.Shared); - txnManager.SaveKeyEntryToLock(key2, false, LockType.Shared); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Main); + txnManager.SaveKeyEntryToLock(key1, LockType.Shared); + txnManager.SaveKeyEntryToLock(key2, LockType.Shared); txnManager.Run(true); createTransaction = true; } @@ -1218,7 +826,7 @@ public unsafe GarnetStatus LCS(PinnedSpanByte key1, PinnedSpanByte key2, ref Spa } private unsafe GarnetStatus LCSInternal(PinnedSpanByte key1, PinnedSpanByte key2, ref SpanByteAndMemory output, ref TContext context, bool lenOnly = false, bool withIndices = false, bool withMatchLen = false, int minMatchLen = 0) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { PinnedSpanByte val1, val2; var status1 = GET(key1, out val1, ref context); diff --git a/libs/server/Storage/Session/ObjectStore/AdvancedOps.cs b/libs/server/Storage/Session/ObjectStore/AdvancedOps.cs index db6a3a83362..12eb92e1d26 100644 --- a/libs/server/Storage/Session/ObjectStore/AdvancedOps.cs +++ b/libs/server/Storage/Session/ObjectStore/AdvancedOps.cs @@ -6,13 +6,13 @@ namespace Garnet.server { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; sealed partial class StorageSession : IDisposable { public GarnetStatus RMW_ObjectStore(ReadOnlySpan key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { var status = objectStoreContext.RMW(key, ref input, ref output); @@ -30,7 +30,7 @@ public GarnetStatus RMW_ObjectStore(ReadOnlySpan key, ref } public GarnetStatus Read_ObjectStore(ReadOnlySpan key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { var status = objectStoreContext.Read(key, ref input, ref output); diff --git a/libs/server/Storage/Session/ObjectStore/Common.cs b/libs/server/Storage/Session/ObjectStore/Common.cs index 98511f116c8..7fdcaedf59e 100644 --- a/libs/server/Storage/Session/ObjectStore/Common.cs +++ b/libs/server/Storage/Session/ObjectStore/Common.cs @@ -12,15 +12,15 @@ namespace Garnet.server { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; sealed partial class StorageSession : IDisposable { #region Common ObjectStore Methods - unsafe GarnetStatus RMWObjectStoreOperation(ReadOnlySpan key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + unsafe GarnetStatus RMWObjectStoreOperation(ReadOnlySpan key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext { if (objectStoreContext.Session is null) ThrowObjectStoreUninitializedException(); @@ -36,8 +36,8 @@ unsafe GarnetStatus RMWObjectStoreOperation(ReadOnlySpan k } unsafe GarnetStatus RMWObjectStoreOperation(ReadOnlySpan key, PinnedSpanByte input, - out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext { if (objectStoreContext.Session is null) ThrowObjectStoreUninitializedException(); @@ -58,7 +58,7 @@ unsafe GarnetStatus RMWObjectStoreOperation(ReadOnlySpan k /// /// GarnetStatus RMWObjectStoreOperationWithOutput(ReadOnlySpan key, ref ObjectInput input, ref TObjectContext objectStoreContext, ref GarnetObjectStoreOutput output) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { if (objectStoreContext.Session is null) ThrowObjectStoreUninitializedException(); @@ -80,7 +80,7 @@ GarnetStatus RMWObjectStoreOperationWithOutput(ReadOnlySpan /// GarnetStatus ReadObjectStoreOperationWithOutput(ReadOnlySpan key, ref ObjectInput input, ref TObjectContext objectStoreContext, ref GarnetObjectStoreOutput output) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { if (objectStoreContext.Session is null) ThrowObjectStoreUninitializedException(); @@ -103,7 +103,7 @@ GarnetStatus ReadObjectStoreOperationWithOutput(ReadOnlySpan unsafe GarnetStatus ReadObjectStoreOperationWithOutput(ReadOnlySpan key, PinnedSpanByte input, ref TObjectContext objectStoreContext, ref GarnetObjectStoreOutput output) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { if (objectStoreContext.Session is null) ThrowObjectStoreUninitializedException(); @@ -124,7 +124,7 @@ unsafe GarnetStatus ReadObjectStoreOperationWithOutput(ReadOnlyS /// The list of items for the response /// public unsafe GarnetStatus ObjectScan(GarnetObjectType objectType, PinnedSpanByte key, long cursor, string match, int count, out PinnedSpanByte[] items, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { Debug.Assert(objectType is GarnetObjectType.Hash or GarnetObjectType.Set or GarnetObjectType.SortedSet); @@ -714,8 +714,8 @@ unsafe bool TryProcessRespSimple64IntOutput(GarnetObjectStoreOutput output, out /// /// /// - unsafe GarnetStatus ReadObjectStoreOperation(ReadOnlySpan key, PinnedSpanByte input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + unsafe GarnetStatus ReadObjectStoreOperation(ReadOnlySpan key, PinnedSpanByte input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext { if (objectStoreContext.Session is null) ThrowObjectStoreUninitializedException(); @@ -750,8 +750,8 @@ unsafe GarnetStatus ReadObjectStoreOperation(ReadOnlySpan /// /// /// - unsafe GarnetStatus ReadObjectStoreOperation(ReadOnlySpan key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + unsafe GarnetStatus ReadObjectStoreOperation(ReadOnlySpan key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext { if (objectStoreContext.Session is null) ThrowObjectStoreUninitializedException(); @@ -775,6 +775,20 @@ unsafe GarnetStatus ReadObjectStoreOperation(ReadOnlySpan return GarnetStatus.NOTFOUND; } + /// + /// Deletes a key from the object store context. + /// + /// The name of the key to use in the operation + /// Basic context for the object store. + /// + public GarnetStatus DELETE_ObjectStore(PinnedSpanByte key, ref TObjectContext objectContext) + where TObjectContext : ITsavoriteContext + { + var status = objectContext.Delete(key.ReadOnlySpan); + Debug.Assert(!status.IsPending); + return status.Found ? GarnetStatus.OK : GarnetStatus.NOTFOUND; + } + /// /// Iterates members of a collection object using a cursor, /// a match pattern and count parameters @@ -784,7 +798,7 @@ unsafe GarnetStatus ReadObjectStoreOperation(ReadOnlySpan /// /// public GarnetStatus ObjectScan(ReadOnlySpan key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key, ref input, ref objectStoreContext, ref output); [MethodImpl(MethodImplOptions.NoInlining)] @@ -802,7 +816,7 @@ static void ThrowObjectStoreUninitializedException() /// /// private GarnetStatus CompletePendingAndGetGarnetStatus(Status status, ref TObjectContext objectStoreContext, ref GarnetObjectStoreOutput output) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { if (status.IsPending) CompletePendingForObjectStoreSession(ref status, ref output, ref objectStoreContext); @@ -827,7 +841,7 @@ private GarnetStatus CompletePendingAndGetGarnetStatus(Status st /// The context of the object store. /// The status of the operation. private GarnetStatus ObjectCollect(PinnedSpanByte searchKey, ReadOnlySpan typeObject, SingleWriterMultiReaderLock collectLock, ref ObjectInput input, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { if (!collectLock.TryWriteLock()) return GarnetStatus.NOTFOUND; diff --git a/libs/server/Storage/Session/ObjectStore/CompletePending.cs b/libs/server/Storage/Session/ObjectStore/CompletePending.cs index 932f5166e6a..ec88b96052e 100644 --- a/libs/server/Storage/Session/ObjectStore/CompletePending.cs +++ b/libs/server/Storage/Session/ObjectStore/CompletePending.cs @@ -6,8 +6,8 @@ namespace Garnet.server { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; sealed partial class StorageSession { @@ -18,7 +18,7 @@ sealed partial class StorageSession /// /// static void CompletePendingForObjectStoreSession(ref Status status, ref GarnetObjectStoreOutput output, ref TContext objectContext) - where TContext : ITsavoriteContext + where TContext : ITsavoriteContext { objectContext.CompletePendingWithOutputs(out var completedOutputs, wait: true); var more = completedOutputs.Next(); diff --git a/libs/server/Storage/Session/ObjectStore/HashOps.cs b/libs/server/Storage/Session/ObjectStore/HashOps.cs index 1d8951e5379..5fba189b75d 100644 --- a/libs/server/Storage/Session/ObjectStore/HashOps.cs +++ b/libs/server/Storage/Session/ObjectStore/HashOps.cs @@ -7,8 +7,8 @@ namespace Garnet.server { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Server API methods - HASH @@ -33,7 +33,7 @@ sealed partial class StorageSession : IDisposable /// /// public unsafe GarnetStatus HashSet(PinnedSpanByte key, PinnedSpanByte field, PinnedSpanByte value, out int itemsDoneCount, ref TObjectContext objectStoreContext, bool nx = false) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { itemsDoneCount = 0; @@ -65,7 +65,7 @@ public unsafe GarnetStatus HashSet(PinnedSpanByte key, PinnedSpa /// /// public unsafe GarnetStatus HashSet(PinnedSpanByte key, (PinnedSpanByte field, PinnedSpanByte value)[] elements, out int itemsDoneCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { itemsDoneCount = 0; @@ -101,7 +101,7 @@ public unsafe GarnetStatus HashSet(PinnedSpanByte key, (PinnedSp /// /// public GarnetStatus HashDelete(PinnedSpanByte key, PinnedSpanByte field, out int itemsDoneCount, ref TObjectContext objectStoreContext, bool nx = false) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => HashDelete(key, [field], out itemsDoneCount, ref objectStoreContext); /// @@ -114,7 +114,7 @@ public GarnetStatus HashDelete(PinnedSpanByte key, PinnedSpanByt /// /// public unsafe GarnetStatus HashDelete(PinnedSpanByte key, PinnedSpanByte[] fields, out int itemsDoneCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { itemsDoneCount = 0; @@ -144,7 +144,7 @@ public unsafe GarnetStatus HashDelete(PinnedSpanByte key, Pinned /// /// public unsafe GarnetStatus HashGet(PinnedSpanByte key, PinnedSpanByte field, out PinnedSpanByte value, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { value = default; @@ -179,7 +179,7 @@ public unsafe GarnetStatus HashGet(PinnedSpanByte key, PinnedSpa /// /// public unsafe GarnetStatus HashGetMultiple(PinnedSpanByte key, PinnedSpanByte[] fields, out PinnedSpanByte[] values, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { values = default; @@ -213,7 +213,7 @@ public unsafe GarnetStatus HashGetMultiple(PinnedSpanByte key, P /// /// public unsafe GarnetStatus HashGetAll(PinnedSpanByte key, out PinnedSpanByte[] values, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { values = default; @@ -245,7 +245,7 @@ public unsafe GarnetStatus HashGetAll(PinnedSpanByte key, out Pi /// /// public unsafe GarnetStatus HashLength(PinnedSpanByte key, out int items, ref TObjectContext objectStoreContext, bool nx = false) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { items = 0; @@ -273,7 +273,7 @@ public unsafe GarnetStatus HashLength(PinnedSpanByte key, out in /// /// public unsafe GarnetStatus HashExists(PinnedSpanByte key, PinnedSpanByte field, out bool exists, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { exists = false; if (key.Length == 0) @@ -302,7 +302,7 @@ public unsafe GarnetStatus HashExists(PinnedSpanByte key, Pinned /// /// public unsafe GarnetStatus HashRandomField(PinnedSpanByte key, out PinnedSpanByte field, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { field = default; @@ -340,7 +340,7 @@ public unsafe GarnetStatus HashRandomField(PinnedSpanByte key, o /// /// public unsafe GarnetStatus HashRandomField(PinnedSpanByte key, int count, bool withValues, out PinnedSpanByte[] fields, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { fields = default; @@ -376,8 +376,8 @@ public unsafe GarnetStatus HashRandomField(PinnedSpanByte key, i /// /// /// - public GarnetStatus HashSet(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus HashSet(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); /// @@ -393,7 +393,7 @@ public GarnetStatus HashSet(PinnedSpanByte key, ref ObjectInput /// /// public GarnetStatus HashGet(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -406,7 +406,7 @@ public GarnetStatus HashGet(PinnedSpanByte key, ref ObjectInput /// /// public GarnetStatus HashGetAll(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -419,7 +419,7 @@ public GarnetStatus HashGetAll(PinnedSpanByte key, ref ObjectInp /// /// public GarnetStatus HashGetMultiple(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -432,7 +432,7 @@ public GarnetStatus HashGetMultiple(PinnedSpanByte key, ref Obje /// /// public GarnetStatus HashRandomField(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -444,8 +444,8 @@ public GarnetStatus HashRandomField(PinnedSpanByte key, ref Obje /// /// /// - public GarnetStatus HashLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus HashLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); /// @@ -457,8 +457,8 @@ public GarnetStatus HashLength(PinnedSpanByte key, ref ObjectInp /// /// /// - public GarnetStatus HashStrLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus HashStrLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); /// @@ -470,8 +470,8 @@ public GarnetStatus HashStrLength(PinnedSpanByte key, ref Object /// /// /// - public GarnetStatus HashDelete(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus HashDelete(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); /// @@ -483,8 +483,8 @@ public GarnetStatus HashDelete(PinnedSpanByte key, ref ObjectInp /// /// /// - public GarnetStatus HashExists(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus HashExists(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); /// @@ -497,7 +497,7 @@ public GarnetStatus HashExists(PinnedSpanByte key, ref ObjectInp /// /// public GarnetStatus HashKeys(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -510,7 +510,7 @@ public GarnetStatus HashKeys(PinnedSpanByte key, ref ObjectInput /// /// public GarnetStatus HashVals(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -522,8 +522,8 @@ public GarnetStatus HashVals(PinnedSpanByte key, ref ObjectInput /// /// /// - public GarnetStatus HashIncrement(PinnedSpanByte key, PinnedSpanByte input, out ObjectOutputHeader output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus HashIncrement(PinnedSpanByte key, PinnedSpanByte input, out OutputHeader output, ref TObjectContext objectContext) + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperation(key.ReadOnlySpan, input, out output, ref objectContext); /// @@ -537,7 +537,7 @@ public GarnetStatus HashIncrement(PinnedSpanByte key, PinnedSpan /// /// public GarnetStatus HashIncrement(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -550,7 +550,7 @@ public GarnetStatus HashIncrement(PinnedSpanByte key, ref Object /// The object context for the operation. /// The status of the operation. public GarnetStatus HashExpire(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -565,7 +565,7 @@ public GarnetStatus HashExpire(PinnedSpanByte key, ref ObjectInp /// The object context for the operation. /// The status of the operation. public GarnetStatus HashTimeToLive(PinnedSpanByte key, bool isMilliseconds, bool isTimestamp, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { var innerInput = new ObjectInput(input.header, ref input.parseState, arg1: isMilliseconds ? 1 : 0, arg2: isTimestamp ? 1 : 0); @@ -582,7 +582,7 @@ public GarnetStatus HashTimeToLive(PinnedSpanByte key, bool isMi /// The object context for the operation. /// The status of the operation. public GarnetStatus HashPersist(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -598,7 +598,7 @@ public GarnetStatus HashPersist(PinnedSpanByte key, ref ObjectIn /// Otherwise, the operation is performed on the specified keys. /// public unsafe GarnetStatus HashCollect(ReadOnlySpan keys, ref ObjectInput input, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { if (keys[0].ReadOnlySpan.SequenceEqual("*"u8)) return ObjectCollect(keys[0], CmdStrings.HASH, _hcollectTaskLock, ref input, ref objectContext); diff --git a/libs/server/Storage/Session/ObjectStore/ListOps.cs b/libs/server/Storage/Session/ObjectStore/ListOps.cs index c07d6a7651c..865c2122a99 100644 --- a/libs/server/Storage/Session/ObjectStore/ListOps.cs +++ b/libs/server/Storage/Session/ObjectStore/ListOps.cs @@ -7,8 +7,8 @@ namespace Garnet.server { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; sealed partial class StorageSession : IDisposable { @@ -26,7 +26,7 @@ sealed partial class StorageSession : IDisposable /// /// public unsafe GarnetStatus ListPush(PinnedSpanByte key, PinnedSpanByte[] elements, ListOperation lop, out int itemsDoneCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { itemsDoneCount = 0; @@ -61,7 +61,7 @@ public unsafe GarnetStatus ListPush(PinnedSpanByte key, PinnedSp /// /// public unsafe GarnetStatus ListPush(PinnedSpanByte key, PinnedSpanByte element, ListOperation lop, out int itemsDoneCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { itemsDoneCount = 0; @@ -90,7 +90,7 @@ public unsafe GarnetStatus ListPush(PinnedSpanByte key, PinnedSp /// /// The popped element public GarnetStatus ListPop(PinnedSpanByte key, ListOperation lop, ref TObjectContext objectStoreContext, out PinnedSpanByte element) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { var status = ListPop(key, 1, lop, ref objectStoreContext, out var elements); element = status == GarnetStatus.OK ? elements.FirstOrDefault() : default; @@ -109,7 +109,7 @@ public GarnetStatus ListPop(PinnedSpanByte key, ListOperation lo /// /// The count elements popped from the list public unsafe GarnetStatus ListPop(PinnedSpanByte key, int count, ListOperation lop, ref TObjectContext objectStoreContext, out PinnedSpanByte[] elements) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { // Prepare the input var header = new RespInputHeader(GarnetObjectType.List) { ListOp = lop }; @@ -140,7 +140,7 @@ public unsafe GarnetStatus ListPop(PinnedSpanByte key, int count /// /// The count elements popped from the list public unsafe GarnetStatus ListPopMultiple(PinnedSpanByte[] keys, OperationDirection direction, int count, ref TObjectContext objectContext, out PinnedSpanByte key, out PinnedSpanByte[] elements) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { foreach (var k in keys) { @@ -175,7 +175,7 @@ public unsafe GarnetStatus ListPopMultiple(PinnedSpanByte[] keys /// /// public unsafe GarnetStatus ListLength(PinnedSpanByte key, ref TObjectContext objectStoreContext, out int count) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { count = 0; @@ -218,12 +218,14 @@ public GarnetStatus ListMove(PinnedSpanByte sourceKey, PinnedSpanByte destinatio if (txnManager.state != TxnState.Running) { createTransaction = true; - txnManager.SaveKeyEntryToLock(sourceKey, true, LockType.Exclusive); - txnManager.SaveKeyEntryToLock(destinationKey, true, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object | TransactionStoreTypes.Unified); + txnManager.SaveKeyEntryToLock(sourceKey, LockType.Exclusive); + txnManager.SaveKeyEntryToLock(destinationKey, LockType.Exclusive); _ = txnManager.Run(true); } - var objectStoreTransactionalContext = txnManager.ObjectStoreTransactionalContext; + var objectContext = txnManager.ObjectStoreTransactionalContext; + var unifiedContext = txnManager.UnifiedStoreTransactionalContext; try { @@ -246,7 +248,7 @@ public GarnetStatus ListMove(PinnedSpanByte sourceKey, PinnedSpanByte destinatio if (!sameKey) { // Read destination key - statusOp = GET(destinationKey, out var destinationList, ref objectStoreTransactionalContext); + statusOp = GET(destinationKey, out var destinationList, ref objectContext); if (statusOp == GarnetStatus.NOTFOUND) { @@ -287,8 +289,7 @@ public GarnetStatus ListMove(PinnedSpanByte sourceKey, PinnedSpanByte destinatio { if (srcListObject.LnkList.Count == 0) { - _ = EXPIRE(sourceKey, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, - ref transactionalContext, ref objectTransactionalContext); + _ = EXPIRE(sourceKey, TimeSpan.Zero, out _, ExpireOption.None, ref unifiedContext); } // Left push (addfirst) to destination @@ -301,7 +302,7 @@ public GarnetStatus ListMove(PinnedSpanByte sourceKey, PinnedSpanByte destinatio newListValue = new ListObject(dstListObject.LnkList, dstListObject.sizes); // Upsert - _ = SET(destinationKey, newListValue, ref objectStoreTransactionalContext); + _ = SET(destinationKey, newListValue, ref objectContext); } else { @@ -335,7 +336,7 @@ public GarnetStatus ListMove(PinnedSpanByte sourceKey, PinnedSpanByte destinatio /// /// true when successful public unsafe bool ListTrim(PinnedSpanByte key, int start, int stop, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { // Prepare the input var header = new RespInputHeader(GarnetObjectType.List) { ListOp = ListOperation.LTRIM }; @@ -355,8 +356,8 @@ public unsafe bool ListTrim(PinnedSpanByte key, int start, int s /// /// /// - public GarnetStatus ListPush(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus ListPush(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext { var status = RMWObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); itemBroker.HandleCollectionUpdate(key.ToArray()); @@ -374,7 +375,7 @@ public GarnetStatus ListPush(PinnedSpanByte key, ref ObjectInput /// /// public GarnetStatus ListPosition(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { return ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); } @@ -388,7 +389,7 @@ public GarnetStatus ListPosition(PinnedSpanByte key, ref ObjectI /// /// public GarnetStatus ListTrim(PinnedSpanByte key, ref ObjectInput input, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperation(key.ReadOnlySpan, ref input, out _, ref objectStoreContext); /// @@ -401,7 +402,7 @@ public GarnetStatus ListTrim(PinnedSpanByte key, ref ObjectInput /// /// public GarnetStatus ListRange(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -413,8 +414,8 @@ public GarnetStatus ListRange(PinnedSpanByte key, ref ObjectInpu /// /// /// - public GarnetStatus ListInsert(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus ListInsert(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext { var status = RMWObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); itemBroker.HandleCollectionUpdate(key.ToArray()); @@ -431,7 +432,7 @@ public GarnetStatus ListInsert(PinnedSpanByte key, ref ObjectInp /// /// public GarnetStatus ListIndex(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -444,8 +445,8 @@ public GarnetStatus ListIndex(PinnedSpanByte key, ref ObjectInpu /// /// /// - public GarnetStatus ListRemove(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus ListRemove(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); /// @@ -459,7 +460,7 @@ public GarnetStatus ListRemove(PinnedSpanByte key, ref ObjectInp /// /// public unsafe GarnetStatus ListPop(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -472,8 +473,8 @@ public unsafe GarnetStatus ListPop(PinnedSpanByte key, ref Objec /// /// /// - public unsafe GarnetStatus ListLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public unsafe GarnetStatus ListLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); /// @@ -486,7 +487,7 @@ public unsafe GarnetStatus ListLength(PinnedSpanByte key, ref Ob /// /// public unsafe GarnetStatus ListSet(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); } } \ No newline at end of file diff --git a/libs/server/Storage/Session/ObjectStore/SetOps.cs b/libs/server/Storage/Session/ObjectStore/SetOps.cs index 35dc29b03f8..7e1f52e0225 100644 --- a/libs/server/Storage/Session/ObjectStore/SetOps.cs +++ b/libs/server/Storage/Session/ObjectStore/SetOps.cs @@ -8,8 +8,8 @@ namespace Garnet.server { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Server session for RESP protocol - SET @@ -28,7 +28,7 @@ sealed partial class StorageSession : IDisposable /// /// internal unsafe GarnetStatus SetAdd(PinnedSpanByte key, PinnedSpanByte member, out int saddCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { saddCount = 0; @@ -57,7 +57,7 @@ internal unsafe GarnetStatus SetAdd(PinnedSpanByte key, PinnedSp /// /// internal unsafe GarnetStatus SetAdd(PinnedSpanByte key, PinnedSpanByte[] members, out int saddCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { saddCount = 0; @@ -91,7 +91,7 @@ internal unsafe GarnetStatus SetAdd(PinnedSpanByte key, PinnedSp /// /// internal unsafe GarnetStatus SetRemove(PinnedSpanByte key, PinnedSpanByte member, out int sremCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { sremCount = 0; @@ -121,7 +121,7 @@ internal unsafe GarnetStatus SetRemove(PinnedSpanByte key, Pinne /// /// internal unsafe GarnetStatus SetRemove(PinnedSpanByte key, PinnedSpanByte[] members, out int sremCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { sremCount = 0; @@ -150,7 +150,7 @@ internal unsafe GarnetStatus SetRemove(PinnedSpanByte key, Pinne /// /// internal unsafe GarnetStatus SetLength(PinnedSpanByte key, out int count, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { count = 0; @@ -176,7 +176,7 @@ internal unsafe GarnetStatus SetLength(PinnedSpanByte key, out i /// /// internal unsafe GarnetStatus SetMembers(PinnedSpanByte key, out PinnedSpanByte[] members, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { members = default; @@ -206,7 +206,7 @@ internal unsafe GarnetStatus SetMembers(PinnedSpanByte key, out /// /// internal GarnetStatus SetPop(PinnedSpanByte key, out PinnedSpanByte element, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { var status = SetPop(key, int.MinValue, out var elements, ref objectStoreContext); element = default; @@ -226,7 +226,7 @@ internal GarnetStatus SetPop(PinnedSpanByte key, out PinnedSpanB /// /// internal unsafe GarnetStatus SetPop(PinnedSpanByte key, int count, out PinnedSpanByte[] elements, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { elements = default; @@ -267,12 +267,14 @@ internal unsafe GarnetStatus SetMove(PinnedSpanByte sourceKey, PinnedSpanByte de if (txnManager.state != TxnState.Running) { createTransaction = true; - txnManager.SaveKeyEntryToLock(sourceKey, true, LockType.Exclusive); - txnManager.SaveKeyEntryToLock(destinationKey, true, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object | TransactionStoreTypes.Unified); + txnManager.SaveKeyEntryToLock(sourceKey, LockType.Exclusive); + txnManager.SaveKeyEntryToLock(destinationKey, LockType.Exclusive); _ = txnManager.Run(true); } var objectTransactionalContext = txnManager.ObjectStoreTransactionalContext; + var unifiedTransactionalContext = txnManager.UnifiedStoreTransactionalContext; try { @@ -313,8 +315,7 @@ internal unsafe GarnetStatus SetMove(PinnedSpanByte sourceKey, PinnedSpanByte de if (srcSetObject.Set.Count == 0) { - _ = EXPIRE(sourceKey, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, - ref transactionalContext, ref objectTransactionalContext); + _ = EXPIRE(sourceKey, TimeSpan.Zero, out _, ExpireOption.None, ref unifiedTransactionalContext); } _ = dstSetObject.Set.Add(arrMember); @@ -361,8 +362,9 @@ public GarnetStatus SetIntersect(PinnedSpanByte[] keys, out HashSet outp { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); _ = txnManager.Run(true); } @@ -403,14 +405,16 @@ public GarnetStatus SetIntersectStore(PinnedSpanByte key, PinnedSpanByte[] keys, { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; - txnManager.SaveKeyEntryToLock(key, true, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object | TransactionStoreTypes.Unified); + txnManager.SaveKeyEntryToLock(key, LockType.Exclusive); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); _ = txnManager.Run(true); } // SetObject var setObjectStoreTransactionalContext = txnManager.ObjectStoreTransactionalContext; + var setUnifiedStoreTransactionalContext = txnManager.UnifiedStoreTransactionalContext; try { @@ -431,8 +435,7 @@ public GarnetStatus SetIntersectStore(PinnedSpanByte key, PinnedSpanByte[] keys, } else { - _ = EXPIRE(key, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, - ref transactionalContext, ref setObjectStoreTransactionalContext); + _ = EXPIRE(key, TimeSpan.Zero, out _, ExpireOption.None, ref setUnifiedStoreTransactionalContext); } count = members.Count; @@ -449,7 +452,7 @@ public GarnetStatus SetIntersectStore(PinnedSpanByte key, PinnedSpanByte[] keys, private GarnetStatus SetIntersect(ReadOnlySpan keys, ref TObjectContext objectContext, out HashSet output) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { output = new HashSet(ByteArrayComparer.Instance); @@ -532,8 +535,9 @@ public GarnetStatus SetUnion(PinnedSpanByte[] keys, out HashSet output) { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); _ = txnManager.Run(true); } @@ -572,14 +576,16 @@ public GarnetStatus SetUnionStore(PinnedSpanByte key, PinnedSpanByte[] keys, out { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; - txnManager.SaveKeyEntryToLock(key, true, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object | TransactionStoreTypes.Unified); + txnManager.SaveKeyEntryToLock(key, LockType.Exclusive); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); _ = txnManager.Run(true); } // SetObject var setObjectStoreTransactionalContext = txnManager.ObjectStoreTransactionalContext; + var setUnifiedStoreTransactionalContext = txnManager.UnifiedStoreTransactionalContext; try { @@ -600,8 +606,7 @@ public GarnetStatus SetUnionStore(PinnedSpanByte key, PinnedSpanByte[] keys, out } else { - _ = EXPIRE(key, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, - ref transactionalContext, ref setObjectStoreTransactionalContext); + _ = EXPIRE(key, TimeSpan.Zero, out _, ExpireOption.None, ref setUnifiedStoreTransactionalContext); } count = members.Count; @@ -617,7 +622,7 @@ public GarnetStatus SetUnionStore(PinnedSpanByte key, PinnedSpanByte[] keys, out } private GarnetStatus SetUnion(PinnedSpanByte[] keys, ref TObjectContext objectContext, out HashSet output) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { output = new HashSet(ByteArrayComparer.Instance); if (keys.Length == 0) @@ -651,8 +656,8 @@ private GarnetStatus SetUnion(PinnedSpanByte[] keys, ref TObject /// /// /// - public GarnetStatus SetAdd(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus SetAdd(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectContext) + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectContext); /// @@ -666,8 +671,8 @@ public GarnetStatus SetAdd(PinnedSpanByte key, ref ObjectInput i /// /// /// - public GarnetStatus SetRemove(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus SetRemove(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectContext) + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectContext); /// @@ -679,8 +684,8 @@ public GarnetStatus SetRemove(PinnedSpanByte key, ref ObjectInpu /// /// /// - public GarnetStatus SetLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus SetLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectContext) + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectContext); /// @@ -693,7 +698,7 @@ public GarnetStatus SetLength(PinnedSpanByte key, ref ObjectInpu /// /// public GarnetStatus SetMembers(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -706,7 +711,7 @@ public GarnetStatus SetMembers(PinnedSpanByte key, ref ObjectInp /// /// public GarnetStatus SetIsMember(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -718,7 +723,7 @@ public GarnetStatus SetIsMember(PinnedSpanByte key, ref ObjectIn /// /// public unsafe GarnetStatus SetIsMember(PinnedSpanByte key, PinnedSpanByte[] members, out int[] result, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { result = default; @@ -753,7 +758,7 @@ public unsafe GarnetStatus SetIsMember(PinnedSpanByte key, Pinne /// /// public GarnetStatus SetPop(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -770,7 +775,7 @@ public GarnetStatus SetPop(PinnedSpanByte key, ref ObjectInput i /// /// public GarnetStatus SetRandomMember(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -792,8 +797,9 @@ public GarnetStatus SetDiff(PinnedSpanByte[] keys, out HashSet members) { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); _ = txnManager.Run(true); } @@ -832,14 +838,16 @@ public GarnetStatus SetDiffStore(PinnedSpanByte key, PinnedSpanByte[] keys, out { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; - txnManager.SaveKeyEntryToLock(key, true, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object | TransactionStoreTypes.Unified); + txnManager.SaveKeyEntryToLock(key, LockType.Exclusive); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); _ = txnManager.Run(true); } // SetObject var setObjectStoreTransactionalContext = txnManager.ObjectStoreTransactionalContext; + var setUnifiedStoreTransactionalContext = txnManager.UnifiedStoreTransactionalContext; try { @@ -859,8 +867,7 @@ public GarnetStatus SetDiffStore(PinnedSpanByte key, PinnedSpanByte[] keys, out } else { - _ = EXPIRE(key, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, - ref transactionalContext, ref setObjectStoreTransactionalContext); + _ = EXPIRE(key, TimeSpan.Zero, out _, ExpireOption.None, ref setUnifiedStoreTransactionalContext); } count = diffSet.Count; @@ -876,7 +883,7 @@ public GarnetStatus SetDiffStore(PinnedSpanByte key, PinnedSpanByte[] keys, out } private GarnetStatus SetDiff(PinnedSpanByte[] keys, ref TObjectContext objectContext, out HashSet output) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { output = new HashSet(); if (keys.Length == 0) @@ -950,8 +957,9 @@ public GarnetStatus SetIntersectLength(ReadOnlySpan keys, int? l { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); _ = txnManager.Run(true); } diff --git a/libs/server/Storage/Session/ObjectStore/SortedSetGeoOps.cs b/libs/server/Storage/Session/ObjectStore/SortedSetGeoOps.cs index c7241c3ca38..f38395a0bb9 100644 --- a/libs/server/Storage/Session/ObjectStore/SortedSetGeoOps.cs +++ b/libs/server/Storage/Session/ObjectStore/SortedSetGeoOps.cs @@ -8,8 +8,8 @@ namespace Garnet.server { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; sealed partial class StorageSession : IDisposable { @@ -24,7 +24,7 @@ sealed partial class StorageSession : IDisposable /// /// public GarnetStatus GeoAdd(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -39,7 +39,7 @@ public GarnetStatus GeoAdd(PinnedSpanByte key, ref ObjectInput i /// /// public GarnetStatus GeoCommands(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -61,7 +61,7 @@ public GarnetStatus GeoSearchReadOnly(PinnedSpanByte key, ref Ge ref ObjectInput input, ref SpanByteAndMemory output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { var createTransaction = false; @@ -69,7 +69,8 @@ public GarnetStatus GeoSearchReadOnly(PinnedSpanByte key, ref Ge { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; - txnManager.SaveKeyEntryToLock(key, true, LockType.Shared); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object); + txnManager.SaveKeyEntryToLock(key, LockType.Shared); txnManager.Run(true); } @@ -118,7 +119,7 @@ public unsafe GarnetStatus GeoSearchStore(PinnedSpanByte key, Pi ref ObjectInput input, ref SpanByteAndMemory output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { var createTransaction = false; @@ -126,11 +127,13 @@ public unsafe GarnetStatus GeoSearchStore(PinnedSpanByte key, Pi { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; - txnManager.SaveKeyEntryToLock(destination, true, LockType.Exclusive); - txnManager.SaveKeyEntryToLock(key, true, LockType.Shared); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object | TransactionStoreTypes.Unified); + txnManager.SaveKeyEntryToLock(destination, LockType.Exclusive); + txnManager.SaveKeyEntryToLock(key, LockType.Shared); _ = txnManager.Run(true); } - var objectStoreTransactionalContext = txnManager.ObjectStoreTransactionalContext; + var geoObjectStoreTransactionalContext = txnManager.ObjectStoreTransactionalContext; + var geoUnifiedStoreTransactionalContext = txnManager.UnifiedStoreTransactionalContext; using var writer = new RespMemoryWriter(functionsState.respProtocolVersion, ref output); @@ -138,7 +141,7 @@ public unsafe GarnetStatus GeoSearchStore(PinnedSpanByte key, Pi { SpanByteAndMemory searchOutMem = default; - var status = GET(key, out var firstObj, ref objectStoreTransactionalContext); + var status = GET(key, out var firstObj, ref geoObjectStoreTransactionalContext); if (status == GarnetStatus.OK) { if (firstObj.GarnetObject is SortedSetObject firstSortedSet) @@ -155,7 +158,7 @@ public unsafe GarnetStatus GeoSearchStore(PinnedSpanByte key, Pi if (status == GarnetStatus.NOTFOUND) { // Expire/Delete the destination key if the source key is not found - _ = EXPIRE(destination, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, ref transactionalContext, ref objectStoreTransactionalContext); + _ = EXPIRE(destination, TimeSpan.Zero, out _, ExpireOption.None, ref geoUnifiedStoreTransactionalContext); writer.WriteInt32(0); return GarnetStatus.OK; } @@ -175,7 +178,7 @@ public unsafe GarnetStatus GeoSearchStore(PinnedSpanByte key, Pi return GarnetStatus.OK; } - _ = objectStoreTransactionalContext.Delete(destination.ReadOnlySpan); + _ = geoObjectStoreTransactionalContext.Delete(destination.ReadOnlySpan); _ = RespReadUtils.TryReadUnsignedArrayLength(out var foundItems, ref currOutPtr, endOutPtr); @@ -201,7 +204,7 @@ public unsafe GarnetStatus GeoSearchStore(PinnedSpanByte key, Pi }, ref parseState); var zAddOutput = new GarnetObjectStoreOutput(); - RMWObjectStoreOperationWithOutput(destination, ref zAddInput, ref objectStoreTransactionalContext, ref zAddOutput); + RMWObjectStoreOperationWithOutput(destination, ref zAddInput, ref geoObjectStoreTransactionalContext, ref zAddOutput); writer.WriteInt32(foundItems); } diff --git a/libs/server/Storage/Session/ObjectStore/SortedSetOps.cs b/libs/server/Storage/Session/ObjectStore/SortedSetOps.cs index c301eab0928..6262aab7bbf 100644 --- a/libs/server/Storage/Session/ObjectStore/SortedSetOps.cs +++ b/libs/server/Storage/Session/ObjectStore/SortedSetOps.cs @@ -12,8 +12,8 @@ namespace Garnet.server { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; sealed partial class StorageSession : IDisposable { @@ -30,7 +30,7 @@ sealed partial class StorageSession : IDisposable /// /// public unsafe GarnetStatus SortedSetAdd(PinnedSpanByte key, PinnedSpanByte score, PinnedSpanByte member, out int zaddCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { zaddCount = 0; if (key.Length == 0) @@ -65,7 +65,7 @@ public unsafe GarnetStatus SortedSetAdd(PinnedSpanByte key, Pinn /// /// public unsafe GarnetStatus SortedSetAdd(PinnedSpanByte key, (PinnedSpanByte score, PinnedSpanByte member)[] inputs, out int zaddCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { zaddCount = 0; @@ -108,7 +108,7 @@ public unsafe GarnetStatus SortedSetAdd(PinnedSpanByte key, (Pin /// public unsafe GarnetStatus SortedSetRemove(PinnedSpanByte key, PinnedSpanByte member, out int zremCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { zremCount = 0; @@ -139,7 +139,7 @@ public unsafe GarnetStatus SortedSetRemove(PinnedSpanByte key, P /// /// public unsafe GarnetStatus SortedSetRemove(PinnedSpanByte key, PinnedSpanByte[] members, out int zremCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { zremCount = 0; @@ -170,7 +170,7 @@ public unsafe GarnetStatus SortedSetRemove(PinnedSpanByte key, P /// public unsafe GarnetStatus SortedSetRemoveRangeByLex(PinnedSpanByte key, string min, string max, out int countRemoved, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { countRemoved = 0; @@ -217,7 +217,7 @@ public unsafe GarnetStatus SortedSetRemoveRangeByLex(PinnedSpanB /// public unsafe GarnetStatus SortedSetRemoveRangeByScore(PinnedSpanByte key, string min, string max, out int countRemoved, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { countRemoved = 0; @@ -273,7 +273,7 @@ public unsafe GarnetStatus SortedSetRemoveRangeByScore(PinnedSpa /// public unsafe GarnetStatus SortedSetRemoveRangeByRank(PinnedSpanByte key, int start, int stop, out int countRemoved, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { countRemoved = 0; @@ -330,7 +330,7 @@ public unsafe GarnetStatus SortedSetRemoveRangeByRank(PinnedSpan /// /// public unsafe GarnetStatus SortedSetPop(PinnedSpanByte key, int count, bool lowScoresFirst, out (PinnedSpanByte member, PinnedSpanByte score)[] pairs, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { pairs = default; if (key.Length == 0) @@ -366,7 +366,7 @@ public unsafe GarnetStatus SortedSetPop(PinnedSpanByte key, int /// public unsafe GarnetStatus SortedSetIncrement(PinnedSpanByte key, double increment, PinnedSpanByte member, out double newScore, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { newScore = 0; @@ -412,7 +412,7 @@ public unsafe GarnetStatus SortedSetIncrement(PinnedSpanByte key /// /// public unsafe GarnetStatus SortedSetLength(PinnedSpanByte key, out int zcardCount, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { zcardCount = 0; @@ -447,7 +447,7 @@ public unsafe GarnetStatus SortedSetLength(PinnedSpanByte key, o /// /// public unsafe GarnetStatus SortedSetRange(PinnedSpanByte key, PinnedSpanByte min, PinnedSpanByte max, SortedSetOrderOperation sortedSetOrderOperation, ref TObjectContext objectContext, out PinnedSpanByte[] elements, out string error, bool withScores = false, bool reverse = false, (string, int) limit = default) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { elements = default; error = default; @@ -530,8 +530,9 @@ public unsafe GarnetStatus SortedSetDifference(ReadOnlySpan keys { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); txnManager.Run(true); } @@ -578,13 +579,15 @@ public GarnetStatus SortedSetDifferenceStore(PinnedSpanByte destinationKey, Read { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; - txnManager.SaveKeyEntryToLock(destinationKey, true, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object | TransactionStoreTypes.Unified); + txnManager.SaveKeyEntryToLock(destinationKey, LockType.Exclusive); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); _ = txnManager.Run(true); } var objectContext = txnManager.ObjectStoreTransactionalContext; + var unifiedContext = txnManager.UnifiedStoreTransactionalContext; try { @@ -609,8 +612,7 @@ public GarnetStatus SortedSetDifferenceStore(PinnedSpanByte destinationKey, Read } else { - _ = EXPIRE(destinationKey, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, - ref transactionalContext, ref objectContext); + _ = EXPIRE(destinationKey, TimeSpan.Zero, out _, ExpireOption.None, ref unifiedContext); } return status; @@ -631,7 +633,7 @@ public GarnetStatus SortedSetDifferenceStore(PinnedSpanByte destinationKey, Read /// /// public unsafe GarnetStatus SortedSetRank(PinnedSpanByte key, PinnedSpanByte member, bool reverse, out long? rank, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { rank = null; if (key.Length == 0) @@ -678,7 +680,7 @@ public unsafe GarnetStatus SortedSetRank(PinnedSpanByte key, Pin /// /// public GarnetStatus SortedSetAdd(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { var status = RMWObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); itemBroker.HandleCollectionUpdate(key.ToArray()); @@ -696,7 +698,7 @@ public GarnetStatus SortedSetAdd(PinnedSpanByte key, ref ObjectI /// The context of the object store. /// Returns a GarnetStatus indicating the success or failure of the operation. public unsafe GarnetStatus SortedSetRangeStore(PinnedSpanByte dstKey, PinnedSpanByte srcKey, ref ObjectInput input, out int result, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { if (txnManager.ObjectStoreTransactionalContext.Session is null) ThrowObjectStoreUninitializedException(); @@ -712,19 +714,21 @@ public unsafe GarnetStatus SortedSetRangeStore(PinnedSpanByte ds { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; - txnManager.SaveKeyEntryToLock(dstKey, isObject: true, LockType.Exclusive); - txnManager.SaveKeyEntryToLock(srcKey, isObject: true, LockType.Shared); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object | TransactionStoreTypes.Unified); + txnManager.SaveKeyEntryToLock(dstKey, LockType.Exclusive); + txnManager.SaveKeyEntryToLock(srcKey, LockType.Shared); _ = txnManager.Run(true); } // SetObject - var objectStoreTransactionalContext = txnManager.ObjectStoreTransactionalContext; + var ssObjectStoreTransactionalContext = txnManager.ObjectStoreTransactionalContext; + var ssUnifiedStoreTransactionalContext = txnManager.UnifiedStoreTransactionalContext; try { SpanByteAndMemory rangeOutputMem = default; var rangeOutput = new GarnetObjectStoreOutput(rangeOutputMem); - var status = SortedSetRange(srcKey, ref input, ref rangeOutput, ref objectStoreTransactionalContext); + var status = SortedSetRange(srcKey, ref input, ref rangeOutput, ref ssObjectStoreTransactionalContext); rangeOutputMem = rangeOutput.SpanByteAndMemory; if (status == GarnetStatus.WRONGTYPE) @@ -733,7 +737,7 @@ public unsafe GarnetStatus SortedSetRangeStore(PinnedSpanByte ds if (status == GarnetStatus.NOTFOUND) { // Expire/Delete the destination key if the source key is not found - _ = EXPIRE(dstKey, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, ref transactionalContext, ref objectStoreTransactionalContext); + _ = EXPIRE(dstKey, TimeSpan.Zero, out _, ExpireOption.None, ref ssUnifiedStoreTransactionalContext); return GarnetStatus.OK; } @@ -747,7 +751,7 @@ public unsafe GarnetStatus SortedSetRangeStore(PinnedSpanByte ds var endOutPtr = rangeOutPtr + rangeOutputMem.Length; var destinationKey = dstKey.ReadOnlySpan; - objectStoreTransactionalContext.Delete(destinationKey); + ssObjectStoreTransactionalContext.Delete(destinationKey); RespReadUtils.TryReadUnsignedArrayLength(out var arrayLen, ref currOutPtr, endOutPtr); Debug.Assert(arrayLen % 2 == 0, "Should always contain element and its score"); @@ -772,7 +776,7 @@ public unsafe GarnetStatus SortedSetRangeStore(PinnedSpanByte ds }, ref parseState); var zAddOutput = new GarnetObjectStoreOutput(); - RMWObjectStoreOperationWithOutput(destinationKey, ref zAddInput, ref objectStoreTransactionalContext, ref zAddOutput); + RMWObjectStoreOperationWithOutput(destinationKey, ref zAddInput, ref ssObjectStoreTransactionalContext, ref zAddOutput); itemBroker.HandleCollectionUpdate(destinationKey.ToArray()); } } @@ -799,8 +803,8 @@ public unsafe GarnetStatus SortedSetRangeStore(PinnedSpanByte ds /// /// /// - public GarnetStatus SortedSetRemove(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus SortedSetRemove(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); /// @@ -812,8 +816,8 @@ public GarnetStatus SortedSetRemove(PinnedSpanByte key, ref Obje /// /// /// - public GarnetStatus SortedSetLength(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus SortedSetLength(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); /// @@ -828,7 +832,7 @@ public GarnetStatus SortedSetLength(PinnedSpanByte key, ref Obje /// /// public GarnetStatus SortedSetRange(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -842,7 +846,7 @@ public GarnetStatus SortedSetRange(PinnedSpanByte key, ref Objec /// /// public GarnetStatus SortedSetScore(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -856,7 +860,7 @@ public GarnetStatus SortedSetScore(PinnedSpanByte key, ref Objec /// /// public GarnetStatus SortedSetScores(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -870,7 +874,7 @@ public GarnetStatus SortedSetScores(PinnedSpanByte key, ref Obje /// /// public GarnetStatus SortedSetPop(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -884,7 +888,7 @@ public GarnetStatus SortedSetPop(PinnedSpanByte key, ref ObjectI /// /// public unsafe GarnetStatus SortedSetCount(PinnedSpanByte key, PinnedSpanByte minScore, PinnedSpanByte maxScore, out int numElements, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { numElements = 0; if (key.Length == 0) @@ -923,7 +927,7 @@ public unsafe GarnetStatus SortedSetCount(PinnedSpanByte key, Pi /// /// public GarnetStatus SortedSetCount(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -936,8 +940,8 @@ public GarnetStatus SortedSetCount(PinnedSpanByte key, ref Objec /// /// /// - public GarnetStatus SortedSetRemoveRangeByLex(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus SortedSetRemoveRangeByLex(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectContext) + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectContext); /// @@ -951,8 +955,8 @@ public GarnetStatus SortedSetRemoveRangeByLex(PinnedSpanByte key /// /// /// - public GarnetStatus SortedSetLengthByValue(PinnedSpanByte key, ref ObjectInput input, out ObjectOutputHeader output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + public GarnetStatus SortedSetLengthByValue(PinnedSpanByte key, ref ObjectInput input, out OutputHeader output, ref TObjectContext objectStoreContext) + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperation(key.ReadOnlySpan, ref input, out output, ref objectStoreContext); /// @@ -966,7 +970,7 @@ public GarnetStatus SortedSetLengthByValue(PinnedSpanByte key, r /// /// public GarnetStatus SortedSetIncrement(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); /// @@ -980,7 +984,7 @@ public GarnetStatus SortedSetIncrement(PinnedSpanByte key, ref O /// /// public GarnetStatus SortedSetRemoveRange(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -993,7 +997,7 @@ public GarnetStatus SortedSetRemoveRange(PinnedSpanByte key, ref /// /// public GarnetStatus SortedSetRank(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -1006,7 +1010,7 @@ public GarnetStatus SortedSetRank(PinnedSpanByte key, ref Object /// /// public GarnetStatus SortedSetRandomMember(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectContext, ref output); /// @@ -1020,7 +1024,7 @@ public GarnetStatus SortedSetRandomMember(PinnedSpanByte key, re /// /// public GarnetStatus SortedSetScan(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectStoreContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ReadOnlySpan, ref input, ref objectStoreContext, ref output); public GarnetStatus SortedSetUnion(ReadOnlySpan keys, double[] weights, SortedSetAggregateType aggregateType, out SortedSet<(double, byte[])> pairs) @@ -1036,8 +1040,9 @@ public GarnetStatus SortedSetUnion(ReadOnlySpan keys, double[] w { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); txnManager.Run(true); } @@ -1078,13 +1083,15 @@ public GarnetStatus SortedSetUnionStore(PinnedSpanByte destinationKey, ReadOnlyS { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; - txnManager.SaveKeyEntryToLock(destinationKey, true, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object | TransactionStoreTypes.Unified); + txnManager.SaveKeyEntryToLock(destinationKey, LockType.Exclusive); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); _ = txnManager.Run(true); } var objectContext = txnManager.ObjectStoreTransactionalContext; + var unifiedContext = txnManager.UnifiedStoreTransactionalContext; try { @@ -1110,8 +1117,7 @@ public GarnetStatus SortedSetUnionStore(PinnedSpanByte destinationKey, ReadOnlyS } else { - _ = EXPIRE(destinationKey, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, - ref transactionalContext, ref objectContext); + _ = EXPIRE(destinationKey, TimeSpan.Zero, out _, ExpireOption.None, ref unifiedContext); } return status; @@ -1125,7 +1131,7 @@ public GarnetStatus SortedSetUnionStore(PinnedSpanByte destinationKey, ReadOnlyS private GarnetStatus SortedSetUnion(ReadOnlySpan keys, ref TObjectContext objectContext, out Dictionary pairs, double[] weights = null, SortedSetAggregateType aggregateType = SortedSetAggregateType.Sum) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { pairs = default; @@ -1204,7 +1210,7 @@ private GarnetStatus SortedSetUnion(ReadOnlySpan } private GarnetStatus SortedSetDifference(ReadOnlySpan keys, ref TObjectContext objectContext, out Dictionary pairs) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { pairs = default; @@ -1268,8 +1274,9 @@ public unsafe GarnetStatus SortedSetMPop(ReadOnlySpan keys, int { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object); foreach (var key in keys) - txnManager.SaveKeyEntryToLock(key, true, LockType.Exclusive); + txnManager.SaveKeyEntryToLock(key, LockType.Exclusive); txnManager.Run(true); } @@ -1334,13 +1341,15 @@ public GarnetStatus SortedSetIntersectStore(PinnedSpanByte destinationKey, ReadO { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; - txnManager.SaveKeyEntryToLock(destinationKey, true, LockType.Exclusive); + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object | TransactionStoreTypes.Unified); + txnManager.SaveKeyEntryToLock(destinationKey, LockType.Exclusive); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); _ = txnManager.Run(true); } var objectContext = txnManager.ObjectStoreTransactionalContext; + var unifiedContext = txnManager.UnifiedStoreTransactionalContext; try { @@ -1366,8 +1375,7 @@ public GarnetStatus SortedSetIntersectStore(PinnedSpanByte destinationKey, ReadO } else { - _ = EXPIRE(destinationKey, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None, - ref transactionalContext, ref objectContext); + _ = EXPIRE(destinationKey, TimeSpan.Zero, out _, ExpireOption.None, ref unifiedContext); } return status; @@ -1395,8 +1403,9 @@ public GarnetStatus SortedSetIntersect(ReadOnlySpan keys, double { Debug.Assert(txnManager.state == TxnState.None); createTransaction = true; + txnManager.AddTransactionStoreTypes(TransactionStoreTypes.Object); foreach (var item in keys) - txnManager.SaveKeyEntryToLock(item, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(item, LockType.Shared); txnManager.Run(true); } @@ -1437,7 +1446,7 @@ public GarnetStatus SortedSetIntersect(ReadOnlySpan keys, double /// The resulting dictionary of intersected elements and their scores. /// private GarnetStatus SortedSetIntersection(ReadOnlySpan keys, double[] weights, SortedSetAggregateType aggregateType, ref TObjectContext objectContext, out Dictionary pairs) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { pairs = default; @@ -1535,7 +1544,7 @@ private GarnetStatus SortedSetIntersection(ReadOnlySpanThe object context for the operation. /// The status of the operation. public GarnetStatus SortedSetExpire(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { return RMWObjectStoreOperationWithOutput(key.ToArray(), ref input, ref objectContext, ref output); } @@ -1552,7 +1561,7 @@ public GarnetStatus SortedSetExpire(PinnedSpanByte key, ref Obje /// The context of the object store. /// Returns a GarnetStatus indicating the success or failure of the operation. public GarnetStatus SortedSetExpire(PinnedSpanByte key, ReadOnlySpan members, DateTimeOffset expireAt, ExpireOption expireOption, out int[] results, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { results = default; var expirationTimeInTicks = expireAt.UtcTicks; @@ -1585,7 +1594,7 @@ public GarnetStatus SortedSetExpire(PinnedSpanByte key, ReadOnly /// The object context for the operation. /// The status of the operation. public GarnetStatus SortedSetTimeToLive(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => ReadObjectStoreOperationWithOutput(key.ToArray(), ref input, ref objectContext, ref output); /// @@ -1598,7 +1607,7 @@ public GarnetStatus SortedSetTimeToLive(PinnedSpanByte key, ref /// The context of the object store. /// Returns a GarnetStatus indicating the success or failure of the operation. public GarnetStatus SortedSetTimeToLive(PinnedSpanByte key, ReadOnlySpan members, out TimeSpan[] expireIn, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { expireIn = default; var header = new RespInputHeader(GarnetObjectType.SortedSet) { SortedSetOp = SortedSetOperation.ZTTL }; @@ -1626,7 +1635,7 @@ public GarnetStatus SortedSetTimeToLive(PinnedSpanByte key, Read /// The object context for the operation. /// The status of the operation. public GarnetStatus SortedSetPersist(PinnedSpanByte key, ref ObjectInput input, ref GarnetObjectStoreOutput output, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext => RMWObjectStoreOperationWithOutput(key.ToArray(), ref input, ref objectContext, ref output); /// @@ -1639,7 +1648,7 @@ public GarnetStatus SortedSetPersist(PinnedSpanByte key, ref Obj /// The context of the object store. /// Returns a GarnetStatus indicating the success or failure of the operation. public GarnetStatus SortedSetPersist(PinnedSpanByte key, ReadOnlySpan members, out int[] results, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { results = default; var header = new RespInputHeader(GarnetObjectType.SortedSet) { SortedSetOp = SortedSetOperation.ZPERSIST }; @@ -1671,7 +1680,7 @@ public GarnetStatus SortedSetPersist(PinnedSpanByte key, ReadOnl /// Otherwise, the operation is performed on the specified keys. /// public GarnetStatus SortedSetCollect(ReadOnlySpan keys, ref ObjectInput input, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { if (keys[0].ReadOnlySpan.SequenceEqual("*"u8)) { @@ -1697,7 +1706,7 @@ public GarnetStatus SortedSetCollect(ReadOnlySpan public GarnetStatus SortedSetCollect(ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { return SortedSetCollect([], ref objectContext); } @@ -1714,7 +1723,7 @@ public GarnetStatus SortedSetCollect(ref TObjectContext objectCo /// Otherwise, the operation is performed on the specified keys. /// public GarnetStatus SortedSetCollect(ReadOnlySpan keys, ref TObjectContext objectContext) - where TObjectContext : ITsavoriteContext + where TObjectContext : ITsavoriteContext { var header = new RespInputHeader(GarnetObjectType.SortedSet) { SortedSetOp = SortedSetOperation.ZCOLLECT }; var innerInput = new ObjectInput(header); diff --git a/libs/server/Storage/Session/StorageSession.cs b/libs/server/Storage/Session/StorageSession.cs index 73da80a116f..98f32a91952 100644 --- a/libs/server/Storage/Session/StorageSession.cs +++ b/libs/server/Storage/Session/StorageSession.cs @@ -8,11 +8,8 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Storage Session - the internal layer that Garnet uses to perform storage operations @@ -26,8 +23,8 @@ sealed partial class StorageSession : IDisposable /// /// Session Contexts for main store /// - public BasicContext basicContext; - public TransactionalContext transactionalContext; + public BasicContext basicContext; + public TransactionalContext transactionalContext; SectorAlignedMemory sectorAlignedMemoryHll1; SectorAlignedMemory sectorAlignedMemoryHll2; @@ -39,8 +36,14 @@ sealed partial class StorageSession : IDisposable /// /// Session Contexts for object store /// - public BasicContext objectStoreBasicContext; - public TransactionalContext objectStoreTransactionalContext; + public BasicContext objectStoreBasicContext; + public TransactionalContext objectStoreTransactionalContext; + + /// + /// Session Contexts for unified store + /// + public BasicContext unifiedStoreBasicContext; + public TransactionalContext unifiedStoreTransactionalContext; public readonly ScratchBufferBuilder scratchBufferBuilder; public readonly FunctionsState functionsState; @@ -78,20 +81,22 @@ public StorageSession(StoreWrapper storeWrapper, Debug.Assert(dbFound); this.stateMachineDriver = db.StateMachineDriver; - var session = db.MainStore.NewSession(functions); + var session = db.Store.NewSession(functions); var objectStoreFunctions = new ObjectSessionFunctions(functionsState); - var objectStoreSession = db.ObjectStore?.NewSession(objectStoreFunctions); + var objectStoreSession = db.Store.NewSession(objectStoreFunctions); + + var unifiedStoreFunctions = new UnifiedSessionFunctions(functionsState); + var unifiedStoreSession = db.Store.NewSession(unifiedStoreFunctions); basicContext = session.BasicContext; transactionalContext = session.TransactionalContext; - if (objectStoreSession != null) - { - objectStoreBasicContext = objectStoreSession.BasicContext; - objectStoreTransactionalContext = objectStoreSession.TransactionalContext; - } + objectStoreBasicContext = objectStoreSession.BasicContext; + objectStoreTransactionalContext = objectStoreSession.TransactionalContext; + unifiedStoreBasicContext = unifiedStoreSession.BasicContext; + unifiedStoreTransactionalContext = unifiedStoreSession.TransactionalContext; - HeadAddress = db.MainStore.Log.HeadAddress; + HeadAddress = db.Store.Log.HeadAddress; ObjectScanCountLimit = storeWrapper.serverOptions.ObjectScanCountLimit; } @@ -108,6 +113,7 @@ public void Dispose() sectorAlignedMemoryBitmap?.Dispose(); basicContext.Session.Dispose(); objectStoreBasicContext.Session?.Dispose(); + unifiedStoreBasicContext.Session?.Dispose(); sectorAlignedMemoryHll1?.Dispose(); sectorAlignedMemoryHll2?.Dispose(); } diff --git a/libs/server/Storage/Session/UnifiedStore/AdvancedOps.cs b/libs/server/Storage/Session/UnifiedStore/AdvancedOps.cs new file mode 100644 index 00000000000..605be41209d --- /dev/null +++ b/libs/server/Storage/Session/UnifiedStore/AdvancedOps.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using Tsavorite.core; + +namespace Garnet.server +{ + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; + + sealed partial class StorageSession : IDisposable + { + public GarnetStatus Read_UnifiedStore(ReadOnlySpan key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output, ref TUnifiedContext unifiedContext) + where TUnifiedContext : ITsavoriteContext + { + var status = unifiedContext.Read(key, ref input, ref output); + + if (status.IsPending) + CompletePendingForUnifiedStoreSession(ref status, ref output, ref unifiedContext); + + return status.Found ? GarnetStatus.OK : GarnetStatus.NOTFOUND; + } + + public GarnetStatus RMW_UnifiedStore(ReadOnlySpan key, ref UnifiedStoreInput input, ref GarnetUnifiedStoreOutput output, ref TUnifiedContext context) + where TUnifiedContext : ITsavoriteContext + { + var status = context.RMW(key, ref input, ref output); + + if (status.IsPending) + CompletePendingForUnifiedStoreSession(ref status, ref output, ref context); + + return status.Found || status.Record.Created || status.Record.InPlaceUpdated + ? GarnetStatus.OK + : GarnetStatus.NOTFOUND; + } + } +} \ No newline at end of file diff --git a/libs/server/Storage/Session/UnifiedStore/CompletePending.cs b/libs/server/Storage/Session/UnifiedStore/CompletePending.cs new file mode 100644 index 00000000000..9e4a1d153d5 --- /dev/null +++ b/libs/server/Storage/Session/UnifiedStore/CompletePending.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Diagnostics; +using Tsavorite.core; + +namespace Garnet.server +{ + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; + + sealed partial class StorageSession + { + /// + /// Handles the complete pending for Unified Store session + /// + /// + /// + /// + static void CompletePendingForUnifiedStoreSession(ref Status status, ref GarnetUnifiedStoreOutput output, ref TUnifiedContext unified) + where TUnifiedContext : ITsavoriteContext + { + unified.CompletePendingWithOutputs(out var completedOutputs, wait: true); + var more = completedOutputs.Next(); + Debug.Assert(more); + status = completedOutputs.Current.Status; + output = completedOutputs.Current.Output; + Debug.Assert(!completedOutputs.Next()); + completedOutputs.Dispose(); + } + } +} \ No newline at end of file diff --git a/libs/server/Storage/Session/UnifiedStore/UnifiedStoreOps.cs b/libs/server/Storage/Session/UnifiedStore/UnifiedStoreOps.cs new file mode 100644 index 00000000000..588e3651872 --- /dev/null +++ b/libs/server/Storage/Session/UnifiedStore/UnifiedStoreOps.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; +using Garnet.common; +using Tsavorite.core; + +namespace Garnet.server +{ + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; + + sealed partial class StorageSession : IDisposable + { + /// + /// Checks if a key exists in the unified store context. + /// + /// + /// The name of the key to use in the operation + /// Basic unifiedContext for the unified store. + /// + public GarnetStatus EXISTS(PinnedSpanByte key, ref TUnifiedContext unifiedContext) + where TUnifiedContext : ITsavoriteContext + { + // Prepare input + var input = new UnifiedStoreInput(RespCommand.EXISTS); + + // Prepare GarnetUnifiedStoreOutput output + var output = new GarnetUnifiedStoreOutput(); + + var status = Read_UnifiedStore(key, ref input, ref output, ref unifiedContext); + + return status; + } + + /// + /// Deletes a key from the unified store context. + /// + /// The name of the key to use in the operation + /// Basic unifiedContext for the unified store. + /// + public GarnetStatus DELETE(PinnedSpanByte key, ref TUnifiedContext unifiedContext) + where TUnifiedContext : ITsavoriteContext + { + var status = unifiedContext.Delete(key.ReadOnlySpan); + Debug.Assert(!status.IsPending); + return status.Found ? GarnetStatus.OK : GarnetStatus.NOTFOUND; + } + + /// + /// Set a timeout on key + /// + /// + /// The key to set the timeout on. + /// Milliseconds value for the timeout. + /// True when the timeout was properly set. + /// >Flags to use for the operation. + /// Basic context for the unified store. + /// + public unsafe GarnetStatus EXPIRE(PinnedSpanByte key, PinnedSpanByte expiryMs, out bool timeoutSet, ExpireOption expireOption, ref TUnifiedContext unifiedContext) + where TUnifiedContext : ITsavoriteContext + => EXPIRE(key, TimeSpan.FromMilliseconds(NumUtils.ReadInt64(expiryMs.Length, expiryMs.ToPointer())), out timeoutSet, expireOption, ref unifiedContext); + + /// + /// Set a timeout on key using absolute Unix timestamp (seconds since January 1, 1970). + /// + /// + /// The key to set the timeout on. + /// Absolute Unix timestamp + /// True when the timeout was properly set. + /// Flags to use for the operation. + /// Basic context for the unified store. + /// When true, is treated as milliseconds else seconds + /// Return GarnetStatus.OK when key found, else GarnetStatus.NOTFOUND + public unsafe GarnetStatus EXPIREAT(PinnedSpanByte key, long expiryTimestamp, out bool timeoutSet, ExpireOption expireOption, ref TUnifiedContext unifiedContext, bool milliseconds = false) + where TUnifiedContext : ITsavoriteContext + { + return EXPIRE(key, expiryTimestamp, out timeoutSet, expireOption, ref unifiedContext, milliseconds ? RespCommand.PEXPIREAT : RespCommand.EXPIREAT); + } + + /// + /// Set a timeout on key. + /// + /// + /// The key to set the timeout on. + /// The timespan value to set the expiration for. + /// True when the timeout was properly set. + /// Flags to use for the operation. + /// Basic context for the unified store. + /// When true the command executed is PEXPIRE, expire by default. + /// Return GarnetStatus.OK when key found, else GarnetStatus.NOTFOUND + public unsafe GarnetStatus EXPIRE(PinnedSpanByte key, TimeSpan expiry, out bool timeoutSet, ExpireOption expireOption, ref TUnifiedContext unifiedContext, bool milliseconds = false) + where TUnifiedContext : ITsavoriteContext + { + return EXPIRE(key, (long)(milliseconds ? expiry.TotalMilliseconds : expiry.TotalSeconds), out timeoutSet, expireOption, + ref unifiedContext, milliseconds ? RespCommand.PEXPIRE : RespCommand.EXPIRE); + } + + /// + /// Set a timeout on key. + /// + /// + /// The key to set the timeout on. + /// The timespan value to set the expiration for. + /// True when the timeout was properly set. + /// Flags to use for the operation. + /// Basic context for the main store + /// The current RESP command + /// + public unsafe GarnetStatus EXPIRE(PinnedSpanByte key, long expiration, out bool timeoutSet, ExpireOption expireOption, ref TUnifiedContext unifiedContext, RespCommand respCommand) + where TUnifiedContext : ITsavoriteContext + { + Span rmwOutput = stackalloc byte[OutputHeader.Size]; + var unifiedOutput = new GarnetUnifiedStoreOutput(SpanByteAndMemory.FromPinnedSpan(rmwOutput)); + + // Convert to expiration time in ticks + var expirationTimeInTicks = respCommand switch + { + RespCommand.EXPIRE => DateTimeOffset.UtcNow.AddSeconds(expiration).UtcTicks, + RespCommand.PEXPIRE => DateTimeOffset.UtcNow.AddMilliseconds(expiration).UtcTicks, + RespCommand.EXPIREAT => ConvertUtils.UnixTimestampInSecondsToTicks(expiration), + _ => ConvertUtils.UnixTimestampInMillisecondsToTicks(expiration) + }; + + var expirationWithOption = new ExpirationWithOption(expirationTimeInTicks, expireOption); + + var input = new UnifiedStoreInput(RespCommand.EXPIRE, arg1: expirationWithOption.Word); + var status = unifiedContext.RMW(key.ReadOnlySpan, ref input, ref unifiedOutput); + + if (status.IsPending) + CompletePendingForUnifiedStoreSession(ref status, ref unifiedOutput, ref unifiedContext); + + timeoutSet = status.Found && ((OutputHeader*)unifiedOutput.SpanByteAndMemory.SpanByte.ToPointer())->result1 == 1; + + return status.Found ? GarnetStatus.OK : GarnetStatus.NOTFOUND; + } + } +} \ No newline at end of file diff --git a/libs/server/Storage/SizeTracker/CacheSizeTracker.cs b/libs/server/Storage/SizeTracker/CacheSizeTracker.cs index 86b11b29d6a..b5a1805c56f 100644 --- a/libs/server/Storage/SizeTracker/CacheSizeTracker.cs +++ b/libs/server/Storage/SizeTracker/CacheSizeTracker.cs @@ -10,8 +10,8 @@ namespace Garnet.server { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Tracks the size of the main log and read cache. @@ -20,8 +20,8 @@ namespace Garnet.server /// public class CacheSizeTracker { - internal readonly LogSizeTracker mainLogTracker; - internal readonly LogSizeTracker readCacheTracker; + internal readonly LogSizeTracker mainLogTracker; + internal readonly LogSizeTracker readCacheTracker; private long targetSize; public long ReadCacheTargetSize; @@ -72,7 +72,7 @@ public readonly long CalculateRecordSize(in TSourceLogRecord l /// Total memory size target /// Target memory size for read cache /// - public CacheSizeTracker(TsavoriteKV store, KVSettings logSettings, + public CacheSizeTracker(TsavoriteKV store, KVSettings logSettings, long targetSize, long readCacheTargetSize, ILoggerFactory loggerFactory = null) { Debug.Assert(store != null); @@ -85,19 +85,19 @@ public CacheSizeTracker(TsavoriteKV if (targetSize > 0) { - this.mainLogTracker = new LogSizeTracker(store.Log, logSizeCalculator, + this.mainLogTracker = new LogSizeTracker(store.Log, logSizeCalculator, targetSize, targetSize / deltaFraction, loggerFactory?.CreateLogger("ObjSizeTracker")); store.Log.SubscribeEvictions(mainLogTracker); - store.Log.SubscribeDeserializations(new LogOperationObserver(mainLogTracker, LogOperationType.Deserialize)); + store.Log.SubscribeDeserializations(new LogOperationObserver(mainLogTracker, LogOperationType.Deserialize)); store.Log.IsSizeBeyondLimit = () => mainLogTracker.IsSizeBeyondLimit; } if (store.ReadCache != null && readCacheTargetSize > 0) { - this.readCacheTracker = new LogSizeTracker(store.ReadCache, logSizeCalculator, + this.readCacheTracker = new LogSizeTracker(store.ReadCache, logSizeCalculator, readCacheTargetSize, readCacheTargetSize / deltaFraction, loggerFactory?.CreateLogger("ObjReadCacheSizeTracker")); store.ReadCache.SubscribeEvictions(readCacheTracker); - store.ReadCache.SubscribeDeserializations(new LogOperationObserver(readCacheTracker, LogOperationType.Deserialize)); + store.ReadCache.SubscribeDeserializations(new LogOperationObserver(readCacheTracker, LogOperationType.Deserialize)); store.ReadCache.IsSizeBeyondLimit = () => readCacheTracker.IsSizeBeyondLimit; } } diff --git a/libs/server/StoreWrapper.cs b/libs/server/StoreWrapper.cs index 2fd8d890715..49291bdc1e8 100644 --- a/libs/server/StoreWrapper.cs +++ b/libs/server/StoreWrapper.cs @@ -19,11 +19,8 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Wrapper for store and store-specific information @@ -43,12 +40,7 @@ public sealed class StoreWrapper /// /// Store (of DB 0) /// - public TsavoriteKV store => databaseManager.MainStore; - - /// - /// Object store (of DB 0) - /// - public TsavoriteKV objectStore => databaseManager.ObjectStore; + public TsavoriteKV store => databaseManager.Store; /// /// AOF (of DB 0) @@ -61,12 +53,11 @@ public sealed class StoreWrapper public DateTimeOffset lastSaveTime => databaseManager.LastSaveTime; /// - /// Object store size tracker (of DB 0) + /// Store size tracker (of DB 0) /// - public CacheSizeTracker objectStoreSizeTracker => databaseManager.ObjectStoreSizeTracker; + public CacheSizeTracker sizeTracker => databaseManager.SizeTracker; - public IStoreFunctions mainStoreFunctions => store.StoreFunctions; - public IStoreFunctions objectStoreFunctions => objectStore?.StoreFunctions; + public IStoreFunctions storeFunctions => store.StoreFunctions; /// /// Server options @@ -158,15 +149,10 @@ public sealed class StoreWrapper bool disposed; /// - /// Garnet checkpoint manager for main store + /// Garnet checkpoint manager /// public GarnetCheckpointManager StoreCheckpointManager => (GarnetCheckpointManager)store?.CheckpointManager; - /// - /// Garnet checkpoint manager for object store - /// - public GarnetCheckpointManager ObjectStoreCheckpointManager => (GarnetCheckpointManager)objectStore?.CheckpointManager; - /// /// Constructor /// @@ -207,8 +193,7 @@ public StoreWrapper( if (serverOptions.SlowLogThreshold > 0) this.slowLogContainer = new SlowLogContainer(serverOptions.SlowLogMaxEntries); - if (!serverOptions.DisableObjects) - this.itemBroker = new CollectionItemBroker(); + this.itemBroker = new CollectionItemBroker(); // Initialize store scripting cache if (serverOptions.EnableLua) @@ -268,11 +253,6 @@ public StoreWrapper( { StoreCheckpointManager.CurrentHistoryId = runId; } - - if (!serverOptions.DisableObjects && ObjectStoreCheckpointManager != null) - { - ObjectStoreCheckpointManager.CurrentHistoryId = runId; - } } } @@ -411,9 +391,8 @@ public async Task TakeOnDemandCheckpoint(DateTimeOffset entryTime, int dbId = 0) /// /// Recover checkpoint /// - public void RecoverCheckpoint(bool replicaRecover = false, bool recoverMainStoreFromToken = false, - bool recoverObjectStoreFromToken = false, CheckpointMetadata metadata = null) - => databaseManager.RecoverCheckpoint(replicaRecover, recoverMainStoreFromToken, recoverObjectStoreFromToken, metadata); + public void RecoverCheckpoint(bool replicaRecover = false, bool recoverMainStoreFromToken = false, CheckpointMetadata metadata = null) + => databaseManager.RecoverCheckpoint(replicaRecover, recoverMainStoreFromToken, metadata); /// /// Mark the beginning of a checkpoint by taking and a lock to avoid concurrent checkpointing @@ -714,12 +693,6 @@ async Task ObjectCollectTask(int objectCollectFrequencySecs, CancellationToken t Debug.Assert(objectCollectFrequencySecs > 0); try { - if (serverOptions.DisableObjects) - { - logger?.LogWarning("ExpiredObjectCollectionFrequencySecs option is configured but Object store is disabled. Stopping the background hash collect task."); - return; - } - while (true) { if (token.IsCancellationRequested) return; @@ -833,7 +806,7 @@ internal void Start() Task.Run(() => IndexAutoGrowTask(ctsCommit.Token)); } - databaseManager.StartObjectSizeTrackers(ctsCommit.Token); + databaseManager.StartSizeTrackers(ctsCommit.Token); } public bool HasKeysInSlots(List slots) @@ -852,11 +825,11 @@ public bool HasKeysInSlots(List slots) } } - if (!hasKeyInSlots && !serverOptions.DisableObjects) + if (!hasKeyInSlots) { var functionsState = databaseManager.CreateFunctionsState(); - var objstorefunctions = new ObjectSessionFunctions(functionsState); - var objectStoreSession = objectStore?.NewSession(objstorefunctions); + var objStorefunctions = new ObjectSessionFunctions(functionsState); + var objectStoreSession = store?.NewSession(objStorefunctions); var iter = objectStoreSession.Iterate(); while (!hasKeyInSlots && iter.GetNext()) { diff --git a/libs/server/Transaction/TransactionManager.cs b/libs/server/Transaction/TransactionManager.cs index 5f982565b8d..5240bd4a05d 100644 --- a/libs/server/Transaction/TransactionManager.cs +++ b/libs/server/Transaction/TransactionManager.cs @@ -11,22 +11,35 @@ namespace Garnet.server { using BasicGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, BasicContext, + ObjectAllocator>>, + BasicContext, ObjectAllocator>>>; - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; using TransactionalGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, TransactionalContext, + ObjectAllocator>>, + TransactionalContext, ObjectAllocator>>>; + [Flags] + public enum TransactionStoreTypes : byte + { + None = 0, + Main = 1, + Object = 1 << 1, + Unified = 1 << 2, + } + /// /// Transaction manager /// @@ -35,22 +48,32 @@ public sealed unsafe partial class TransactionManager /// /// Basic context for main store /// - readonly BasicContext basicContext; + readonly BasicContext basicContext; /// /// Transactional context for main store /// - readonly TransactionalContext transactionalContext; + readonly TransactionalContext transactionalContext; /// /// Basic context for object store /// - readonly BasicContext objectStoreBasicContext; + readonly BasicContext objectStoreBasicContext; /// /// Transactional context for object store /// - readonly TransactionalContext objectStoreTransactionalContext; + readonly TransactionalContext objectStoreTransactionalContext; + + /// + /// Basic context for unified store + /// + readonly BasicContext unifiedStoreBasicContext; + + /// + /// Transactional context for unified store + /// + readonly TransactionalContext unifiedStoreTransactionalContext; // Not readonly to avoid defensive copy GarnetWatchApi garnetTxPrepareApi; @@ -76,16 +99,18 @@ public sealed unsafe partial class TransactionManager public TxnState state; private const int initialSliceBufferSize = 1 << 10; private const int initialKeyBufferSize = 1 << 10; - StoreType transactionStoreType; readonly ILogger logger; long txnVersion; + private TransactionStoreTypes storeTypes; - internal TransactionalContext TransactionalContext + internal TransactionalContext TransactionalContext => transactionalContext; - internal TransactionalUnsafeContext TransactionalUnsafeContext + internal TransactionalUnsafeContext TransactionalUnsafeContext => basicContext.Session.TransactionalUnsafeContext; - internal TransactionalContext ObjectStoreTransactionalContext + internal TransactionalContext ObjectStoreTransactionalContext => objectStoreTransactionalContext; + internal TransactionalContext UnifiedStoreTransactionalContext + => unifiedStoreTransactionalContext; /// /// Array to keep pointer keys in keyBuffer @@ -107,11 +132,12 @@ internal TransactionManager(StoreWrapper storeWrapper, transactionalContext = session.TransactionalContext; var objectStoreSession = storageSession.objectStoreBasicContext.Session; - if (objectStoreSession != null) - { - objectStoreBasicContext = objectStoreSession.BasicContext; - objectStoreTransactionalContext = objectStoreSession.TransactionalContext; - } + objectStoreBasicContext = objectStoreSession.BasicContext; + objectStoreTransactionalContext = objectStoreSession.TransactionalContext; + + var unifiedStoreSession = storageSession.unifiedStoreBasicContext.Session; + unifiedStoreBasicContext = unifiedStoreSession.BasicContext; + unifiedStoreTransactionalContext = unifiedStoreSession.TransactionalContext; this.functionsState = storageSession.functionsState; this.appendOnlyFile = functionsState.appendOnlyFile; @@ -120,7 +146,7 @@ internal TransactionManager(StoreWrapper storeWrapper, this.respSession = respSession; watchContainer = new WatchedKeysContainer(initialSliceBufferSize, functionsState.watchVersionMap); - keyEntries = new TxnKeyEntries(initialSliceBufferSize, transactionalContext, objectStoreTransactionalContext); + keyEntries = new TxnKeyEntries(initialSliceBufferSize, transactionalContext, objectStoreTransactionalContext, unifiedStoreTransactionalContext); this.scratchBufferAllocator = scratchBufferAllocator; var dbFound = storeWrapper.TryGetDatabase(dbId, out var db); @@ -146,15 +172,12 @@ internal void Reset(bool isRunning) { keyEntries.UnlockAllKeys(); - // Release context - if (transactionStoreType == StoreType.Main || transactionStoreType == StoreType.All) + // Release contexts + if ((storeTypes & TransactionStoreTypes.Main) == TransactionStoreTypes.Main) transactionalContext.EndTransaction(); - if (transactionStoreType == StoreType.Object || transactionStoreType == StoreType.All) - { - if (objectStoreBasicContext.IsNull) - throw new Exception("Trying to perform object store transaction with object store disabled"); + if ((storeTypes & TransactionStoreTypes.Object) == TransactionStoreTypes.Object) objectStoreTransactionalContext.EndTransaction(); - } + unifiedStoreTransactionalContext.EndTransaction(); } finally { @@ -165,7 +188,7 @@ internal void Reset(bool isRunning) this.txnStartHead = 0; this.operationCntTxn = 0; this.state = TxnState.None; - this.transactionStoreType = 0; + this.storeTypes = TransactionStoreTypes.None; functionsState.StoredProcMode = false; // Reset cluster variables used for slot verification @@ -173,7 +196,7 @@ internal void Reset(bool isRunning) this.keyCount = 0; } - internal bool RunTransactionProc(byte id, ref CustomProcedureInput procInput, CustomTransactionProcedure proc, ref MemoryResult output) + internal bool RunTransactionProc(byte id, ref CustomProcedureInput procInput, CustomTransactionProcedure proc, ref MemoryResult output, bool isRecovering = false) { var running = false; scratchBufferAllocator.Reset(); @@ -225,8 +248,14 @@ internal bool RunTransactionProc(byte id, ref CustomProcedureInput procInput, Cu { try { - // Run finalize procedure at the end - proc.Finalize(garnetTxFinalizeApi, ref procInput, ref output); + // Run finalize procedure at the end. + // If the transaction was invoked during AOF replay skip the finalize step altogether + // Finalize logs to AOF accordingly, so let the replay pick up the commits from AOF as + // part of normal AOF replay. + if (!isRecovering) + { + proc.Finalize(garnetTxFinalizeApi, ref procInput, ref output); + } } catch { } @@ -270,33 +299,34 @@ internal void Commit(bool internal_txn = false) Reset(true); } - internal void Watch(PinnedSpanByte key, StoreType type) + internal void Watch(PinnedSpanByte key) { - // Update watch type if object store is disabled - if (type == StoreType.All && objectStoreBasicContext.IsNull) - type = StoreType.Main; - - UpdateTransactionStoreType(type); - watchContainer.AddWatch(key, type); + watchContainer.AddWatch(key); + + // Release context + if ((storeTypes & TransactionStoreTypes.Main) == TransactionStoreTypes.Main) + transactionalContext.ResetModified(key.ReadOnlySpan); + if ((storeTypes & TransactionStoreTypes.Object) == TransactionStoreTypes.Object) + objectStoreTransactionalContext.ResetModified(key.ReadOnlySpan); + unifiedStoreTransactionalContext.ResetModified(key.ReadOnlySpan); + } - if (type == StoreType.Main || type == StoreType.All) - basicContext.ResetModified(key.ReadOnlySpan); - if ((type == StoreType.Object || type == StoreType.All) && !objectStoreBasicContext.IsNull) - objectStoreBasicContext.ResetModified(key.ReadOnlySpan); + internal void AddTransactionStoreTypes(TransactionStoreTypes transactionStoreTypes) + { + this.storeTypes |= transactionStoreTypes; } - void UpdateTransactionStoreType(StoreType type) + internal void AddTransactionStoreType(StoreType storeType) { - if (transactionStoreType != StoreType.All) + var transactionStoreTypes = storeType switch { - if (transactionStoreType == 0) - transactionStoreType = type; - else - { - if (transactionStoreType != type) - transactionStoreType = StoreType.All; - } - } + StoreType.Main => TransactionStoreTypes.Main, + StoreType.Object => TransactionStoreTypes.Object, + StoreType.All => TransactionStoreTypes.Unified, + _ => TransactionStoreTypes.None + }; + + this.storeTypes |= transactionStoreTypes; } internal string GetLockset() => keyEntries.GetLockset(); @@ -310,32 +340,22 @@ internal void GetKeysForValidation(byte* recvBufferPtr, out PinnedSpanByte[] key readOnly = keyEntries.IsReadOnly; } - void BeginTransaction(StoreType transactionStoreType) + void BeginTransaction() { - if (transactionStoreType is StoreType.All or StoreType.Main) - { + if ((storeTypes & TransactionStoreTypes.Main) == TransactionStoreTypes.Main) transactionalContext.BeginTransaction(); - } - if (transactionStoreType is StoreType.All or StoreType.Object) - { - if (objectStoreBasicContext.IsNull) - throw new Exception("Trying to perform object store transaction with object store disabled"); + if ((storeTypes & TransactionStoreTypes.Object) == TransactionStoreTypes.Object) objectStoreTransactionalContext.BeginTransaction(); - } + unifiedStoreTransactionalContext.BeginTransaction(); } - void LocksAcquired(StoreType transactionStoreType, long txnVersion) + void LocksAcquired(long txnVersion) { - if (transactionStoreType is StoreType.All or StoreType.Main) - { + if ((storeTypes & TransactionStoreTypes.Main) == TransactionStoreTypes.Main) transactionalContext.LocksAcquired(txnVersion); - } - if (transactionStoreType is StoreType.All or StoreType.Object) - { - if (objectStoreBasicContext.IsNull) - throw new Exception("Trying to perform object store transaction with object store disabled"); + if ((storeTypes & TransactionStoreTypes.Object) == TransactionStoreTypes.Object) objectStoreTransactionalContext.LocksAcquired(txnVersion); - } + unifiedStoreTransactionalContext.LocksAcquired(txnVersion); } internal bool Run(bool internal_txn = false, bool fail_fast_on_lock = false, TimeSpan lock_timeout = default) @@ -348,7 +368,7 @@ internal bool Run(bool internal_txn = false, bool fail_fast_on_lock = false, Tim txnVersion = stateMachineDriver.AcquireTransactionVersion(); // Acquire lock sessions - BeginTransaction(transactionStoreType); + BeginTransaction(); bool lockSuccess; if (fail_fast_on_lock) @@ -378,7 +398,7 @@ internal bool Run(bool internal_txn = false, bool fail_fast_on_lock = false, Tim txnVersion = stateMachineDriver.VerifyTransactionVersion(txnVersion); // Update sessions with transaction version - LocksAcquired(transactionStoreType, txnVersion); + LocksAcquired(txnVersion); if (appendOnlyFile != null && !functionsState.StoredProcMode) { diff --git a/libs/server/Transaction/TxnKeyEntry.cs b/libs/server/Transaction/TxnKeyEntry.cs index 5829deebd98..7c945ae5fb4 100644 --- a/libs/server/Transaction/TxnKeyEntry.cs +++ b/libs/server/Transaction/TxnKeyEntry.cs @@ -8,25 +8,19 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Entry for a key to lock and unlock in transactions /// - [StructLayout(LayoutKind.Explicit, Size = 10)] + [StructLayout(LayoutKind.Explicit, Size = 9)] struct TxnKeyEntry : ITransactionalKey { [FieldOffset(0)] internal long keyHash; [FieldOffset(8)] - internal bool isObject; - - [FieldOffset(9)] internal LockType lockType; #region ITransactionalKey @@ -43,7 +37,7 @@ public override readonly string ToString() // The debugger often can't call the Globalization NegativeSign property so ToString() would just display the class name var keyHashSign = keyHash < 0 ? "-" : string.Empty; var absKeyHash = keyHash >= 0 ? keyHash : -keyHash; - return $"{keyHashSign}{absKeyHash}:{(isObject ? "obj" : "raw")}:{(lockType == LockType.None ? "-" : (lockType == LockType.Shared ? "s" : "x"))}"; + return $"{keyHashSign}{absKeyHash}:{(lockType == LockType.None ? "-" : (lockType == LockType.Shared ? "s" : "x"))}"; } } @@ -51,22 +45,21 @@ internal sealed class TxnKeyEntries { // Basic keys int keyCount; - int mainKeyCount; TxnKeyEntry[] keys; - bool mainStoreKeyLocked; - bool objectStoreKeyLocked; + bool unifiedStoreKeyLocked; readonly TxnKeyComparison comparison; public int phase; - internal TxnKeyEntries(int initialCount, TransactionalContext transactionalContext, - TransactionalContext objectStoreTransactionalContext) + internal TxnKeyEntries(int initialCount, TransactionalContext transactionalContext, + TransactionalContext objectStoreTransactionalContext, + TransactionalContext unifiedStoreTransactionalContext) { keys = GC.AllocateArray(initialCount, pinned: true); // We sort a single array for speed, and the sessions use the same sorting logic, - comparison = new(transactionalContext, objectStoreTransactionalContext); + comparison = new(transactionalContext, objectStoreTransactionalContext, unifiedStoreTransactionalContext); } public bool IsReadOnly @@ -74,7 +67,7 @@ public bool IsReadOnly get { var readOnly = true; - for (int i = 0; i < keyCount; i++) + for (var i = 0; i < keyCount; i++) { if (keys[i].lockType == LockType.Exclusive) { @@ -86,11 +79,9 @@ public bool IsReadOnly } } - public void AddKey(PinnedSpanByte keyArgSlice, bool isObject, LockType type) + public void AddKey(PinnedSpanByte keyArgSlice, LockType type) { - var keyHash = !isObject - ? comparison.transactionalContext.GetKeyHash(keyArgSlice.ReadOnlySpan) - : comparison.objectStoreTransactionalContext.GetKeyHash(keyArgSlice.ReadOnlySpan); + var keyHash = comparison.unifiedStoreTransactionalContext.GetKeyHash(keyArgSlice.ReadOnlySpan); // Grow the buffer if needed if (keyCount >= keys.Length) @@ -102,11 +93,8 @@ public void AddKey(PinnedSpanByte keyArgSlice, bool isObject, LockType type) // Populate the new key slot. keys[keyCount].keyHash = keyHash; - keys[keyCount].isObject = isObject; keys[keyCount].lockType = type; ++keyCount; - if (!isObject) - ++mainKeyCount; } internal void LockAllKeys() @@ -119,18 +107,11 @@ internal void LockAllKeys() // This does not call Tsavorite's SortKeyHashes because we need to consider isObject as well. MemoryExtensions.Sort(keys.AsSpan().Slice(0, keyCount), comparison.comparisonDelegate); - // Issue main store locks - if (mainKeyCount > 0) + // Issue unified store locks + if (keyCount > 0) { - comparison.transactionalContext.Lock(keys.AsSpan()[..mainKeyCount]); - mainStoreKeyLocked = true; - } - - // Issue object store locks - if (mainKeyCount < keyCount) - { - comparison.objectStoreTransactionalContext.Lock(keys.AsSpan().Slice(mainKeyCount, keyCount - mainKeyCount)); - objectStoreKeyLocked = true; + comparison.unifiedStoreTransactionalContext.Lock(keys.AsSpan().Slice(0, keyCount)); + unifiedStoreKeyLocked = true; } phase = 0; @@ -146,24 +127,12 @@ internal bool TryLockAllKeys(TimeSpan lock_timeout) // This does not call Tsavorite's SortKeyHashes because we need to consider isObject as well. MemoryExtensions.Sort(keys.AsSpan().Slice(0, keyCount), comparison.comparisonDelegate); - // Issue main store locks - // TryLock will unlock automatically in case of partial failure - if (mainKeyCount > 0) - { - mainStoreKeyLocked = comparison.transactionalContext.TryLock(keys.AsSpan()[..mainKeyCount], lock_timeout); - if (!mainStoreKeyLocked) - { - phase = 0; - return false; - } - } - - // Issue object store locks + // Issue unified store locks // TryLock will unlock automatically in case of partial failure - if (mainKeyCount < keyCount) + if (keyCount > 0) { - objectStoreKeyLocked = comparison.objectStoreTransactionalContext.TryLock(keys.AsSpan().Slice(mainKeyCount, keyCount - mainKeyCount), lock_timeout); - if (!objectStoreKeyLocked) + unifiedStoreKeyLocked = comparison.unifiedStoreTransactionalContext.TryLock(keys.AsSpan().Slice(0, keyCount), lock_timeout); + if (!unifiedStoreKeyLocked) { phase = 0; return false; @@ -177,14 +146,10 @@ internal bool TryLockAllKeys(TimeSpan lock_timeout) internal void UnlockAllKeys() { phase = 2; - if (mainStoreKeyLocked && mainKeyCount > 0) - comparison.transactionalContext.Unlock(keys.AsSpan()[..mainKeyCount]); - if (objectStoreKeyLocked && mainKeyCount < keyCount) - comparison.objectStoreTransactionalContext.Unlock(keys.AsSpan().Slice(mainKeyCount, keyCount - mainKeyCount)); - mainKeyCount = 0; + if (unifiedStoreKeyLocked && keyCount > 0) + comparison.unifiedStoreTransactionalContext.Unlock(keys.AsSpan().Slice(0, keyCount)); keyCount = 0; - mainStoreKeyLocked = false; - objectStoreKeyLocked = false; + unifiedStoreKeyLocked = false; phase = 0; } diff --git a/libs/server/Transaction/TxnKeyEntryComparison.cs b/libs/server/Transaction/TxnKeyEntryComparison.cs index bc2df1ffd29..c64a24a8167 100644 --- a/libs/server/Transaction/TxnKeyEntryComparison.cs +++ b/libs/server/Transaction/TxnKeyEntryComparison.cs @@ -6,38 +6,29 @@ namespace Garnet.server { - using MainStoreAllocator = SpanByteAllocator>; - using MainStoreFunctions = StoreFunctions; - - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; internal sealed class TxnKeyComparison { - public TransactionalContext transactionalContext; - public TransactionalContext objectStoreTransactionalContext; + public TransactionalContext transactionalContext; + public TransactionalContext objectStoreTransactionalContext; + public TransactionalContext unifiedStoreTransactionalContext; public readonly Comparison comparisonDelegate; - internal TxnKeyComparison(TransactionalContext transactionalContext, - TransactionalContext objectStoreTransactionalContext) + internal TxnKeyComparison(TransactionalContext transactionalContext, + TransactionalContext objectStoreTransactionalContext, + TransactionalContext unifiedStoreTransactionalContext) { this.transactionalContext = transactionalContext; this.objectStoreTransactionalContext = objectStoreTransactionalContext; + this.unifiedStoreTransactionalContext = unifiedStoreTransactionalContext; comparisonDelegate = Compare; } /// public int Compare(TxnKeyEntry key1, TxnKeyEntry key2) - { - // This sorts by isObject, then calls Tsavorite to sort by lock code and then by lockType. - var cmp = key1.isObject.CompareTo(key2.isObject); - if (cmp != 0) - return cmp; - if (key1.isObject) - return objectStoreTransactionalContext.CompareKeyHashes(ref key1, ref key2); - else - return transactionalContext.CompareKeyHashes(ref key1, ref key2); - } + => unifiedStoreTransactionalContext.CompareKeyHashes(ref key1, ref key2); } } \ No newline at end of file diff --git a/libs/server/Transaction/TxnKeyManager.cs b/libs/server/Transaction/TxnKeyManager.cs index f6f7803e806..178f44f0948 100644 --- a/libs/server/Transaction/TxnKeyManager.cs +++ b/libs/server/Transaction/TxnKeyManager.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System; using Garnet.common; using Tsavorite.core; @@ -13,12 +12,10 @@ sealed partial class TransactionManager /// Save key entry /// /// - /// /// - public void SaveKeyEntryToLock(PinnedSpanByte key, bool isObject, LockType type) + public void SaveKeyEntryToLock(PinnedSpanByte key, LockType type) { - UpdateTransactionStoreType(isObject ? StoreType.Object : StoreType.Main); - keyEntries.AddKey(key, isObject, type); + keyEntries.AddKey(key, type); } /// @@ -52,523 +49,35 @@ public unsafe void VerifyKeyOwnership(PinnedSpanByte key, LockType type) if (!respSession.clusterSession.NetworkIterativeSlotVerify(key, readOnly, respSession.SessionAsking)) { this.state = TxnState.Aborted; - return; - } - } - - /// - /// Returns a number of skipped args - /// - internal int GetKeys(RespCommand command, int inputCount, out ReadOnlySpan error) - { - error = CmdStrings.RESP_ERR_GENERIC_UNK_CMD; - return command switch - { - RespCommand.APPEND => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.BITCOUNT => SingleKey(StoreType.Main, LockType.Shared), - RespCommand.BITFIELD => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.BITFIELD_RO => SingleKey(StoreType.Main, LockType.Shared), - RespCommand.BITOP => SingleWriteKeyListReadKeys(inputCount, false, offset: 1), - RespCommand.BITPOS => SingleKey(StoreType.Main, LockType.Shared), - RespCommand.COSCAN => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.DECR => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.DECRBY => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.DEL => ListKeys(inputCount, StoreType.All, LockType.Exclusive), - RespCommand.DELIFGREATER => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.EXISTS => ListKeys(inputCount, StoreType.All, LockType.Shared), - RespCommand.EXPIRE => SingleKey(StoreType.All, LockType.Exclusive), - RespCommand.EXPIREAT => SingleKey(StoreType.All, LockType.Exclusive), - RespCommand.EXPIRETIME => SingleKey(StoreType.All, LockType.Shared), - RespCommand.GEOADD => SortedSetObjectKeys(command, inputCount), - RespCommand.GEODIST => SortedSetObjectKeys(command, inputCount), - RespCommand.GEOHASH => SortedSetObjectKeys(command, inputCount), - RespCommand.GEOPOS => SortedSetObjectKeys(command, inputCount), - RespCommand.GEORADIUS => SortedSetObjectKeys(command, inputCount), - RespCommand.GEORADIUS_RO => SortedSetObjectKeys(command, inputCount), - RespCommand.GEORADIUSBYMEMBER => SortedSetObjectKeys(command, inputCount), - RespCommand.GEORADIUSBYMEMBER_RO => SortedSetObjectKeys(command, inputCount), - RespCommand.GEOSEARCH => SortedSetObjectKeys(command, inputCount), - RespCommand.GEOSEARCHSTORE => SortedSetObjectKeys(command, inputCount), - RespCommand.GET => SingleKey(StoreType.Main, LockType.Shared), - RespCommand.GETBIT => SingleKey(StoreType.Main, LockType.Shared), - RespCommand.GETDEL => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.GETEX => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.GETIFNOTMATCH => SingleKey(StoreType.Main, LockType.Shared), - RespCommand.GETRANGE => SingleKey(StoreType.Main, LockType.Shared), - RespCommand.GETSET => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.GETWITHETAG => SingleKey(StoreType.Main, LockType.Shared), - RespCommand.HDEL => HashObjectKeys(command), - RespCommand.HEXISTS => HashObjectKeys(command), - RespCommand.HEXPIRE => HashObjectKeys(command), - RespCommand.HEXPIREAT => HashObjectKeys(command), - RespCommand.HEXPIRETIME => HashObjectKeys(command), - RespCommand.HGET => HashObjectKeys(command), - RespCommand.HGETALL => HashObjectKeys(command), - RespCommand.HINCRBY => HashObjectKeys(command), - RespCommand.HINCRBYFLOAT => HashObjectKeys(command), - RespCommand.HKEYS => HashObjectKeys(command), - RespCommand.HLEN => HashObjectKeys(command), - RespCommand.HMGET => HashObjectKeys(command), - RespCommand.HMSET => HashObjectKeys(command), - RespCommand.HPERSIST => HashObjectKeys(command), - RespCommand.HPEXPIRE => HashObjectKeys(command), - RespCommand.HPEXPIREAT => HashObjectKeys(command), - RespCommand.HPEXPIRETIME => HashObjectKeys(command), - RespCommand.HPTTL => HashObjectKeys(command), - RespCommand.HRANDFIELD => HashObjectKeys(command), - RespCommand.HSCAN => HashObjectKeys(command), - RespCommand.HSET => HashObjectKeys(command), - RespCommand.HSETNX => HashObjectKeys(command), - RespCommand.HSTRLEN => HashObjectKeys(command), - RespCommand.HTTL => HashObjectKeys(command), - RespCommand.HVALS => HashObjectKeys(command), - RespCommand.INCR => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.INCRBY => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.INCRBYFLOAT => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.LCS => ListKeys(2, StoreType.Main, LockType.Shared), - RespCommand.LINDEX => ListObjectKeys(command), - RespCommand.LINSERT => ListObjectKeys(command), - RespCommand.LLEN => ListObjectKeys(command), - RespCommand.LMOVE => ListObjectKeys(command), - RespCommand.LMPOP => ListObjectKeys(command), - RespCommand.LPOP => ListObjectKeys(command), - RespCommand.LPOS => ListObjectKeys(command), - RespCommand.LPUSH => ListObjectKeys(command), - RespCommand.LPUSHX => ListObjectKeys(command), - RespCommand.LRANGE => ListObjectKeys(command), - RespCommand.LREM => ListObjectKeys(command), - RespCommand.LSET => ListObjectKeys(command), - RespCommand.LTRIM => ListObjectKeys(command), - RespCommand.MGET => ListKeys(inputCount, StoreType.Main, LockType.Shared), - RespCommand.MSET => MSETKeys(inputCount, LockType.Exclusive), - RespCommand.MSETNX => MSETKeys(inputCount, LockType.Exclusive), - RespCommand.PERSIST => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.PEXPIRE => SingleKey(StoreType.All, LockType.Exclusive), - RespCommand.PEXPIREAT => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.PEXPIRETIME => SingleKey(StoreType.All, LockType.Shared), - RespCommand.PFADD => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.PFCOUNT => ListKeys(inputCount, StoreType.Main, LockType.Shared), - RespCommand.PFMERGE => SingleWriteKeyListReadKeys(inputCount, false), - RespCommand.PSETEX => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.PTTL => SingleKey(StoreType.All, LockType.Shared), - RespCommand.RENAME => SingleKey(StoreType.All, LockType.Exclusive), - RespCommand.RENAMENX => SingleKey(StoreType.All, LockType.Exclusive), - RespCommand.RPOP => ListObjectKeys(command), - RespCommand.RPOPLPUSH => ListObjectKeys(command), - RespCommand.RPUSH => ListObjectKeys(command), - RespCommand.RPUSHX => ListObjectKeys(command), - RespCommand.SADD => SetObjectKeys(command, inputCount), - RespCommand.SCARD => SetObjectKeys(command, inputCount), - RespCommand.SDIFF => SetObjectKeys(command, inputCount), - RespCommand.SDIFFSTORE => SetObjectKeys(command, inputCount), - RespCommand.SET => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.SETBIT => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.SETEX => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.SETEXNX => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.SETEXXX => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.SETIFGREATER => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.SETIFMATCH => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.SETNX => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.SETRANGE => SingleKey(StoreType.Main, LockType.Exclusive), - RespCommand.SINTER => SetObjectKeys(command, inputCount), - RespCommand.SINTERCARD => SetObjectKeys(command, inputCount), - RespCommand.SINTERSTORE => SetObjectKeys(command, inputCount), - RespCommand.SISMEMBER => SetObjectKeys(command, inputCount), - RespCommand.SMEMBERS => SetObjectKeys(command, inputCount), - RespCommand.SMISMEMBER => SetObjectKeys(command, inputCount), - RespCommand.SMOVE => SetObjectKeys(command, inputCount), - RespCommand.SPOP => SetObjectKeys(command, inputCount), - RespCommand.SRANDMEMBER => SetObjectKeys(command, inputCount), - RespCommand.SREM => SetObjectKeys(command, inputCount), - RespCommand.SSCAN => SetObjectKeys(command, inputCount), - RespCommand.STRLEN => SingleKey(StoreType.Main, LockType.Shared), - RespCommand.SUBSTR => SingleKey(StoreType.Main, LockType.Shared), - RespCommand.SUNION => SetObjectKeys(command, inputCount), - RespCommand.SUNIONSTORE => SetObjectKeys(command, inputCount), - RespCommand.TTL => SingleKey(StoreType.All, LockType.Shared), - RespCommand.TYPE => SingleKey(StoreType.All, LockType.Shared), - RespCommand.UNLINK => ListKeys(inputCount, StoreType.All, LockType.Exclusive), - RespCommand.ZADD => SortedSetObjectKeys(command, inputCount), - RespCommand.ZCARD => SortedSetObjectKeys(command, inputCount), - RespCommand.ZCOUNT => SortedSetObjectKeys(command, inputCount), - RespCommand.ZDIFF => SortedSetObjectKeys(command, inputCount), - RespCommand.ZDIFFSTORE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZEXPIRE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZEXPIREAT => SortedSetObjectKeys(command, inputCount), - RespCommand.ZEXPIRETIME => SortedSetObjectKeys(command, inputCount), - RespCommand.ZINCRBY => SortedSetObjectKeys(command, inputCount), - RespCommand.ZINTER => SortedSetObjectKeys(command, inputCount), - RespCommand.ZINTERCARD => SortedSetObjectKeys(command, inputCount), - RespCommand.ZINTERSTORE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZLEXCOUNT => SortedSetObjectKeys(command, inputCount), - RespCommand.ZMPOP => SortedSetObjectKeys(command, inputCount), - RespCommand.ZMSCORE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZPERSIST => SortedSetObjectKeys(command, inputCount), - RespCommand.ZPEXPIRE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZPEXPIREAT => SortedSetObjectKeys(command, inputCount), - RespCommand.ZPEXPIRETIME => SortedSetObjectKeys(command, inputCount), - RespCommand.ZPOPMAX => SortedSetObjectKeys(command, inputCount), - RespCommand.ZPOPMIN => SortedSetObjectKeys(command, inputCount), - RespCommand.ZPTTL => SortedSetObjectKeys(command, inputCount), - RespCommand.ZRANDMEMBER => SortedSetObjectKeys(command, inputCount), - RespCommand.ZRANGE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZRANGEBYLEX => SortedSetObjectKeys(command, inputCount), - RespCommand.ZRANGEBYSCORE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZRANGESTORE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZRANK => SortedSetObjectKeys(command, inputCount), - RespCommand.ZREM => SortedSetObjectKeys(command, inputCount), - RespCommand.ZREMRANGEBYLEX => SortedSetObjectKeys(command, inputCount), - RespCommand.ZREMRANGEBYRANK => SortedSetObjectKeys(command, inputCount), - RespCommand.ZREMRANGEBYSCORE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZREVRANGE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZREVRANGEBYLEX => SortedSetObjectKeys(command, inputCount), - RespCommand.ZREVRANGEBYSCORE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZREVRANK => SortedSetObjectKeys(command, inputCount), - RespCommand.ZSCAN => SortedSetObjectKeys(command, inputCount), - RespCommand.ZSCORE => SortedSetObjectKeys(command, inputCount), - RespCommand.ZTTL => SortedSetObjectKeys(command, inputCount), - RespCommand.ZUNION => SortedSetObjectKeys(command, inputCount), - RespCommand.ZUNIONSTORE => SortedSetObjectKeys(command, inputCount), - _ => OtherCommands(command, out error) - }; - } - - private int OtherCommands(RespCommand command, out ReadOnlySpan error) - { - error = CmdStrings.RESP_ERR_GENERIC_UNK_CMD; - if (command == RespCommand.DEBUG) - { - if (respSession.CanRunDebug()) - return 1; - - error = System.Text.Encoding.ASCII.GetBytes(string.Format( - CmdStrings.GenericErrCommandDisallowedWithOption, RespCommand.DEBUG, "enable-debug-command")); - return -1; - } - - return command switch - { - RespCommand.CLIENT => 1, - RespCommand.CONFIG => 1, - RespCommand.ECHO => 1, - RespCommand.PING => 1, - RespCommand.PUBLISH => 1, - RespCommand.SELECT => 1, - RespCommand.SPUBLISH => 1, - RespCommand.SWAPDB => 1, - RespCommand.TIME => 1, - _ => -1 - }; - } - - private int GeoCommands(RespCommand command, int inputCount) - { - var idx = 0; - - // GEOSEARCHSTORE dest key.... - // While all other commands here start with GEOsomething key... - if (command == RespCommand.GEOSEARCHSTORE) - { - var destinationKey = respSession.parseState.GetArgSliceByRef(idx++); - SaveKeyEntryToLock(destinationKey, true, LockType.Exclusive); - SaveKeyArgSlice(destinationKey); - } - - // Either this is GEOSEARCHSTORE, and index 1 is sourcekey, or some other command and index 0 is sourcekey. - var key = respSession.parseState.GetArgSliceByRef(idx++); - SaveKeyEntryToLock(key, true, LockType.Shared); - SaveKeyArgSlice(key); - - switch (command) - { - case RespCommand.GEOSEARCH: - case RespCommand.GEORADIUS_RO: - case RespCommand.GEORADIUSBYMEMBER_RO: - return 1; - case RespCommand.GEOSEARCHSTORE: - return 2; - case RespCommand.GEORADIUS: - case RespCommand.GEORADIUSBYMEMBER: - // These commands may or may not store a result - for (var i = idx; i < inputCount - 1; i++) - { - var span = respSession.parseState.GetArgSliceByRef(i).ReadOnlySpan; - - if (span.EqualsUpperCaseSpanIgnoringCase(CmdStrings.STORE) || - span.EqualsUpperCaseSpanIgnoringCase(CmdStrings.STOREDIST)) - { - var destinationKey = respSession.parseState.GetArgSliceByRef(i + 1); - SaveKeyEntryToLock(destinationKey, true, LockType.Exclusive); - SaveKeyArgSlice(destinationKey); - break; - } - } - - return 1; - default: - // Should never reach here. - throw new NotSupportedException(); - } - } - - private int SortedSetObjectKeys(RespCommand command, int inputCount) - { - return command switch - { - RespCommand.GEOADD => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.GEODIST => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.GEOHASH => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.GEOPOS => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.GEORADIUS => GeoCommands(RespCommand.GEORADIUS, inputCount), - RespCommand.GEORADIUS_RO => GeoCommands(RespCommand.GEORADIUS_RO, inputCount), - RespCommand.GEORADIUSBYMEMBER => GeoCommands(RespCommand.GEORADIUSBYMEMBER, inputCount), - RespCommand.GEORADIUSBYMEMBER_RO => GeoCommands(RespCommand.GEORADIUSBYMEMBER_RO, inputCount), - RespCommand.GEOSEARCH => GeoCommands(RespCommand.GEOSEARCH, inputCount), - RespCommand.GEOSEARCHSTORE => GeoCommands(RespCommand.GEOSEARCHSTORE, inputCount), - RespCommand.ZADD => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZCARD => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZCOUNT => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZDIFF => ListReadKeysWithCount(LockType.Shared), - RespCommand.ZDIFFSTORE => SingleWriteKeyListReadKeysWithCount(inputCount), - RespCommand.ZEXPIRE => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZEXPIREAT => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZEXPIRETIME => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZINCRBY => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZINTER => ListReadKeysWithCount(LockType.Shared), - RespCommand.ZINTERCARD => ListReadKeysWithCount(LockType.Shared), - RespCommand.ZINTERSTORE => SingleWriteKeyListReadKeysWithCount(inputCount), - RespCommand.ZLEXCOUNT => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZMPOP => ListReadKeysWithCount(LockType.Exclusive), - RespCommand.ZMSCORE => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZPERSIST => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZPEXPIRE => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZPEXPIREAT => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZPEXPIRETIME => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZPOPMAX => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZPOPMIN => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZPTTL => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZRANDMEMBER => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZRANGE => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZRANGEBYLEX => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZRANGEBYSCORE => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZRANGESTORE => SingleWriteKeyListReadKeys(2), - RespCommand.ZRANK => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZREM => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZREMRANGEBYLEX => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZREMRANGEBYRANK => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZREMRANGEBYSCORE => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.ZREVRANGE => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZREVRANGEBYLEX => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZREVRANGEBYSCORE => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZREVRANK => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZSCAN => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZSCORE => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZTTL => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.ZUNION => ListReadKeysWithCount(LockType.Shared), - RespCommand.ZUNIONSTORE => SingleWriteKeyListReadKeysWithCount(inputCount), - _ => -1 - }; - } - - private int ListObjectKeys(RespCommand command) - { - return command switch - { - RespCommand.LINDEX => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.LINSERT => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.LLEN => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.LMOVE => ListKeys(2, StoreType.Object, LockType.Exclusive), - RespCommand.LMPOP => ListReadKeysWithCount(LockType.Exclusive), - RespCommand.LPOP => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.LPOS => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.LPUSH => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.LPUSHX => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.LRANGE => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.LREM => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.LSET => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.LTRIM => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.RPOP => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.RPOPLPUSH => ListKeys(2, StoreType.Object, LockType.Exclusive), - RespCommand.RPUSH => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.RPUSHX => SingleKey(StoreType.Object, LockType.Exclusive), - _ => -1 - }; - } - - private int HashObjectKeys(RespCommand command) - { - return command switch - { - RespCommand.HDEL => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.HEXISTS => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HEXPIRE => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.HEXPIREAT => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.HEXPIRETIME => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HGET => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HGETALL => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HINCRBY => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.HINCRBYFLOAT => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.HKEYS => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HLEN => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HMGET => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HMSET => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.HPERSIST => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.HPEXPIRE => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.HPEXPIREAT => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.HPEXPIRETIME => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HPTTL => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HRANDFIELD => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HSCAN => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HSET => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.HSETNX => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.HSTRLEN => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HTTL => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.HVALS => SingleKey(StoreType.Object, LockType.Shared), - _ => -1 - }; - } - - private int SetObjectKeys(RespCommand command, int inputCount) - { - return command switch - { - RespCommand.SADD => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.SCARD => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.SDIFF => ListKeys(inputCount, StoreType.Object, LockType.Shared), - RespCommand.SDIFFSTORE => SingleWriteKeyListReadKeys(inputCount), - RespCommand.SINTER => ListKeys(inputCount, StoreType.Object, LockType.Shared), - RespCommand.SINTERCARD => ListReadKeysWithCount(LockType.Shared), - RespCommand.SINTERSTORE => SingleWriteKeyListReadKeys(inputCount), - RespCommand.SISMEMBER => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.SMEMBERS => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.SMISMEMBER => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.SMOVE => ListKeys(2, StoreType.Object, LockType.Exclusive), - RespCommand.SPOP => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.SRANDMEMBER => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.SREM => SingleKey(StoreType.Object, LockType.Exclusive), - RespCommand.SSCAN => SingleKey(StoreType.Object, LockType.Shared), - RespCommand.SUNION => ListKeys(inputCount, StoreType.Object, LockType.Shared), - RespCommand.SUNIONSTORE => SingleWriteKeyListReadKeys(inputCount), - _ => -1 - }; - } - - /// - /// Returns a single for commands that have a single key - /// - private int SingleKey(StoreType storeType, LockType type) - { - var key = respSession.parseState.GetArgSliceByRef(0); - if (storeType is StoreType.Main or StoreType.All) - SaveKeyEntryToLock(key, false, type); - if (storeType is StoreType.Object or StoreType.All && !objectStoreBasicContext.IsNull) - SaveKeyEntryToLock(key, true, type); - SaveKeyArgSlice(key); - return 1; - } - - /// - /// Returns a list of keys for commands: MGET, DEL, UNLINK - /// - private int ListKeys(int inputCount, StoreType storeType, LockType type) - { - for (var i = 0; i < inputCount; i++) - { - var key = respSession.parseState.GetArgSliceByRef(i); - if (storeType is StoreType.Main or StoreType.All) - SaveKeyEntryToLock(key, false, type); - if (storeType is StoreType.Object or StoreType.All && !objectStoreBasicContext.IsNull) - SaveKeyEntryToLock(key, true, type); - SaveKeyArgSlice(key); - } - return inputCount; - } - - /// - /// Returns a list of keys for LMPOP command - /// - private int ListReadKeysWithCount(LockType type, bool isObject = true) - { - if (respSession.parseState.Count == 0) - return -2; - - if (!respSession.parseState.TryGetInt(0, out var numKeys)) - return -2; - - if (numKeys + 1 > respSession.parseState.Count) - return -2; - - for (var i = 1; i < numKeys + 1; i++) - { - var key = respSession.parseState.GetArgSliceByRef(i); - SaveKeyEntryToLock(key, isObject, type); - SaveKeyArgSlice(key); } - return numKeys; } /// - /// Returns a list of keys for MSET commands + /// Locks keys according to command's key specifications /// - private int MSETKeys(int inputCount, LockType type, bool isObject = true) + /// Simplified command info + internal void LockKeys(SimpleRespCommandInfo cmdInfo) { - for (var i = 0; i < inputCount; i += 2) - { - var key = respSession.parseState.GetArgSliceByRef(i); - SaveKeyEntryToLock(key, isObject, type); - SaveKeyArgSlice(key); - } - return inputCount; - } - - /// - /// Returns a list of keys for set *STORE commands (e.g. SUNIONSTORE, SINTERSTORE etc.) - /// Where the first key's value is written to and the rest of the keys' values are read from. - /// - private int SingleWriteKeyListReadKeys(int inputCount, bool isObject = true, int offset = 0) - { - if (inputCount <= offset) - return 0; - - var key = respSession.parseState.GetArgSliceByRef(offset); - SaveKeyEntryToLock(key, isObject, LockType.Exclusive); - SaveKeyArgSlice(key); - - for (var i = offset + 1; i < inputCount; i++) - { - key = respSession.parseState.GetArgSliceByRef(i); - SaveKeyEntryToLock(key, isObject, LockType.Shared); - SaveKeyArgSlice(key); - } + if (cmdInfo.KeySpecs == null || cmdInfo.KeySpecs.Length == 0) + return; - return inputCount; - } + AddTransactionStoreType(cmdInfo.StoreType); - /// - /// Returns a list of keys for complex *STORE commands (e.g. ZUNIONSTORE, ZINTERSTORE etc.) - /// Where the first key's value is written to and the rest of the keys' values are read from. - /// We can get number of read keys by checking the second argument. - /// - private int SingleWriteKeyListReadKeysWithCount(int inputCount, bool isObject = true) - { - if (inputCount > 0) + foreach (var keySpec in cmdInfo.KeySpecs) { - var key = respSession.parseState.GetArgSliceByRef(0); - SaveKeyEntryToLock(key, isObject, LockType.Exclusive); - SaveKeyArgSlice(key); - } + if (!respSession.parseState.TryGetKeySearchArgsFromSimpleKeySpec(keySpec, cmdInfo.IsSubCommand, out var searchArgs)) + continue; - if ((inputCount < 2) || !respSession.parseState.TryGetInt(1, out var numKeysArg)) - return -2; + var isReadOnly = (keySpec.Flags & KeySpecificationFlags.RO) == KeySpecificationFlags.RO; + var lockType = isReadOnly ? LockType.Shared : LockType.Exclusive; - for (var i = 2; i < inputCount && i < numKeysArg + 2; i++) - { - var key = respSession.parseState.GetArgSliceByRef(i); - SaveKeyEntryToLock(key, isObject, LockType.Shared); - SaveKeyArgSlice(key); + for (var currIdx = searchArgs.firstIdx; currIdx <= searchArgs.lastIdx; currIdx += searchArgs.step) + { + var key = respSession.parseState.GetArgSliceByRef(currIdx); + SaveKeyEntryToLock(key, lockType); + SaveKeyArgSlice(key); + } } - - return inputCount; } } } \ No newline at end of file diff --git a/libs/server/Transaction/TxnRespCommands.cs b/libs/server/Transaction/TxnRespCommands.cs index e3cbb809b11..6dd165336d3 100644 --- a/libs/server/Transaction/TxnRespCommands.cs +++ b/libs/server/Transaction/TxnRespCommands.cs @@ -167,23 +167,18 @@ private bool NetworkSKIP(RespCommand cmd) } } - // Get and add keys to txn key list - int skipped = txnManager.GetKeys(cmd, count, out ReadOnlySpan error); - - if (skipped < 0) + if (cmd == RespCommand.DEBUG && !CanRunDebug()) { - // We ran out of data in network buffer, let caller handler it - if (skipped == -2) return false; - - // We found an unsupported command, abort - while (!RespWriteUtils.TryWriteError(error, ref dcurr, dend)) + while (!RespWriteUtils.TryWriteError(System.Text.Encoding.ASCII.GetBytes(string.Format( + CmdStrings.GenericErrCommandDisallowedWithOption, RespCommand.DEBUG, "enable-debug-command")), ref dcurr, dend)) SendAndReset(); txnManager.Abort(); - return true; } + txnManager.LockKeys(commandInfo); + while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_QUEUED, ref dcurr, dend)) SendAndReset(); @@ -228,9 +223,11 @@ private bool CommonWATCH(StoreType type) keys.Add(nextKey); } + txnManager.AddTransactionStoreType(type); + foreach (var toWatch in keys) { - txnManager.Watch(toWatch, type); + txnManager.Watch(toWatch); } while (!RespWriteUtils.TryWriteDirect(CmdStrings.RESP_OK, ref dcurr, dend)) diff --git a/libs/server/Transaction/TxnWatchedKeysContainer.cs b/libs/server/Transaction/TxnWatchedKeysContainer.cs index 0887e3a9339..4d588c0e46f 100644 --- a/libs/server/Transaction/TxnWatchedKeysContainer.cs +++ b/libs/server/Transaction/TxnWatchedKeysContainer.cs @@ -55,14 +55,14 @@ public bool RemoveWatch(PinnedSpanByte key) { if (key.ReadOnlySpan.SequenceEqual(keySlices[i].slice.ReadOnlySpan)) { - keySlices[i].type = 0; + keySlices[i].isWatched = false; return true; } } return false; } - public void AddWatch(PinnedSpanByte key, StoreType type) + public void AddWatch(PinnedSpanByte key) { if (sliceCount >= sliceBufferSize) { @@ -96,7 +96,7 @@ public void AddWatch(PinnedSpanByte key, StoreType type) key.ReadOnlySpan.CopyTo(slice.Span); keySlices[sliceCount].slice = slice; - keySlices[sliceCount].type = type; + keySlices[sliceCount].isWatched = true; keySlices[sliceCount].hash = Utility.HashBytes(slice.ReadOnlySpan); keySlices[sliceCount].version = versionMap.ReadVersion(keySlices[sliceCount].hash); @@ -114,7 +114,7 @@ public bool ValidateWatchVersion() for (int i = 0; i < sliceCount; i++) { WatchedKeySlice key = keySlices[i]; - if (key.type == 0) continue; + if (!key.isWatched) continue; if (versionMap.ReadVersion(key.hash) != key.version) return false; } @@ -126,13 +126,10 @@ public bool SaveKeysToLock(TransactionManager txnManager) for (int i = 0; i < sliceCount; i++) { WatchedKeySlice watchedKeySlice = keySlices[i]; - if (watchedKeySlice.type == 0) continue; + if (!watchedKeySlice.isWatched) continue; var slice = keySlices[i].slice; - if (watchedKeySlice.type == StoreType.Main || watchedKeySlice.type == StoreType.All) - txnManager.SaveKeyEntryToLock(slice, false, LockType.Shared); - if (watchedKeySlice.type == StoreType.Object || watchedKeySlice.type == StoreType.All) - txnManager.SaveKeyEntryToLock(slice, true, LockType.Shared); + txnManager.SaveKeyEntryToLock(slice, LockType.Shared); } return true; } diff --git a/libs/server/Transaction/WatchedKeySlice.cs b/libs/server/Transaction/WatchedKeySlice.cs index 42c393b2c96..640a191c4a1 100644 --- a/libs/server/Transaction/WatchedKeySlice.cs +++ b/libs/server/Transaction/WatchedKeySlice.cs @@ -19,6 +19,6 @@ struct WatchedKeySlice public long hash; [FieldOffset(28)] - public StoreType type; + public bool isWatched; } } \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/benchmark/BDN-Tsavorite.Benchmark/InliningTests.cs b/libs/storage/Tsavorite/cs/benchmark/BDN-Tsavorite.Benchmark/InliningTests.cs index da27b144fb0..89f37cf740d 100644 --- a/libs/storage/Tsavorite/cs/benchmark/BDN-Tsavorite.Benchmark/InliningTests.cs +++ b/libs/storage/Tsavorite/cs/benchmark/BDN-Tsavorite.Benchmark/InliningTests.cs @@ -39,7 +39,7 @@ void SetupStore() { IndexSize = 1L << 26, LogDevice = logDevice - }, StoreFunctions.Create() + }, StoreFunctions.Create(new SpanByteComparer(), new SpanByteRecordDisposer()) , (allocatorSettings, storeFunctions) => new(allocatorSettings, storeFunctions) ); } diff --git a/libs/storage/Tsavorite/cs/benchmark/BDN-Tsavorite.Benchmark/IterationTests.cs b/libs/storage/Tsavorite/cs/benchmark/BDN-Tsavorite.Benchmark/IterationTests.cs index 5ea43f82a6a..c8b4f85d8b3 100644 --- a/libs/storage/Tsavorite/cs/benchmark/BDN-Tsavorite.Benchmark/IterationTests.cs +++ b/libs/storage/Tsavorite/cs/benchmark/BDN-Tsavorite.Benchmark/IterationTests.cs @@ -37,7 +37,7 @@ void SetupStore() { IndexSize = 1L << 26, LogDevice = logDevice - }, StoreFunctions.Create() + }, StoreFunctions.Create(new SpanByteComparer(), new SpanByteRecordDisposer()) , (allocatorSettings, storeFunctions) => new(allocatorSettings, storeFunctions) ); } diff --git a/libs/storage/Tsavorite/cs/benchmark/YCSB.benchmark/SpanByteYcsbBenchmark.cs b/libs/storage/Tsavorite/cs/benchmark/YCSB.benchmark/SpanByteYcsbBenchmark.cs index 4cd5640ce27..67beb353d9e 100644 --- a/libs/storage/Tsavorite/cs/benchmark/YCSB.benchmark/SpanByteYcsbBenchmark.cs +++ b/libs/storage/Tsavorite/cs/benchmark/YCSB.benchmark/SpanByteYcsbBenchmark.cs @@ -111,7 +111,7 @@ internal SpanByteYcsbBenchmark(KeySpanByte[] i_keys_, KeySpanByte[] t_keys_, Tes } store = new(kvSettings - , StoreFunctions.Create() + , StoreFunctions.Create(new SpanByteComparer(), new SpanByteRecordDisposer()) , (allocatorSettings, storeFunctions) => new(allocatorSettings, storeFunctions) ); } diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs index 443336520e7..2ffdc35a93d 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs @@ -17,10 +17,24 @@ namespace Tsavorite.core using static LogAddress; using static VarbyteLengthUtility; + /// + /// Type-free base class for hybrid log memory allocator. Contains utility methods that do not need type args and are not performance-critical + /// so can be virtual. + /// + public abstract class AllocatorBase + { + /// Create the circular buffers for flushing to device. Only implemented by ObjectAllocator. + internal virtual CircularDiskWriteBuffer CreateCircularFlushBuffers(IDevice objectLogDevice, ILogger logger) => default; + /// Create the circular flush buffers for object dexerialization from device. Only implemented by ObjectAllocator. + internal virtual CircularDiskReadBuffer CreateCircularReadBuffers(IDevice objectLogDevice, ILogger logger) => default; + /// Create the circular flush buffers for object dexerialization from device. Only implemented by ObjectAllocator. + internal virtual CircularDiskReadBuffer CreateCircularReadBuffers() => default; + } + /// /// Base class for hybrid log memory allocator. Contains utility methods, some of which are not performance-critical so can be virtual. /// - public abstract unsafe partial class AllocatorBase : IDisposable + public abstract unsafe partial class AllocatorBase : AllocatorBase, IDisposable where TStoreFunctions : IStoreFunctions where TAllocator : IAllocator { @@ -41,6 +55,10 @@ public abstract unsafe partial class AllocatorBase /// Sometimes it's useful to know this explicitly rather than rely on method overrides etc. internal bool IsObjectAllocator => transientObjectIdMap is not null; + /// If true, then this allocator has as the first bytes on a page, so allocating a logical address + /// in must skip these bytes. + internal int pageHeaderSize; + #region Protected size definitions /// Buffer size internal readonly int BufferSize; @@ -1057,12 +1075,12 @@ long HandlePageOverflow(ref PageOffset localTailPageOffset, int numSlots) // Set up the TailPageOffset to account for the page header and then this allocation. localTailPageOffset.Page++; - localTailPageOffset.Offset = PageHeader.Size + numSlots; + localTailPageOffset.Offset = numSlots + pageHeaderSize; TailPageOffset = localTailPageOffset; // At this point the slot is allocated and we are not allowed to refresh epochs any longer. // Return the first logical address after the page header. - return GetFirstValidLogicalAddressOnPage(localTailPageOffset.Page); + return GetLogicalAddressOfStartOfPage(localTailPageOffset.Page) + pageHeaderSize; // Same as GetFirstValidLogicalAddressOnPage(localTailPageOffset.Page) but faster } /// Try allocate, no thread spinning allowed @@ -1595,11 +1613,6 @@ private void AsyncReadPagesForRecovery(CircularDiskReadBuffer readBuff } } - /// Create the circular buffers for flushing to device. Only implemented by ObjectAllocator. - internal virtual CircularDiskWriteBuffer CreateCircularFlushBuffers(IDevice objectLogDevice, ILogger logger) => default; - /// Create the circular flush buffers for object dexerialization from device. Only implemented by ObjectAllocator. - internal virtual CircularDiskReadBuffer CreateCircularReadBuffers(IDevice objectLogDevice, ILogger logger) => default; - /// /// Flush page range to disk /// Called when all threads have agreed that a page range is sealed. diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorScan.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorScan.cs index a7b72535dee..a6cba4cfc6e 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorScan.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorScan.cs @@ -74,7 +74,7 @@ internal bool PushScanImpl(long beginAddress, lon continue; // Pull Iter records are in temp storage so do not need locks. - stop = !scanFunctions.Reader(in iter, new RecordMetadata(iter.CurrentAddress), numRecords, out _); + stop = !scanFunctions.Reader(in iter, new RecordMetadata(iter.CurrentAddress, iter.ETag), numRecords, out _); } catch (Exception ex) { @@ -109,7 +109,7 @@ internal bool IterateHashChain(TsavoriteKV= readOnlyAddress && !logRecord.Info.IsClosed) store.LockForScan(ref stackCtx, key); - stop = !scanFunctions.Reader(in logRecord, new RecordMetadata(iter.CurrentAddress), numRecords, out _); + stop = !scanFunctions.Reader(in logRecord, new RecordMetadata(iter.CurrentAddress, iter.ETag), numRecords, out _); } catch (Exception ex) { @@ -168,7 +168,7 @@ internal unsafe bool GetFromDiskAndPushToReader(ReadOnlySpan(adjustedSize, pinned:true); + var tmp = GC.AllocateArray(adjustedSize, pinned: true); var p = (long)Unsafe.AsPointer(ref tmp[0]); pointers[index] = RoundUp(p, sectorSize); frame[index] = tmp; diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/DiskLogRecord.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/DiskLogRecord.cs index ff0bcb0e6bc..f5244d8e611 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/DiskLogRecord.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/DiskLogRecord.cs @@ -209,7 +209,7 @@ public OverflowByteArray ValueOverflow public readonly IHeapObject ValueObject => logRecord.ValueObject; /// - public readonly long ETag => logRecord.ETag; + public readonly long ETag => logRecord.IsSet ? logRecord.ETag : LogRecord.NoETag; /// public readonly long Expiration => logRecord.Expiration; diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/HeapObjectBase.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/HeapObjectBase.cs index 77da9190ce9..4b7be30db9a 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/HeapObjectBase.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/HeapObjectBase.cs @@ -180,4 +180,4 @@ public void ClearSerializedObjectData() SerializationPhase = SerializationPhase.REST; // Reset to initial state } } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/IAllocator.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/IAllocator.cs index c3d9dfbd4b9..df2d7d2d5c9 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/IAllocator.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/IAllocator.cs @@ -71,7 +71,7 @@ RecordSizeInfo GetUpsertRecordSizeReturn the for transient log records (e.g. iterator) - ObjectIdMap TranssientObjectIdMap { get; } + ObjectIdMap TransientObjectIdMap { get; } /// Dispose an in-memory log record void DisposeRecord(ref LogRecord logRecord, DisposeReason disposeReason); diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/LogRecord.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/LogRecord.cs index 9b60cd463ca..68de84a4fa1 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/LogRecord.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/LogRecord.cs @@ -161,7 +161,6 @@ public readonly OverflowByteArray KeyOverflow } set { - var (length, dataAddress) = GetKeyFieldInfo(IndicatorAddress); if (!Info.KeyIsOverflow || length != ObjectIdMap.ObjectIdSize) throw new TsavoriteException("set_KeyOverflow should only be called when transferring into a new record with KeyIsInline==false and key.Length==ObjectIdSize"); @@ -175,7 +174,8 @@ public readonly Span ValueSpan [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - Debug.Assert(!Info.ValueIsObject, "ValueSpan is not valid for Object values"); + if (Info.ValueIsObject) + throw new TsavoriteException("ValueSpan is not valid for Object values"); var (length, dataAddress) = GetValueFieldInfo(IndicatorAddress); return Info.ValueIsInline ? new((byte*)dataAddress, (int)length) : objectIdMap.GetOverflowByteArray(*(int*)dataAddress).Span; } @@ -187,9 +187,8 @@ public readonly IHeapObject ValueObject [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - Debug.Assert(Info.ValueIsObject, "ValueObject is not valid for Span values"); if (!Info.ValueIsObject) - return default; + throw new TsavoriteException("ValueObject is not valid for Span values"); var (length, dataAddress) = GetValueFieldInfo(IndicatorAddress); return objectIdMap.GetHeapObject(*(int*)dataAddress); } @@ -426,10 +425,12 @@ private readonly bool TrySetValueLength(in RecordSizeInfo sizeInfo, bool zeroIni if (sizeInfo.ValueLengthBytes > valueLengthBytes) return false; - // Growth and fillerLen may be negative if shrinking. + // inlineValueGrowth and fillerLen may be negative if shrinking value or converting to Overflow/Object. + // ETag and Expiration won't change, but optionalGrowth may be positive or negative if adding or removing ObjectLogPosition. var inlineValueGrowth = (int)(newInlineValueSize - oldInlineValueSize); var oldOptionalSize = OptionalLength; var newOptionalSize = sizeInfo.OptionalSize; + var optionalGrowth = newOptionalSize - oldOptionalSize; var optionalStartAddress = valueAddress + oldInlineValueSize; var fillerLenAddress = optionalStartAddress + oldOptionalSize; @@ -440,7 +441,7 @@ private readonly bool TrySetValueLength(in RecordSizeInfo sizeInfo, bool zeroIni // new value (including whether it is overflow) and the existing optionals, and success is based on whether that can fit into the allocated // record space. We do not change the presence of optionals h ere; we just ensure there is enough for the larger of (current optionals, // new optionals) and a later operation will actually read/update the optional(s), including setting/clearing the flag(s). - if (fillerLen < inlineValueGrowth + (newOptionalSize - oldOptionalSize)) + if (fillerLen < inlineValueGrowth + optionalGrowth) return false; // Update record part 1: Set varbyte value length to the full length of the value, including filler but NOT optionalSize (which is calculated @@ -524,9 +525,9 @@ private readonly bool TrySetValueLength(in RecordSizeInfo sizeInfo, bool zeroIni // can't change both RecordInfo and the varbyte word atomically. Therefore we must zeroinit from the end of the current "filler // space" (either the address, if it was within the less-than-FillerLengthSize bytes at the end of the record, or the end of the // int value) if we have shrunk the value. - fillerLen -= inlineValueGrowth; // optional data is unchanged even if newOptionalSize != oldOptionalSize + fillerLen -= inlineValueGrowth + optionalGrowth; var hasFillerBit = 0L; - var newFillerLenAddress = optionalStartAddress + oldOptionalSize; // optional data is unchanged even if newOptionalSize != oldOptionalSize + var newFillerLenAddress = optionalStartAddress + newOptionalSize; var endOfNewFillerSpace = newFillerLenAddress; if (fillerLen >= FillerLengthSize) { @@ -622,6 +623,21 @@ public readonly (int actualSize, int allocatedSize) GetInlineRecordSizes() return (actualSize, actualSize + GetFillerLength()); } + /// A tuple of the total size of the main-log (inline) portion of the record when it still has the object-length encoding + /// which leaves the metadata's valueLength incorrect. The tuple is with and without filler length. + public readonly (int actualSize, int allocatedSize) GetInlineRecordSizesWithUnreadObjects() + { + if (Info.IsNull) + return (RecordInfo.Size, RecordInfo.Size); + + // Calculate this directly due to the unread objects. + var (length, dataAddress) = GetValueFieldInfoWithUnreadObjects(IndicatorAddress); + var actualSize = (int)(dataAddress - physicalAddress + length + OptionalLength); + + // Pass the calculated size here due ot unread objects. + return (actualSize, actualSize + GetFillerLength(physicalAddress + actualSize)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal readonly long GetOptionalStartAddress() { @@ -1197,8 +1213,19 @@ public override readonly string ToString() if (physicalAddress == 0) return ""; static string bstr(bool value) => value ? "T" : "F"; - var valueString = Info.ValueIsObject ? $"obj:{ValueObject}" : ValueSpan.ToShortString(20); - return $"ri {Info} | key {Key.ToShortString(20)} | val {valueString} | HasETag {bstr(Info.HasETag)}:{ETag} | HasExpiration {bstr(Info.HasExpiration)}:{Expiration}"; + + string keyString, valueString; + try { keyString = SpanByte.ToShortString(Key); } + catch (Exception ex) { keyString = $""; } + try { valueString = Info.ValueIsObject ? $"obj:{ValueObject}" : ValueSpan.ToShortString(20); } + catch (Exception ex) { valueString = $""; } + + var (keyLengthBytes, valueLengthBytes, hasFillerBit) = DeconstructIndicatorByte(*(byte*)IndicatorAddress); + var (keyLength, keyAddress) = GetKeyFieldInfo(IndicatorAddress); + var (valueLength, valueAddress) = GetValueFieldInfo(IndicatorAddress); + return $"ri {Info} | key ({keyLengthBytes}/{keyLength}/{keyAddress - physicalAddress}) {keyString}" + + $" | val ({valueLengthBytes}/{valueLength}/{valueAddress - physicalAddress}) {valueString}" + + $" | HasETag {bstr(Info.HasETag)}:{ETag} | HasExpir {bstr(Info.HasExpiration)}:{Expiration}"; } } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectAllocator.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectAllocator.cs index ce1b638d48b..b970330ee78 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectAllocator.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectAllocator.cs @@ -109,7 +109,7 @@ public readonly RecordSizeInfo GetUpsertRecordSize _this.CreateRemappedLogRecordOverTransientMemory(logicalAddress, physicalAddress); /// - public readonly ObjectIdMap TranssientObjectIdMap => _this.transientObjectIdMap; + public readonly ObjectIdMap TransientObjectIdMap => _this.transientObjectIdMap; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectAllocatorImpl.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectAllocatorImpl.cs index 59824f58acf..ec0fe50e87f 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectAllocatorImpl.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectAllocatorImpl.cs @@ -58,6 +58,7 @@ public ObjectAllocatorImpl(AllocatorSettings settings, TStoreFunctions storeFunc maxInlineValueSize = 1 << settings.LogSettings.MaxInlineValueSizeBits; freePagePool = new OverflowPool>(4, static p => { }); + pageHeaderSize = PageHeader.Size; if (settings.LogSettings.NumberOfFlushBuffers < LogSettings.kMinFlushBuffers || settings.LogSettings.NumberOfFlushBuffers > LogSettings.kMaxFlushBuffers || !IsPowerOfTwo(settings.LogSettings.NumberOfFlushBuffers)) throw new TsavoriteException($"{nameof(settings.LogSettings.NumberOfFlushBuffers)} must be between {LogSettings.kMinFlushBuffers} and {LogSettings.kMaxFlushBuffers - 1} and a power of 2"); @@ -325,13 +326,18 @@ internal void FreePage(long page) /// Create the flush buffer (for only) internal override CircularDiskWriteBuffer CreateCircularFlushBuffers(IDevice objectLogDevice, ILogger logger) - => new(bufferPool, IStreamBuffer.BufferSize, numberOfFlushBuffers, objectLogDevice ?? this.objectLogDevice, logger); + { + var localObjectLogDevice = objectLogDevice ?? this.objectLogDevice; + return localObjectLogDevice is not null + ? new(bufferPool, IStreamBuffer.BufferSize, numberOfFlushBuffers, localObjectLogDevice, logger) + : null; + } /// Create the flush buffer (for only) internal override CircularDiskReadBuffer CreateCircularReadBuffers(IDevice objectLogDevice, ILogger logger) => new(bufferPool, IStreamBuffer.BufferSize, numberOfDeserializationBuffers, objectLogDevice ?? this.objectLogDevice, logger); - private CircularDiskReadBuffer CreateCircularReadBuffers() + internal override CircularDiskReadBuffer CreateCircularReadBuffers() => new(bufferPool, IStreamBuffer.BufferSize, numberOfDeserializationBuffers, objectLogDevice, logger); private void WriteAsync(CircularDiskWriteBuffer flushBuffers, long flushPage, ulong alignedMainLogFlushPageAddress, uint numBytesToWrite, @@ -341,12 +347,20 @@ private void WriteAsync(CircularDiskWriteBuffer flushBuffers, long flu // We flush within the DiskStreamWriteBuffer, so we do not use the asyncResult here for IO (until the final callback), but it has necessary fields. // Short circuit if we are using a null device - if ((device as NullDevice) != null) + if (device is NullDevice) { device.WriteAsync(IntPtr.Zero, 0, 0, numBytesToWrite, callback, asyncResult); return; } + // Short circuit if we are not using flushBuffers (e.g. using ObjectAllocator for string-only purposes). + if (flushBuffers is null) + { + WriteInlinePageAsync((IntPtr)pagePointers[flushPage % BufferSize], (ulong)(AlignedPageSizeBytes * flushPage), + (uint)AlignedPageSizeBytes, callback, asyncResult, device); + return; + } + Debug.Assert(asyncResult.page == flushPage, $"asyncResult.page {asyncResult.page} should equal flushPage {flushPage}"); var allocatorPage = pages[flushPage % BufferSize]; @@ -423,8 +437,12 @@ private void WriteAsync(CircularDiskWriteBuffer flushBuffers, long flu srcBuffer.available_bytes = (int)numBytesToWrite + startPadding; // Overflow Keys and Values are written to, and Object values are serialized to, this Stream. - var logWriter = new ObjectLogWriter(device, flushBuffers, storeFunctions); - _ = logWriter.OnBeginPartialFlush(objectLogNextRecordStartPosition); + ObjectLogWriter logWriter = null; + if (flushBuffers is not null) + { + logWriter = new(device, flushBuffers, storeFunctions); + _ = logWriter.OnBeginPartialFlush(objectLogNextRecordStartPosition); + } // Include page header when calculating end address. var endPhysicalAddress = (long)srcBuffer.GetValidPointer() + startPadding + numBytesToWrite; @@ -443,7 +461,8 @@ private void WriteAsync(CircularDiskWriteBuffer flushBuffers, long flu // Do not write v+1 records (e.g. during a checkpoint) if (logicalAddress < fuzzyStartLogicalAddress || !logRecord.Info.IsInNewVersion) { - // Do not write objects for fully-inline records + // Do not write objects for fully-inline records. This should always be false if we don't have a logWriter (i.e. no flushBuffers), + // which would be the case where we were created to be used for inline string records only. if (logRecord.Info.RecordHasObjects) { var recordStartPosition = logWriter.GetNextRecordStartPosition(); @@ -476,10 +495,13 @@ private void WriteAsync(CircularDiskWriteBuffer flushBuffers, long flu numBytesToWrite = (uint)(aligned_end - alignedStartOffset); } - // Finally write the main log page as part of OnPartialFlushComplete. + // Finally write the main log page as part of OnPartialFlushComplete, or directly if we had no flushBuffers. // TODO: This will potentially overwrite partial sectors if this is a partial flush; a workaround would be difficult. - logWriter.OnPartialFlushComplete(srcBuffer.GetValidPointer(), alignedBufferSize, device, alignedMainLogFlushPageAddress + (uint)alignedStartOffset, - callback, asyncResult, out objectLogNextRecordStartPosition); + if (logWriter is not null) + logWriter.OnPartialFlushComplete(srcBuffer.GetValidPointer(), alignedBufferSize, device, alignedMainLogFlushPageAddress + (uint)alignedStartOffset, + callback, asyncResult, out objectLogNextRecordStartPosition); + else + device.WriteAsync((IntPtr)srcBuffer.GetValidPointer(), alignedMainLogFlushPageAddress + (uint)alignedStartOffset, (uint)alignedBufferSize, callback, asyncResult); } finally { @@ -554,43 +576,43 @@ private void AsyncReadPageWithObjectsCallback(uint errorCode, uint num var result = (PageAsyncReadResult)context; var pageStartAddress = (long)result.destinationPtr; - result.maxPtr = numBytes; + if (numBytes < result.maxPtr) + result.maxPtr = numBytes; // Iterate all records in range to determine how many bytes we need to read from objlog. - ObjectLogFilePositionInfo startPosition = new(); + ObjectLogFilePositionInfo startPosition = new(), endPosition = new(); + int endKeyLength = 0; + ulong endValueLength = 0; ulong totalBytesToRead = 0; var recordAddress = pageStartAddress + PageHeader.Size; - while (true) + var endAddress = pageStartAddress + result.maxPtr; + + while (recordAddress < endAddress) { var logRecord = new LogRecord(recordAddress); // Use allocatedSize here because that is what LogicalAddress is based on. - var logRecordSize = logRecord.GetInlineRecordSizes().allocatedSize; - recordAddress += logRecordSize; - - if (logRecord.Info.Invalid || logRecord.Info.RecordIsInline) - continue; - - if (!startPosition.IsSet) + if (logRecord.Info.RecordIsInline) { - startPosition = new(logRecord.GetObjectLogRecordStartPositionAndLengths(out _, out _), objectLogNextRecordStartPosition.SegmentSizeBits); + recordAddress += logRecord.GetInlineRecordSizes().allocatedSize; continue; } - // We have already incremented record address to get to the next record; if it is at or beyond the maxPtr, we have processed all records. - if (recordAddress >= pageStartAddress + result.maxPtr) + recordAddress += logRecord.GetInlineRecordSizesWithUnreadObjects().allocatedSize; + if (logRecord.Info.Valid) { - ObjectLogFilePositionInfo endPosition = new(logRecord.GetObjectLogRecordStartPositionAndLengths(out var keyLength, out var valueLength), - objectLogNextRecordStartPosition.SegmentSizeBits); - endPosition.Advance((ulong)keyLength + valueLength); - totalBytesToRead = endPosition - startPosition; - break; + if (!startPosition.IsSet) + startPosition = new(logRecord.GetObjectLogRecordStartPositionAndLengths(out _, out _), objectLogNextRecordStartPosition.SegmentSizeBits); + endPosition = new(logRecord.GetObjectLogRecordStartPositionAndLengths(out endKeyLength, out endValueLength), objectLogNextRecordStartPosition.SegmentSizeBits); } } // The page may not have contained any records with objects if (startPosition.IsSet) { + endPosition.Advance((ulong)endKeyLength + endValueLength); + totalBytesToRead = endPosition - startPosition; + // Iterate all records again to actually do the deserialization. result.readBuffers.nextReadFilePosition = startPosition; recordAddress = pageStartAddress + PageHeader.Size; @@ -598,23 +620,25 @@ private void AsyncReadPageWithObjectsCallback(uint errorCode, uint num var logReader = new ObjectLogReader(result.readBuffers, storeFunctions); logReader.OnBeginReadRecords(startPosition, totalBytesToRead); - do + while (recordAddress < endAddress) { - var logRecord = new LogRecord(recordAddress); + var logRecord = new LogRecord(recordAddress, transientObjectIdMap); // Use allocatedSize here because that is what LogicalAddress is based on. - var logRecordSize = logRecord.GetInlineRecordSizes().allocatedSize; - recordAddress += logRecordSize; - - if (logRecord.Info.Invalid || logRecord.Info.RecordIsInline) + if (logRecord.Info.RecordIsInline) + { + recordAddress += logRecord.GetInlineRecordSizes().allocatedSize; continue; + } - // We don't need the DiskLogRecord here; we're either iterating (and will create it in GetNext()) or recovering - // (and do not need one; we're just populating the record ObjectIds and ObjectIdMap). objectLogDevice is in readBuffers. - _ = logReader.ReadRecordObjects(pageStartAddress, logRecordSize, noKey, transientObjectIdMap, startPosition.SegmentSizeBits, out _ /*diskLogRecord*/); - - // If the incremented record address is at or beyond the maxPtr, we have processed all records. - } while (recordAddress < pageStartAddress + result.maxPtr); + recordAddress += logRecord.GetInlineRecordSizesWithUnreadObjects().allocatedSize; + if (logRecord.Info.Valid) + { + // We don't need the DiskLogRecord here; we're either iterating (and will create it in GetNext()) or recovering + // (and do not need one; we're just populating the record ObjectIds and ObjectIdMap). objectLogDevice is in readBuffers. + _ = logReader.ReadRecordObjects(ref logRecord, noKey, transientObjectIdMap, startPosition.SegmentSizeBits); + } + } } // Call the "real" page read callback @@ -629,7 +653,7 @@ private void AsyncReadPageWithObjectsCallback(uint errorCode, uint num /// public override ITsavoriteScanIterator Scan(TsavoriteKV> store, long beginAddress, long endAddress, DiskScanBufferingMode diskScanBufferingMode, bool includeClosedRecords) - => new ObjectScanIterator>(CreateCircularReadBuffers(), store, this, beginAddress, endAddress, epoch, diskScanBufferingMode, includeClosedRecords: includeClosedRecords); + => new ObjectScanIterator>(store, this, beginAddress, endAddress, epoch, diskScanBufferingMode, includeClosedRecords: includeClosedRecords); /// /// Implementation for push-scanning Tsavorite log, called from LogAccessor @@ -637,7 +661,7 @@ public override ITsavoriteScanIterator Scan(TsavoriteKV(TsavoriteKV> store, long beginAddress, long endAddress, ref TScanFunctions scanFunctions, DiskScanBufferingMode scanBufferingMode) { - using ObjectScanIterator> iter = new(CreateCircularReadBuffers(), store, this, beginAddress, endAddress, epoch, scanBufferingMode, includeClosedRecords: false, logger: logger); + using ObjectScanIterator> iter = new(store, this, beginAddress, endAddress, epoch, scanBufferingMode, includeClosedRecords: false, logger: logger); return PushScanImpl(beginAddress, endAddress, ref scanFunctions, iter); } @@ -648,7 +672,7 @@ internal override bool ScanCursor(TsavoriteKV> iter = new(CreateCircularReadBuffers(), store, this, cursor, endAddress, epoch, DiskScanBufferingMode.SinglePageBuffering, + using ObjectScanIterator> iter = new(store, this, cursor, endAddress, epoch, DiskScanBufferingMode.SinglePageBuffering, includeClosedRecords: maxAddress < long.MaxValue, logger: logger); return ScanLookup>>(store, scanCursorState, ref cursor, count, scanFunctions, iter, validateCursor, maxAddress, resetCursor: resetCursor, includeTombstones: includeTombstones); @@ -660,7 +684,7 @@ internal override bool ScanCursor(TsavoriteKV(TsavoriteKV> store, ReadOnlySpan key, long beginAddress, ref TScanFunctions scanFunctions) { - using ObjectScanIterator> iter = new(CreateCircularReadBuffers(), store, this, beginAddress, epoch, logger: logger); + using ObjectScanIterator> iter = new(store, this, beginAddress, epoch, logger: logger); return IterateHashChain(store, key, beginAddress, ref scanFunctions, iter); } diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectScanIterator.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectScanIterator.cs index 7ab6c03f1be..3a80a262c40 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectScanIterator.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectScanIterator.cs @@ -38,25 +38,27 @@ internal sealed unsafe class ObjectScanIterator : S /// Epoch to use for protection; may be null if is true. /// Provided address range is known by caller to be in memory, even if less than HeadAddress /// - internal ObjectScanIterator(CircularDiskReadBuffer readBuffers, TsavoriteKV store, AllocatorBase hlogBase, + internal ObjectScanIterator(TsavoriteKV store, AllocatorBase hlogBase, long beginAddress, long endAddress, LightEpoch epoch, DiskScanBufferingMode diskScanBufferingMode, InMemoryScanBufferingMode memScanBufferingMode = InMemoryScanBufferingMode.NoBuffering, bool includeClosedRecords = false, bool assumeInMemory = false, ILogger logger = null) - : base(readBuffers, beginAddress == 0 ? hlogBase.GetFirstValidLogicalAddressOnPage(0) : beginAddress, endAddress, diskScanBufferingMode, memScanBufferingMode, includeClosedRecords, epoch, hlogBase.LogPageSizeBits, logger: logger) + : base(beginAddress == 0 ? hlogBase.GetFirstValidLogicalAddressOnPage(0) : beginAddress, endAddress, diskScanBufferingMode, memScanBufferingMode, + includeClosedRecords, epoch, hlogBase.LogPageSizeBits, logger: logger) { this.store = store; this.hlogBase = hlogBase; this.assumeInMemory = assumeInMemory; if (frameSize > 0) frame = new BlittableFrame(frameSize, hlogBase.PageSize, hlogBase.GetDeviceSectorSize()); + InitializeReadBuffers(hlogBase); } /// /// Constructor for use with tail-to-head push iteration of the passed key's record versions /// - internal ObjectScanIterator(CircularDiskReadBuffer readBuffers, TsavoriteKV store, AllocatorBase hlogBase, + internal ObjectScanIterator(TsavoriteKV store, AllocatorBase hlogBase, long beginAddress, LightEpoch epoch, ILogger logger = null) - : base(readBuffers, beginAddress == 0 ? hlogBase.GetFirstValidLogicalAddressOnPage(0) : beginAddress, hlogBase.GetTailAddress(), + : base(beginAddress == 0 ? hlogBase.GetFirstValidLogicalAddressOnPage(0) : beginAddress, hlogBase.GetTailAddress(), DiskScanBufferingMode.SinglePageBuffering, InMemoryScanBufferingMode.NoBuffering, false, epoch, hlogBase.LogPageSizeBits, logger: logger) { this.store = store; @@ -253,8 +255,11 @@ public unsafe bool GetNext() { // We advance a record at a time in the IO frame so set the diskLogRecord to the current frame offset and advance nextAddress. // We dispose the object here because it is read from the disk, unless we transfer it such as by CopyToTail. - diskLogRecord = new(new LogRecord(physicalAddress, hlogBase._wrapper.TranssientObjectIdMap), - obj => store.storeFunctions.DisposeValueObject(obj, DisposeReason.DeserializedFromDisk)); + var logRecord = new LogRecord(physicalAddress, hlogBase._wrapper.TransientObjectIdMap); + diskLogRecord = new(logRecord, + store is not null + ? obj => store.storeFunctions.DisposeValueObject(obj, DisposeReason.DeserializedFromDisk) + : obj => { }); // TODOnow this needs to dispose the object even if store is null; review whether we should have separate arg for behavior instead of a null store } } finally diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/CircularDiskReadBuffer.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/CircularDiskReadBuffer.cs index f14ab35717c..ecf6bff33ee 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/CircularDiskReadBuffer.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/CircularDiskReadBuffer.cs @@ -30,7 +30,7 @@ public class CircularDiskReadBuffer : IDisposable /// Track the remaining length to be read for one or more records for Object values, and we can also read some or all of Overflow values into the buffer. ulong unreadLengthRemaining; - internal CircularDiskReadBuffer(SectorAlignedBufferPool bufferPool, int bufferSize, int numBuffers, IDevice objectLogDevice, ILogger logger) + internal CircularDiskReadBuffer(SectorAlignedBufferPool bufferPool, int bufferSize, int numBuffers, IDevice objectLogDevice, ILogger logger) { this.bufferPool = bufferPool; this.bufferSize = bufferSize; @@ -205,4 +205,4 @@ public void Dispose() public override string ToString() => $"currIdx {currentIndex}; bufSize {bufferSize}; filePosition {nextReadFilePosition}; SecSize {(int)objectLogDevice.SectorSize}"; } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/CircularDiskWriteBuffer.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/CircularDiskWriteBuffer.cs index 49de0528769..d777e5dfe67 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/CircularDiskWriteBuffer.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/CircularDiskWriteBuffer.cs @@ -271,4 +271,4 @@ public override string ToString() return result; } } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/DiskReadBuffer.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/DiskReadBuffer.cs index d7d711748ce..765259bccce 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/DiskReadBuffer.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/DiskReadBuffer.cs @@ -106,7 +106,7 @@ public void Dispose() { memory?.Return(); memory = null; - countdownEvent.Dispose(); + countdownEvent?.Dispose(); countdownEvent = null; } @@ -114,4 +114,4 @@ public void Dispose() public override string ToString() => $"currPos {currentPosition}; endPos {endPosition}; avLen {AvailableLength}; countDown {countdownEvent?.CurrentCount}; buf: {memory}"; } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/DiskWriteBuffer.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/DiskWriteBuffer.cs index 7f4086313e3..2e7ade44191 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/DiskWriteBuffer.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/DiskWriteBuffer.cs @@ -123,4 +123,4 @@ public override string ToString() return $"currPos {currentPosition}; endPos {endPosition}; remCap {RemainingCapacity}; flushedUntilPos {flushedUntilPosition}; countDown {countdownString}; buf: {memory}"; } } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/IStreamBuffer.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/IStreamBuffer.cs index 50166b82f30..4ab11f94b5a 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/IStreamBuffer.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/IStreamBuffer.cs @@ -58,4 +58,4 @@ public interface IStreamBuffer : IDisposable /// The number of bytes read into , which may be less than . int Read(Span destinationSpan, CancellationToken cancellationToken = default); } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogFilePositionInfo.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogFilePositionInfo.cs index 342bf43ca93..b36f24fd22d 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogFilePositionInfo.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogFilePositionInfo.cs @@ -88,7 +88,7 @@ public void AdvanceToNextSegment() Offset = 0; } - public static ulong operator-(ObjectLogFilePositionInfo left, ObjectLogFilePositionInfo right) + public static ulong operator -(ObjectLogFilePositionInfo left, ObjectLogFilePositionInfo right) { Debug.Assert(left.SegmentSizeBits == right.SegmentSizeBits, "Segment size bits must match to compute distance"); Debug.Assert(left.word >= right.word, "comparison position must be greater"); @@ -105,4 +105,4 @@ public void AdvanceToNextSegment() /// public override readonly string ToString() => $"Segment {SegmentId}, Offset {Offset:N0}, Bits {SegmentSizeBits}, SegSize {SegmentSize:N0}"; } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogReader.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogReader.cs index be50e138c02..5c0ff2e0c50 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogReader.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogReader.cs @@ -64,24 +64,6 @@ internal void OnBeginReadRecords(ObjectLogFilePositionInfo filePosition, ulong t /// public void Write(ReadOnlySpan data, CancellationToken cancellationToken = default) => throw new InvalidOperationException("Write is not supported for DiskStreamReadBuffer"); - /// - /// Get the object log entries for Overflow Keys and Values and Object Values for the record at . We create the log record here, - /// because we are calling this over a pages from iterator frames or Restore. - /// - /// Pointer to the initial record read from disk, either from iterator or Restore. - /// Number of bytes available at - /// The requested key, if not ReadAtAddress; we will compare to see if it matches the record. - /// The to place Overflow and Object Keys and Values in. - /// Number of bits in segment size - /// The output , which has its Key and Value ObjectIds filled in in the log record. - /// False if requestedKey is set and we read an Overflow key and it did not match; otherwise true - public bool ReadRecordObjects(long physicalAddress, int recordSize, ReadOnlySpan requestedKey, ObjectIdMap transientObjectIdMap, int segmentSizeBits, out LogRecord logRecord) - { - logRecord = new LogRecord(physicalAddress, transientObjectIdMap); - Debug.Assert(logRecord.GetInlineRecordSizes().actualSize <= recordSize, $"RecordSize ({recordSize}) is less than required LogRecord size ({logRecord.GetInlineRecordSizes().actualSize})"); - return logRecord.Info.RecordIsInline || ReadRecordObjects(ref logRecord, requestedKey, segmentSizeBits); - } - /// /// Get the object log entries for Overflow Keys and Values and Object Values for the input . We do not create the log record here; /// that was already done by the caller (probably from a single-record disk IO). @@ -132,7 +114,7 @@ public bool ReadRecordObjects(ref LogRecord logRecord, ReadOnlySpan reques if (logRecord.Info.KeyIsOverflow) { // This assignment also allocates the slot in ObjectIdMap. The varbyte length info should be unchanged from ObjectIdSize. - logRecord.KeyOverflow = new OverflowByteArray(keyLength, startOffset:0, endOffset:0, zeroInit:false); + logRecord.KeyOverflow = new OverflowByteArray(keyLength, startOffset: 0, endOffset: 0, zeroInit: false); _ = Read(logRecord.KeyOverflow.Span); if (!requestedKey.IsEmpty && !storeFunctions.KeysEqual(requestedKey, logRecord.KeyOverflow.Span)) return false; @@ -227,4 +209,4 @@ public void Dispose() valueObjectSerializer?.EndDeserialize(); } } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogWriter.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogWriter.cs index 37e2f5bfd51..397d9b5cc36 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogWriter.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogWriter.cs @@ -84,7 +84,7 @@ internal DiskWriteBuffer OnBeginPartialFlush(ObjectLogFilePositionInfo filePosit /// Callback sent to the initial Flush() command. Called when we are done with this partial flush operation. /// Context sent to . /// The ending file position after the partial flush is complete - internal unsafe void OnPartialFlushComplete(byte* mainLogPageSpanPtr, int mainLogPageSpanLength, IDevice mainLogDevice, ulong alignedMainLogFlushAddress, + internal unsafe void OnPartialFlushComplete(byte* mainLogPageSpanPtr, int mainLogPageSpanLength, IDevice mainLogDevice, ulong alignedMainLogFlushAddress, DeviceIOCompletionCallback externalCallback, object externalContext, out ObjectLogFilePositionInfo endFilePosition) => flushBuffers.OnPartialFlushComplete(mainLogPageSpanPtr, mainLogPageSpanLength, mainLogDevice, alignedMainLogFlushAddress, externalCallback, externalContext, out endFilePosition); @@ -220,7 +220,7 @@ public void Write(ReadOnlySpan data, CancellationToken cancellationToken = var dataStart = 0; while (data.Length - dataStart > 0) { - Debug.Assert(writeBuffer.RemainingCapacity > 0, + Debug.Assert(writeBuffer.RemainingCapacity > 0, $"RemainingCapacity {writeBuffer.RemainingCapacity} should not be 0 (data.Length {data.Length}, dataStart {dataStart}); this should have already triggered an OnChunkComplete call, which would have reset the buffer"); cancellationToken.ThrowIfCancellationRequested(); // IDevice does not support cancellation, so just check this here diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/PinnedMemoryStream.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/PinnedMemoryStream.cs index 042b2db7774..dfd32a8e044 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/PinnedMemoryStream.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/PinnedMemoryStream.cs @@ -173,4 +173,4 @@ private ValueTask WriteAsync(ReadOnlySpan destinationSpan, CancellationTok public override unsafe void WriteByte(byte value) => streamBuffer.Write(new ReadOnlySpan(&value, 1)); } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/VarbyteLengthUtility.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/VarbyteLengthUtility.cs index 17f2726c1a3..6f5c100e5bd 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/VarbyteLengthUtility.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/VarbyteLengthUtility.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +using System; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; @@ -58,18 +59,18 @@ public static unsafe class VarbyteLengthUtility #pragma warning restore IDE1006 // Naming Styles /// The minimum number of length metadata bytes--NumIndicatorBytes, 1 byte key length, 1 byte value length - public const int MinLengthMetadataBytes = 3; + public const int MinLengthMetadataBytes = NumIndicatorBytes + 2; /// The maximum number of length metadata bytes--NumIndicatorBytes, 4 bytes key length, 7 bytes value length - internal const int MaxLengthMetadataBytes = 12; + internal const int MaxLengthMetadataBytes = NumIndicatorBytes + 11; /// The number of indicator bytes; currently 1 for the length indicator. internal const int NumIndicatorBytes = 3; /// The maximum number of key length bytes in the in-memory single-long word representation. We use zero-based sizes and add 1, so /// 1 bit allows us to specify 1 or 2 bytes; we max at 2, or . Anything over this becomes overflow. - internal const int MaxKeyLengthBytesInWord = 1; + internal const int MaxKeyLengthBytesInWord = 2; /// The maximum number of value length bytes in the in-memory single-long word representation. We use zero-based sizes and add 1, so /// 2 bits allows us to specify 1 to 4 bytes; we max at 3, or . Anything over this becomes overflow. - internal const int MaxValueLengthBytesInWord = 2; + internal const int MaxValueLengthBytesInWord = 3; /// Read var-length bytes at the given location. /// This is compatible with little-endian 'long'; thus, the indicator byte is the low byte of the word, then keyLengthBytes, valueLengthBytes, keyLength, valueLength in ascending address order @@ -206,6 +207,9 @@ internal static (int length, long dataAddress) GetKeyFieldInfo(long indicatorAdd return (keyLength, indicatorAddress + NumIndicatorBytes + keyLengthBytes + valueLengthBytes); } + /// + /// Gets the value field information for an in-memory or on-disk with object size changes to value length restored (objects have been read). + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static (long length, long dataAddress) GetValueFieldInfo(long indicatorAddress) { @@ -221,6 +225,22 @@ internal static (long length, long dataAddress) GetValueFieldInfo(long indicator return (valueLength, indicatorAddress + NumIndicatorBytes + keyLengthBytes + valueLengthBytes + keyLength); } + /// + /// Gets the value field information for an in-memory or on-disk with object size changes to value length not yet restored (objects have not yet been read). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (long length, long dataAddress) GetValueFieldInfoWithUnreadObjects(long indicatorAddress) + { + var (keyLengthBytes, valueLengthBytes, _ /*hasFiller*/) = DeconstructIndicatorByte(*(byte*)indicatorAddress); + + // Move past the indicator byte; the next bytes are key length + var keyLength = ReadVarbyteLengthInWord(*(long*)indicatorAddress, precedingNumBytes: 0, keyLengthBytes); + + // We know the valueLength is the size of the object Id. + // Move past the key and value length bytes and the key data to the start of the value data + return (ObjectIdMap.ObjectIdSize, indicatorAddress + NumIndicatorBytes + keyLengthBytes + valueLengthBytes + keyLength); + } + /// /// Get the value data pointer, as well as the pointer to length, length, and number of length bytes. This is to support in-place updating. /// @@ -229,7 +249,7 @@ internal static (long length, long dataAddress) GetValueFieldInfo(long indicator { var ptr = (byte*)indicatorAddress; var (keyLengthBytes, valueLengthBytes, _ /*hasFiller*/) = DeconstructIndicatorByte(*ptr); - ptr++; + ptr += NumIndicatorBytes; // Move past the indicator byte; the next bytes are key length var keyLength = ReadVarbyteLengthInWord(*(long*)indicatorAddress, precedingNumBytes: 0, keyLengthBytes); @@ -294,10 +314,12 @@ internal static unsafe long ConstructInlineVarbyteLengthWord(int keyLength, int /// internal static unsafe long ConstructInlineVarbyteLengthWord(int keyLengthBytes, int keyLength, int valueLengthBytes, int valueLength, long flagBits) { + if (valueLengthBytes > MaxValueLengthBytesInWord) + throw new ArgumentOutOfRangeException(nameof(valueLengthBytes), $"Value length bytes {valueLengthBytes} exceeds max {MaxValueLengthBytesInWord}"); var word = (long)0; var ptr = (byte*)&word; - *ptr++ = (byte)(ConstructIndicatorByte(keyLengthBytes, valueLengthBytes) | flagBits); - ptr += 2; // Space for RecordType and Namespace + *ptr = (byte)(ConstructIndicatorByte(keyLengthBytes, valueLengthBytes) | flagBits); + ptr += NumIndicatorBytes; WriteVarbyteLengthInWord(ref word, keyLength, precedingNumBytes: 0, keyLengthBytes); WriteVarbyteLengthInWord(ref word, valueLength, precedingNumBytes: keyLengthBytes, valueLengthBytes); diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/OverflowByteArray.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/OverflowByteArray.cs index 0ff0e9ff473..18af9325cac 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/OverflowByteArray.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/OverflowByteArray.cs @@ -45,8 +45,8 @@ struct OverflowHeader internal OverflowByteArray(int length, int startOffset, int endOffset, bool zeroInit) { // Allocate with enough extra space for the metadata (offset from start and end) - Array = !zeroInit - ? GC.AllocateUninitializedArray(length + OverflowHeader.Size) + Array = !zeroInit + ? GC.AllocateUninitializedArray(length + OverflowHeader.Size) : (new byte[length + OverflowHeader.Size]); ref var header = ref Unsafe.As(ref Array[0]); header.startOffset = startOffset; @@ -76,4 +76,4 @@ internal readonly void SetOffsets(int offsetFromStart, int offsetFromEnd) /// Get the of a byte[] allocated by constructor. internal static Span AsSpan(object value) => new OverflowByteArray(Unsafe.As(value)).Span; } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/PageHeader.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/PageHeader.cs index 2f82258f881..d0a907c382b 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/PageHeader.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/PageHeader.cs @@ -70,4 +70,4 @@ internal void SetLowestObjectLogPosition(in ObjectLogFilePositionInfo position) public override readonly string ToString() => $"ver {version}, lowObjLogPos {objectLogLowestPosition}, us1 {unusedUshort1}, ui1 {unusedInt1}, ul1 {unusedLong1}, ul2 {unusedLong2}, ul3 {unusedLong3}, ul4 {unusedLong4}, ul5 {unusedLong5}, ul6 {unusedLong6}"; } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/ScanIteratorBase.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/ScanIteratorBase.cs index 6d8c847c192..97c85936760 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/ScanIteratorBase.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/ScanIteratorBase.cs @@ -7,6 +7,7 @@ namespace Tsavorite.core { +#pragma warning disable IDE0065 // Misplaced using directive using static LogAddress; /// @@ -48,15 +49,15 @@ public abstract class ScanIteratorBase /// This array is in parallel with , , and . private long[] nextLoadedPages; + /// The circular buffer we cycle through for object-log deserialization. + CircularDiskReadBuffer[] readBuffers; + /// Number of bits in the size of the log page private readonly int logPageSizeBits; /// Whether to include closed records in the scan protected readonly bool includeClosedRecords; - /// The circular buffer we cycle through for object-log deserialization. - readonly CircularDiskReadBuffer readBuffers; - /// /// Current address /// @@ -90,10 +91,9 @@ public abstract class ScanIteratorBase /// /// Constructor /// - public unsafe ScanIteratorBase(CircularDiskReadBuffer readBuffers, long beginAddress, long endAddress, DiskScanBufferingMode diskScanBufferingMode, InMemoryScanBufferingMode memScanBufferingMode, + public unsafe ScanIteratorBase(long beginAddress, long endAddress, DiskScanBufferingMode diskScanBufferingMode, InMemoryScanBufferingMode memScanBufferingMode, bool includeClosedRecords, LightEpoch epoch, int logPageSizeBits, bool initForReads = true, ILogger logger = null) { - this.readBuffers = readBuffers; this.logger = logger; this.memScanBufferingMode = memScanBufferingMode; @@ -122,16 +122,14 @@ public unsafe ScanIteratorBase(CircularDiskReadBuffer readBuffers, long beginAdd InitializeForReads(); } - /// - /// Initialize for reads - /// + /// Initialize fields for read callback management public virtual void InitializeForReads() { loadCompletionEvents = new CountdownEvent[frameSize]; loadCTSs = new CancellationTokenSource[frameSize]; loadedPages = new long[frameSize]; nextLoadedPages = new long[frameSize]; - for (int i = 0; i < frameSize; i++) + for (var i = 0; i < frameSize; i++) { loadedPages[i] = -1; nextLoadedPages[i] = -1; @@ -141,6 +139,14 @@ public virtual void InitializeForReads() nextAddress = beginAddress; } + /// Initialize read buffers + public virtual void InitializeReadBuffers(AllocatorBase allocatorBase = default) + { + readBuffers = new CircularDiskReadBuffer[frameSize]; + for (var i = 0; i < frameSize; i++) + readBuffers[i] = allocatorBase?.CreateCircularReadBuffers(); + } + /// /// Buffer and load /// @@ -153,7 +159,7 @@ public virtual void InitializeForReads() /// protected unsafe bool BufferAndLoad(long currentAddress, long currentPage, long currentFrame, long headAddress, long endAddress) { - for (int i = 0; i < frameSize; i++) + for (var i = 0; i < frameSize; i++) { var nextPage = currentPage + i; @@ -182,18 +188,19 @@ protected unsafe bool BufferAndLoad(long currentAddress, long currentPage, long if (val < pageEndAddress && Interlocked.CompareExchange(ref nextLoadedPages[nextFrame], pageEndAddress, val) == val) { var tmp_page = i; + var readBuffer = readBuffers is not null ? readBuffers[nextFrame] : default; if (epoch != null) { epoch.BumpCurrentEpoch(() => { - AsyncReadPagesFromDeviceToFrame(readBuffers, tmp_page + GetPageOfAddress(currentAddress, logPageSizeBits), 1, endAddress, + AsyncReadPagesFromDeviceToFrame(readBuffer, tmp_page + GetPageOfAddress(currentAddress, logPageSizeBits), 1, endAddress, Empty.Default, out loadCompletionEvents[nextFrame], 0, null, null, loadCTSs[nextFrame]); loadedPages[nextFrame] = pageEndAddress; }); } else { - AsyncReadPagesFromDeviceToFrame(readBuffers, tmp_page + GetPageOfAddress(currentAddress, logPageSizeBits), 1, endAddress, + AsyncReadPagesFromDeviceToFrame(readBuffer, tmp_page + GetPageOfAddress(currentAddress, logPageSizeBits), 1, endAddress, Empty.Default, out loadCompletionEvents[nextFrame], 0, null, null, loadCTSs[nextFrame]); loadedPages[nextFrame] = pageEndAddress; } @@ -213,7 +220,7 @@ protected unsafe bool BufferAndLoad(long currentAddress, long currentPage, long /// protected unsafe bool NeedBufferAndLoad(long currentAddress, long currentPage, long currentFrame, long headAddress, long endAddress) { - for (int i = 0; i < frameSize; i++) + for (var i = 0; i < frameSize; i++) { var nextPage = currentPage + i; @@ -263,7 +270,7 @@ private bool WaitForFrameLoad(long currentAddress, long currentFrame) // The exception may have been an OperationCanceledException. loadedPages[currentFrame] = -1; loadCTSs[currentFrame] = new CancellationTokenSource(); - Utility.MonotonicUpdate(ref nextAddress, GetLogicalAddressOfStartOfPage(1 + GetPageOfAddress(currentAddress, logPageSizeBits), logPageSizeBits), out _); + _ = Utility.MonotonicUpdate(ref nextAddress, GetLogicalAddressOfStartOfPage(1 + GetPageOfAddress(currentAddress, logPageSizeBits), logPageSizeBits), out _); throw new TsavoriteException("Page read from storage failed, skipping page. Inner exception: " + e.ToString()); } finally @@ -278,20 +285,31 @@ private bool WaitForFrameLoad(long currentAddress, long currentFrame) /// public virtual void Dispose() { - if (loadCompletionEvents != null) + for (var i = 0; i < frameSize; i++) { - // Wait for ongoing reads to complete/fail - for (int i = 0; i < frameSize; i++) + try { - if (loadedPages[i] != -1) + // Wait for ongoing reads to complete/fail + if (loadCompletionEvents != null) { - try - { - loadCompletionEvents[i].Wait(loadCTSs[i].Token); - } - catch { } + if (loadedPages[i] != -1) + loadCompletionEvents[i]?.Wait(loadCTSs[i].Token); + loadCompletionEvents[i]?.Dispose(); + loadCompletionEvents = default; + } + if (loadCTSs is not null) + { + loadCTSs[i]?.Dispose(); + loadCTSs[i] = null; + } + if (readBuffers is not null) + { + // Do not null this; we didn't hold onto the hlogBase to recreate. CircularDiskReadBuffer.Dispose() clears + // things and leaves it in an "initialized" state. + readBuffers[i]?.Dispose(); } } + catch { } } } @@ -300,15 +318,18 @@ public virtual void Dispose() /// public void Reset() { + Dispose(); loadCompletionEvents = new CountdownEvent[frameSize]; loadCTSs = new CancellationTokenSource[frameSize]; loadedPages = new long[frameSize]; nextLoadedPages = new long[frameSize]; - for (int i = 0; i < frameSize; i++) + for (var i = 0; i < frameSize; i++) { loadedPages[i] = -1; nextLoadedPages[i] = -1; loadCTSs[i] = new CancellationTokenSource(); + // readBuffers do not need to be reset because that is done in its Dispose, leaving it in an "initialized" state. + // Also, OnBeginReadRecords() will do reinitialization internally. } currentAddress = -1; nextAddress = beginAddress; diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteAllocator.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteAllocator.cs index 2f42ee5ec21..ee12406bb5e 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteAllocator.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteAllocator.cs @@ -113,7 +113,7 @@ public readonly RecordSizeInfo GetUpsertRecordSize _this.CreateLogRecord(logicalAddress, physicalAddress); /// - public readonly ObjectIdMap TranssientObjectIdMap => default; + public readonly ObjectIdMap TransientObjectIdMap => default; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteAllocatorImpl.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteAllocatorImpl.cs index e26098208e3..37d1239d037 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteAllocatorImpl.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteAllocatorImpl.cs @@ -19,6 +19,7 @@ public SpanByteAllocatorImpl(AllocatorSettings settings, TStoreFunctions storeFu : base(settings.LogSettings, storeFunctions, wrapperCreator, settings.evictCallback, settings.epoch, settings.flushCallback, settings.logger) { freePagePool = new OverflowPool>(4, p => { }); + pageHeaderSize = PageHeader.Size; } internal int OverflowPageCount => freePagePool.Count; @@ -69,7 +70,7 @@ void ReturnPage(int index) internal LogRecord CreateLogRecord(long logicalAddress) => CreateLogRecord(logicalAddress, GetPhysicalAddress(logicalAddress)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal LogRecord CreateLogRecord(long logicalAddress, long physicalAddress) => new (physicalAddress); + internal LogRecord CreateLogRecord(long logicalAddress, long physicalAddress) => new(physicalAddress); [MethodImpl(MethodImplOptions.AggressiveInlining)] public RecordSizeInfo GetRMWCopyRecordSize(in TSourceLogRecord srcLogRecord, ref TInput input, TVariableLengthInput varlenInput) diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteScanIterator.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteScanIterator.cs index ce4c2cd139e..8e768bb0ea2 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteScanIterator.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteScanIterator.cs @@ -42,7 +42,7 @@ internal SpanByteScanIterator(TsavoriteKV store, Al long beginAddress, long endAddress, LightEpoch epoch, DiskScanBufferingMode diskScanBufferingMode, InMemoryScanBufferingMode memScanBufferingMode = InMemoryScanBufferingMode.NoBuffering, bool includeClosedRecords = false, bool assumeInMemory = false, ILogger logger = null) - : base(readBuffers: default, beginAddress == 0 ? hlogBase.GetFirstValidLogicalAddressOnPage(0) : beginAddress, endAddress, + : base(beginAddress == 0 ? hlogBase.GetFirstValidLogicalAddressOnPage(0) : beginAddress, endAddress, diskScanBufferingMode, memScanBufferingMode, includeClosedRecords, epoch, hlogBase.LogPageSizeBits, logger: logger) { this.store = store; @@ -57,7 +57,7 @@ internal SpanByteScanIterator(TsavoriteKV store, Al /// internal SpanByteScanIterator(TsavoriteKV store, AllocatorBase hlogBase, long beginAddress, LightEpoch epoch, ILogger logger = null) - : base(readBuffers: default, beginAddress == 0 ? hlogBase.GetFirstValidLogicalAddressOnPage(0) : beginAddress, hlogBase.GetTailAddress(), + : base(beginAddress == 0 ? hlogBase.GetFirstValidLogicalAddressOnPage(0) : beginAddress, hlogBase.GetTailAddress(), DiskScanBufferingMode.SinglePageBuffering, InMemoryScanBufferingMode.NoBuffering, false, epoch, hlogBase.LogPageSizeBits, logger: logger) { this.store = store; @@ -254,7 +254,7 @@ public unsafe bool GetNext() { // We advance a record at a time in the IO frame so set the diskLogRecord to the current frame offset and advance nextAddress. // We dispose the object here because it is read from the disk, unless we transfer it such as by CopyToTail (SpanByteAllocator has no objects). - diskLogRecord = new(new LogRecord(physicalAddress, hlogBase._wrapper.TranssientObjectIdMap), + diskLogRecord = new(new LogRecord(physicalAddress, hlogBase._wrapper.TransientObjectIdMap), obj => store.storeFunctions.DisposeValueObject(obj, DisposeReason.DeserializedFromDisk)); } } diff --git a/libs/storage/Tsavorite/cs/src/core/Allocator/TsavoriteLogAllocator.cs b/libs/storage/Tsavorite/cs/src/core/Allocator/TsavoriteLogAllocator.cs index f677ef85307..e50f3d01d21 100644 --- a/libs/storage/Tsavorite/cs/src/core/Allocator/TsavoriteLogAllocator.cs +++ b/libs/storage/Tsavorite/cs/src/core/Allocator/TsavoriteLogAllocator.cs @@ -108,7 +108,7 @@ public readonly RecordSizeInfo GetUpsertRecordSize throw new NotImplementedException("Not implemented for TsavoriteLogAllocator"); /// - public readonly ObjectIdMap TranssientObjectIdMap => throw new NotImplementedException("Not implemented for TsavoriteLogAllocator"); + public readonly ObjectIdMap TransientObjectIdMap => throw new NotImplementedException("Not implemented for TsavoriteLogAllocator"); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Checkpointing/StateMachineDriver.cs b/libs/storage/Tsavorite/cs/src/core/Index/Checkpointing/StateMachineDriver.cs index 85f909bbe3c..e9fceba76cf 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Checkpointing/StateMachineDriver.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Checkpointing/StateMachineDriver.cs @@ -299,7 +299,11 @@ async Task RunStateMachine(CancellationToken token = default) _ = Interlocked.Exchange(ref stateMachine, null); if (ex != null) { - _ = _stateMachineCompleted.TrySetException(ex); + // If the state machine stopped due to cancellation, propagate cancellation to the completion TCS + if (ex is OperationCanceledException || ex is TaskCanceledException) + _ = _stateMachineCompleted.TrySetCanceled(); + else + _ = _stateMachineCompleted.TrySetException(ex); } else { diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Common/CompletedOutput.cs b/libs/storage/Tsavorite/cs/src/core/Index/Common/CompletedOutput.cs index 54f1397c0ad..79a3a3cfe79 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Common/CompletedOutput.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Common/CompletedOutput.cs @@ -118,7 +118,7 @@ internal void TransferFrom(ref TsavoriteKV : ISourceLogRec /// The logical address of the found record, if any; used to create . internal long logicalAddress; + /// The record's ETag, if any; used to create . + internal long eTag; + /// The initial highest logical address of the search; used to limit search ranges when the pending operation completes (e.g. to see if a duplicate was inserted). internal long initialLatestLogicalAddress; @@ -127,12 +130,10 @@ public void Dispose() /// Session functions wrapper for the operation /// Allocator for backing storage [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SerializeForReadOrRMW(ReadOnlySpan key, ref TInput input, ref TOutput output, TContext userContext, + internal void CopyInputsForReadOrRMW(ReadOnlySpan key, ref TInput input, ref TOutput output, TContext userContext, TSessionFunctionsWrapper sessionFunctions, SectorAlignedBufferPool bufferPool) where TSessionFunctionsWrapper : ISessionFunctionsWrapper { - if (diskLogRecord.IsSet) - return; request_key?.Dispose(); request_key = new(key, bufferPool); @@ -241,7 +242,7 @@ public OverflowByteArray ValueOverflow } /// - public readonly long ETag => diskLogRecord.ETag; + public readonly long ETag => diskLogRecord.IsSet ? diskLogRecord.ETag : this.eTag; /// public readonly long Expiration => diskLogRecord.Expiration; diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Common/RecordMetadata.cs b/libs/storage/Tsavorite/cs/src/core/Index/Common/RecordMetadata.cs index a4bcdd144c4..e374f090459 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Common/RecordMetadata.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Common/RecordMetadata.cs @@ -15,12 +15,19 @@ public readonly struct RecordMetadata /// public readonly long Address; - internal RecordMetadata(long address = kInvalidAddress) + /// + /// The ETag of the record, if any; otherwise . + /// + /// Included here to be available for multi-key operations. + public readonly long ETag; + + internal RecordMetadata(long address = kInvalidAddress, long eTag = LogRecord.NoETag) { Address = address; + ETag = eTag; } /// - public override string ToString() => $"addr {AddressString(Address)}"; + public override string ToString() => $"addr {AddressString(Address)}, eTag {ETag}"; } } \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Interfaces/CallbackInfos.cs b/libs/storage/Tsavorite/cs/src/core/Index/Interfaces/CallbackInfos.cs index 74a02464dc4..464d2343d71 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Interfaces/CallbackInfos.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Interfaces/CallbackInfos.cs @@ -196,6 +196,11 @@ public enum ReadAction /// Expire, + /// + /// Stop the operation immediately with a "wrong type" error + /// + WrongType, + /// /// Stop the operation immediately and return. /// diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Recovery/DeltaLog.cs b/libs/storage/Tsavorite/cs/src/core/Index/Recovery/DeltaLog.cs index 0eb567d83c3..91f1d5aa37a 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Recovery/DeltaLog.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Recovery/DeltaLog.cs @@ -74,9 +74,8 @@ public sealed class DeltaLog : ScanIteratorBase, IDisposable /// Constructor /// public DeltaLog(IDevice deltaLogDevice, int logPageSizeBits, long tailAddress, ILogger logger = null) - : base(readBuffers: default, // TODO need to pass in or create this iff ObjectAllocator - but this was disabled for GenericAllocator; need to dig into what's needed for support - 0, tailAddress >= 0 ? tailAddress : deltaLogDevice.GetFileSize(0), DiskScanBufferingMode.SinglePageBuffering, - InMemoryScanBufferingMode.NoBuffering, includeClosedRecords: false, default, logPageSizeBits, false, logger: logger) + : base(0, tailAddress >= 0 ? tailAddress : deltaLogDevice.GetFileSize(0), DiskScanBufferingMode.SinglePageBuffering, + InMemoryScanBufferingMode.NoBuffering, includeClosedRecords: false, epoch: default, logPageSizeBits, initForReads:false, logger: logger) { LogPageSizeBits = logPageSizeBits; PageSize = 1 << LogPageSizeBits; diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Recovery/Recovery.cs b/libs/storage/Tsavorite/cs/src/core/Index/Recovery/Recovery.cs index ae5ece8bf64..f6f30e7e973 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Recovery/Recovery.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Recovery/Recovery.cs @@ -1285,7 +1285,7 @@ private bool RestoreHybridLogInitializePages(long beginAddress, long headAddress var numPages = 0; for (var page = headPage; page <= tailPage; page++) - { + { var pageIndex = GetPageIndexForPage(page); recoveryStatus.readStatus[pageIndex] = ReadStatus.Pending; numPages++; diff --git a/libs/storage/Tsavorite/cs/src/core/Index/StoreFunctions/StoreFunctions.cs b/libs/storage/Tsavorite/cs/src/core/Index/StoreFunctions/StoreFunctions.cs index f9a85e79908..9c3a58c5e21 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/StoreFunctions/StoreFunctions.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/StoreFunctions/StoreFunctions.cs @@ -125,7 +125,7 @@ public static StoreFunctions Create /// Store functions for Key and Value /// - public static StoreFunctions Create() - => new(SpanByteComparer.Instance, valueSerializerCreator: null, SpanByteRecordDisposer.Instance); + public static StoreFunctions Create() + => new(SpanByteComparer.Instance, valueSerializerCreator: null, DefaultRecordDisposer.Instance); } } \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/BlockAllocate.cs b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/BlockAllocate.cs index b024eb7c18f..6b25c0fcd92 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/BlockAllocate.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/BlockAllocate.cs @@ -134,13 +134,13 @@ bool TryAllocateRecordReadCache(ref PendingContext the first address in the tag chain. while (true) { - if (!TryBlockAllocate(readCacheBase, recordSizeInfo.AllocatedInlineRecordSize, out newLogicalAddress, ref pendingContext, out status)) + if (!TryBlockAllocate(readcacheBase, recordSizeInfo.AllocatedInlineRecordSize, out newLogicalAddress, ref pendingContext, out status)) break; - newPhysicalAddress = readCacheBase.GetPhysicalAddress(newLogicalAddress); + newPhysicalAddress = readcacheBase.GetPhysicalAddress(newLogicalAddress); if (VerifyInMemoryAddresses(ref stackCtx)) { - if (!stackCtx.hei.IsReadCache || newLogicalAddress > stackCtx.hei.Address) + if (!stackCtx.hei.IsReadCache || newLogicalAddress > AbsoluteAddress(stackCtx.hei.Address)) return true; // This allocation is below the necessary address so abandon it and repeat the loop. diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/ConditionalCopyToTail.cs b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/ConditionalCopyToTail.cs index 78e6aef38bc..1ef0ce416ab 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/ConditionalCopyToTail.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/ConditionalCopyToTail.cs @@ -127,7 +127,8 @@ internal OperationStatus PrepareIOForConditionalOperation { } : obj => storeFunctions.DisposeValueObject(obj, DisposeReason.DeserializedFromDisk)); + srcLogRecord.IsMemoryLogRecord ? obj => { } + : obj => storeFunctions.DisposeValueObject(obj, DisposeReason.DeserializedFromDisk)); return OperationStatus.RECORD_ON_DISK; } } diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/ContinuePending.cs b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/ContinuePending.cs index 762b8e12933..aaba30b6d30 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/ContinuePending.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/ContinuePending.cs @@ -52,6 +52,7 @@ internal OperationStatus ContinuePendingRead( pendingContext.id = sessionCtx.totalPending++; sessionCtx.ioPendingRequests.Add(pendingContext.id, pendingContext); + // We may have come from an already-pending operation, in which case we don't want to copy the diskLogRecord into the queue. + // But we do want to keep the diskLogRecord in the incoming "ref pendingContext" for disposal, so clear it in the dictionary. + // (We know this will not be a nullref because we just added it). + CollectionsMarshal.GetValueRefOrNullRef(sessionCtx.ioPendingRequests, pendingContext.id).diskLogRecord = default; + // Issue asynchronous I/O request request.id = pendingContext.id; - request.request_key = pendingContext.request_key.Get(); + request.request_key = pendingContext.request_key is null ? default : pendingContext.request_key.Get(); request.logicalAddress = pendingContext.logicalAddress; request.minAddress = pendingContext.minAddress; request.record = default; diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/Helpers.cs b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/Helpers.cs index 0fc908f62f4..98c4d98b709 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/Helpers.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/Helpers.cs @@ -176,7 +176,7 @@ internal enum LatchOperation : byte internal void SetRecordInvalid(long logicalAddress) { // This is called on exception recovery for a newly-inserted record. - var localLog = IsReadCache(logicalAddress) ? readCacheBase : hlogBase; + var localLog = IsReadCache(logicalAddress) ? readcacheBase : hlogBase; LogRecord.GetInfoRef(localLog.GetPhysicalAddress(logicalAddress)).SetInvalid(); } @@ -229,7 +229,7 @@ private bool VerifyInMemoryAddresses(ref OperationStackContext= readCacheBase.HeadAddress) + if (!UseReadCache || stackCtx.recSrc.LowestReadCacheLogicalAddress == kInvalidAddress || stackCtx.recSrc.LowestReadCacheLogicalAddress >= readcacheBase.HeadAddress) return true; // If the splice point went below readcache.HeadAddress, we would have to wait for the chain to be fixed up by eviction, diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/InternalDelete.cs b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/InternalDelete.cs index f0083bf7e0d..c3284cae673 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/InternalDelete.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/InternalDelete.cs @@ -6,6 +6,8 @@ namespace Tsavorite.core { + using static LogAddress; + public unsafe partial class TsavoriteKV : TsavoriteBase where TStoreFunctions : IStoreFunctions where TAllocator : IAllocator @@ -47,6 +49,8 @@ internal OperationStatus InternalDelete stackCtx = new(keyHash); pendingContext.keyHash = keyHash; + pendingContext.logicalAddress = kInvalidAddress; + pendingContext.eTag = LogRecord.NoETag; if (sessionFunctions.Ctx.phase == Phase.IN_PROGRESS_GROW) SplitBuckets(stackCtx.hei.hash); @@ -102,6 +106,9 @@ internal OperationStatus InternalDelete stackCtx = new(keyHash); pendingContext.keyHash = keyHash; + pendingContext.logicalAddress = kInvalidAddress; + pendingContext.eTag = LogRecord.NoETag; if (sessionFunctions.Ctx.phase == Phase.IN_PROGRESS_GROW) SplitBuckets(stackCtx.hei.hash); @@ -141,6 +143,7 @@ internal OperationStatus InternalRMW { pendingContext.type = OperationType.RMW; - pendingContext.SerializeForReadOrRMW(key, ref input, ref output, userContext, sessionFunctions, hlogBase.bufferPool); + pendingContext.CopyInputsForReadOrRMW(key, ref input, ref output, userContext, sessionFunctions, hlogBase.bufferPool); pendingContext.logicalAddress = stackCtx.recSrc.LogicalAddress; } @@ -269,6 +275,8 @@ private bool TryRevivifyInChain stackCtx = new(keyHash); pendingContext.keyHash = keyHash; + pendingContext.logicalAddress = kInvalidAddress; + pendingContext.eTag = LogRecord.NoETag; if (sessionFunctions.Ctx.phase == Phase.IN_PROGRESS_GROW) SplitBuckets(stackCtx.hei.hash); @@ -86,6 +88,7 @@ internal OperationStatus InternalRead { pendingContext.type = OperationType.READ; - pendingContext.SerializeForReadOrRMW(key, ref input, ref output, userContext, sessionFunctions, hlogBase.bufferPool); + pendingContext.CopyInputsForReadOrRMW(key, ref input, ref output, userContext, sessionFunctions, hlogBase.bufferPool); pendingContext.logicalAddress = logicalAddress; } } diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/InternalUpsert.cs b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/InternalUpsert.cs index e496906e656..9cd72533951 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/InternalUpsert.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/InternalUpsert.cs @@ -6,6 +6,8 @@ namespace Tsavorite.core { + using static LogAddress; + public unsafe partial class TsavoriteKV : TsavoriteBase where TStoreFunctions : IStoreFunctions where TAllocator : IAllocator @@ -53,6 +55,8 @@ internal OperationStatus InternalUpsert stackCtx = new(keyHash); pendingContext.keyHash = keyHash; + pendingContext.logicalAddress = kInvalidAddress; + pendingContext.eTag = LogRecord.NoETag; if (sessionFunctions.Ctx.phase == Phase.IN_PROGRESS_GROW) SplitBuckets(stackCtx.hei.hash); @@ -132,6 +136,8 @@ internal OperationStatus InternalUpsert key, ref OperationStackContext< Debug.Assert(UseReadCache, "Should not call FindInReadCache if !UseReadCache"); // minAddress, if present, comes from the pre-pendingIO entry.Address; there may have been no readcache entries then. - minAddress = IsReadCache(minAddress) ? AbsoluteAddress(minAddress) : readCacheBase.HeadAddress; + minAddress = IsReadCache(minAddress) ? AbsoluteAddress(minAddress) : readcacheBase.HeadAddress; RestartChain: @@ -41,7 +41,7 @@ internal bool FindInReadCache(ReadOnlySpan key, ref OperationStackContext< // LatestLogicalAddress is the "leading" pointer and will end up as the highest logical address in the main log for this tag chain. // Increment the trailing "lowest read cache" address (for the splice point). We'll look ahead from this to examine the next record. stackCtx.recSrc.LowestReadCacheLogicalAddress = stackCtx.recSrc.LatestLogicalAddress; - stackCtx.recSrc.LowestReadCachePhysicalAddress = readCacheBase.GetPhysicalAddress(stackCtx.recSrc.LowestReadCacheLogicalAddress); + stackCtx.recSrc.LowestReadCachePhysicalAddress = readcacheBase.GetPhysicalAddress(stackCtx.recSrc.LowestReadCacheLogicalAddress); // Use a non-ref local, because we don't need to update. var recordInfo = LogRecord.GetInfo(stackCtx.recSrc.LowestReadCachePhysicalAddress); @@ -58,7 +58,7 @@ internal bool FindInReadCache(ReadOnlySpan key, ref OperationStackContext< // Keep these at the current readcache location; they'll be the caller's source record. stackCtx.recSrc.LogicalAddress = stackCtx.recSrc.LowestReadCacheLogicalAddress; stackCtx.recSrc.PhysicalAddress = stackCtx.recSrc.LowestReadCachePhysicalAddress; - stackCtx.recSrc.SetAllocator(readCacheBase); + stackCtx.recSrc.SetAllocator(readcacheBase); stackCtx.recSrc.SetHasReadCacheSrc(); // Read() does not need to continue past the found record; updaters need to continue to find latestLogicalAddress and lowestReadCache*Address. @@ -88,9 +88,9 @@ internal bool FindInReadCache(ReadOnlySpan key, ref OperationStackContext< [MethodImpl(MethodImplOptions.AggressiveInlining)] bool ReadCacheNeedToWaitForEviction(ref OperationStackContext stackCtx) { - if (stackCtx.recSrc.LatestLogicalAddress < readCacheBase.HeadAddress) + if (stackCtx.recSrc.LatestLogicalAddress < readcacheBase.HeadAddress) { - SpinWaitUntilRecordIsClosed(stackCtx.recSrc.LatestLogicalAddress, readCacheBase); + SpinWaitUntilRecordIsClosed(stackCtx.recSrc.LatestLogicalAddress, readcacheBase); // Restore to hlog; we may have set readcache into Log and continued the loop, had to restart, and the matching readcache record was evicted. stackCtx.UpdateRecordSourceToCurrentHashEntry(hlogBase); @@ -103,8 +103,8 @@ bool ReadCacheNeedToWaitForEviction(ref OperationStackContext key, ref OperationStackContext stackCtx, long newLogicalAddress) { // Splice into the gap of the last readcache/first main log entries. - Debug.Assert(stackCtx.recSrc.LowestReadCacheLogicalAddress >= readCacheBase.ClosedUntilAddress, - $"{nameof(VerifyInMemoryAddresses)} should have ensured LowestReadCacheLogicalAddress ({stackCtx.recSrc.LowestReadCacheLogicalAddress}) >= readcache.ClosedUntilAddress ({readCacheBase.ClosedUntilAddress})"); + Debug.Assert(stackCtx.recSrc.LowestReadCacheLogicalAddress >= readcacheBase.ClosedUntilAddress, + $"{nameof(VerifyInMemoryAddresses)} should have ensured LowestReadCacheLogicalAddress ({stackCtx.recSrc.LowestReadCacheLogicalAddress}) >= readcache.ClosedUntilAddress ({readcacheBase.ClosedUntilAddress})"); // If the LockTable is enabled, then we either have an exclusive lock and thus cannot have a competing insert to the readcache, or we are doing a // Read() so we allow a momentary overlap of records because they're the same value (no update is being done). @@ -137,8 +137,8 @@ internal void SkipReadCache(ref OperationStackContextIsReadCache) continue; var logicalAddress = entry->Address; - var physicalAddress = readCacheBase.GetPhysicalAddress(logicalAddress); + var physicalAddress = readcacheBase.GetPhysicalAddress(logicalAddress); while (true) { @@ -172,7 +172,7 @@ private void SkipReadCacheBucket(HashBucket* bucket) entry->Address = logicalAddress; if (!entry->IsReadCache) break; - physicalAddress = readCacheBase.GetPhysicalAddress(logicalAddress); + physicalAddress = readcacheBase.GetPhysicalAddress(logicalAddress); } } } @@ -183,6 +183,7 @@ private bool EnsureNoNewMainLogRecordWasSpliced(ReadOnlySpan key, ref Oper { Debug.Assert(!IsReadCache(highestSearchedAddress), "highestSearchedAddress should be a main-log address"); var success = true; + Debug.Assert(AbsoluteAddress(stackCtx.recSrc.LowestReadCacheLogicalAddress) >= readcacheBase.ClosedUntilAddress, "recSrc.LowestReadCachePhysicalAddress should be above ClosedUntilAddress"); var lowest_rcri = LogRecord.GetInfo(stackCtx.recSrc.LowestReadCachePhysicalAddress); Debug.Assert(!IsReadCache(lowest_rcri.PreviousAddress), "lowest-rcri.PreviousAddress should be a main-log address"); if (lowest_rcri.PreviousAddress > highestSearchedAddress) @@ -250,7 +251,7 @@ internal void ReadCacheEvict(long rcLogicalAddress, long rcToLogicalAddress) // Iterate readcache entries in the range rcFrom/ToLogicalAddress, and remove them from the hash chain. while (rcLogicalAddress < rcToLogicalAddress) { - var logRecord = new LogRecord(readCacheBase.GetPhysicalAddress(rcLogicalAddress)); + var logRecord = new LogRecord(readcacheBase.GetPhysicalAddress(rcLogicalAddress)); var (_, rcAllocatedSize) = logRecord.GetInlineRecordSizes(); var rcRecordInfo = logRecord.Info; @@ -269,7 +270,7 @@ internal void ReadCacheEvict(long rcLogicalAddress, long rcToLogicalAddress) // 2. Call FindTag on that key in the main store to get the start of the hash chain. // 3. Walk the hash chain's readcache entries, removing records in the "to be removed" range. // Do not remove Invalid records outside this range; that leads to race conditions. - Debug.Assert(!IsReadCache(rcRecordInfo.PreviousAddress) || rcRecordInfo.PreviousAddress < rcLogicalAddress, "Invalid record ordering in readcache"); + Debug.Assert(!IsReadCache(rcRecordInfo.PreviousAddress) || AbsoluteAddress(rcRecordInfo.PreviousAddress) < rcLogicalAddress, "Invalid record ordering in readcache"); // Find the hash index entry for the key in the store's hash table. HashEntryInfo hei = new(storeFunctions.GetKeyHashCode64(logRecord.Key)); @@ -279,9 +280,9 @@ internal void ReadCacheEvict(long rcLogicalAddress, long rcToLogicalAddress) ReadCacheEvictChain(rcToLogicalAddress, ref hei); NextRecord: - if (readCacheBase.GetOffsetOnPage(rcLogicalAddress) + rcAllocatedSize > readCacheBase.PageSize) + if (readcacheBase.GetOffsetOnPage(rcLogicalAddress) + rcAllocatedSize > readcacheBase.PageSize) { - rcLogicalAddress = readCacheBase.GetLogicalAddressOfStartOfPage(1 + readCacheBase.GetPage(rcLogicalAddress)); + rcLogicalAddress = readcacheBase.GetFirstValidLogicalAddressOnPage(1 + readcacheBase.GetPage(rcLogicalAddress)); continue; } rcLogicalAddress += rcAllocatedSize; @@ -297,7 +298,7 @@ private void ReadCacheEvictChain(long rcToLogicalAddress, ref HashEntryInfo hei) HashBucketEntry entry = new() { word = hei.entry.word }; while (entry.IsReadCache) { - var logRecord = new LogRecord(readCacheBase.GetPhysicalAddress(entry.Address)); + var logRecord = new LogRecord(readcacheBase.GetPhysicalAddress(entry.Address)); ref var recordInfo = ref logRecord.InfoRef; #if DEBUG @@ -309,8 +310,10 @@ private void ReadCacheEvictChain(long rcToLogicalAddress, ref HashEntryInfo hei) #endif // If the record's address is above the eviction range, leave it there and track nextPhysicalAddress. - if (entry.Address >= rcToLogicalAddress) + if (AbsoluteAddress(entry.Address) >= rcToLogicalAddress) { + Debug.Assert(!IsReadCache(recordInfo.PreviousAddress) || entry.Address > recordInfo.PreviousAddress, "Invalid ordering in readcache chain"); + nextPhysicalAddress = logRecord.physicalAddress; entry.word = recordInfo.PreviousAddress; continue; @@ -322,8 +325,12 @@ private void ReadCacheEvictChain(long rcToLogicalAddress, ref HashEntryInfo hei) { ref var nextri = ref LogRecord.GetInfoRef(nextPhysicalAddress); if (nextri.TryUpdateAddress(entry.Address, recordInfo.PreviousAddress)) + { recordInfo.PreviousAddress = kTempInvalidAddress; // The record is no longer in the chain - entry.word = nextri.PreviousAddress; + entry.word = nextri.PreviousAddress; + } + else + Debug.Assert(entry.word == nextri.PreviousAddress, "We should be about to retry nextri.PreviousAddress"); continue; } diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/SplitIndex.cs b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/SplitIndex.cs index 2394b6bd4b9..39cc1397e33 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/SplitIndex.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/SplitIndex.cs @@ -127,7 +127,7 @@ private void SplitChunk( LogRecord logRecord = default; if (entry.IsReadCache) { - if (entry.Address >= readCacheBase.HeadAddress) + if (entry.Address >= readcacheBase.HeadAddress) logRecord = readcache.CreateLogRecord(entry.Address); } else if (entry.Address >= hlogBase.HeadAddress) @@ -247,9 +247,9 @@ private long TraceBackForOtherChainStart(long logicalAddress, int bit) LogRecord logRecord; if (IsReadCache(logicalAddress)) { - if (logicalAddress < readCacheBase.HeadAddress) + if (logicalAddress < readcacheBase.HeadAddress) break; - logRecord = new LogRecord(readCacheBase.GetPhysicalAddress(logicalAddress)); + logRecord = new LogRecord(readcacheBase.GetPhysicalAddress(logicalAddress)); } else { diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/TryCopyToReadCache.cs b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/TryCopyToReadCache.cs index 3a501b11efe..7566ac0cd5c 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/TryCopyToReadCache.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/TryCopyToReadCache.cs @@ -30,7 +30,7 @@ internal bool TryCopyToReadCache : TsavoriteBase, I internal readonly TAllocator hlog; internal readonly AllocatorBase hlogBase; internal readonly TAllocator readcache; - internal readonly AllocatorBase readCacheBase; + internal readonly AllocatorBase readcacheBase; internal readonly TStoreFunctions storeFunctions; @@ -157,8 +157,8 @@ public TsavoriteKV(KVSettings kvSettings, TStoreFunctions storeFunctions, Func(); - readCacheBase.Initialize(); + readcacheBase = readcache.GetBase(); + readcacheBase.Initialize(); ReadCache = new(this, readcache); } @@ -494,7 +494,7 @@ internal Status ContextRead internalStatus = InternalRead(key, keyHash, ref input, ref output, context, ref pcontext, sessionFunctions); while (HandleImmediateRetryStatus(internalStatus, sessionFunctions, ref pcontext)); - recordMetadata = new(pcontext.logicalAddress); + recordMetadata = new(pcontext.logicalAddress, pcontext.ETag); return HandleOperationStatus(sessionFunctions.Ctx, ref pcontext, internalStatus); } @@ -525,7 +525,7 @@ private Status ContextReadAtAddress( internalStatus = InternalRMW(key, keyHash, ref input, ref output, ref context, ref pcontext, sessionFunctions); while (HandleImmediateRetryStatus(internalStatus, sessionFunctions, ref pcontext)); - recordMetadata = new(pcontext.logicalAddress); + recordMetadata = new(pcontext.logicalAddress, pcontext.ETag); return HandleOperationStatus(sessionFunctions.Ctx, ref pcontext, internalStatus); } @@ -634,7 +634,7 @@ public void Dispose() { Free(); hlogBase.Dispose(); - readCacheBase?.Dispose(); + readcacheBase?.Dispose(); LockTable.Dispose(); _lastSnapshotCheckpoint.Dispose(); if (disposeCheckpointManager) @@ -705,7 +705,7 @@ private unsafe string DumpDistributionInternal(int version) if (x.Tentative) ++total_entries_with_tentative_bit_set; - if (((!x.IsReadCache) && (x.Address >= beginAddress)) || (x.IsReadCache && (x.Address >= readCacheBase.HeadAddress))) + if (((!x.IsReadCache) && (x.Address >= beginAddress)) || (x.IsReadCache && (x.Address >= readcacheBase.HeadAddress))) { if (tags.Contains(x.Tag) && !x.Tentative) throw new TsavoriteException("Duplicate tag found in index"); diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/TsavoriteIterator.cs b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/TsavoriteIterator.cs index 3f4c67228b0..5e3a20acc0e 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/TsavoriteIterator.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/TsavoriteIterator.cs @@ -168,7 +168,7 @@ internal bool PushNext(ref TScanFunctions scanFunctions, long nu if (IsTailmostMainKvRecord(key, mainKvIter.Info, ref stackCtx)) { // Push Iter records are in temp storage so do not need locks. - stop = !scanFunctions.Reader(in mainKvIter, new RecordMetadata(mainKvIter.CurrentAddress), numRecords, out _); + stop = !scanFunctions.Reader(in mainKvIter, new RecordMetadata(mainKvIter.CurrentAddress, mainKvIter.ETag), numRecords, out _); return !stop; } @@ -199,7 +199,7 @@ internal bool PushNext(ref TScanFunctions scanFunctions, long nu { if (!tempKvIter.Info.Tombstone) { - stop = !scanFunctions.Reader(in tempKvIter, new RecordMetadata(tempKvIter.CurrentAddress), numRecords, out _); + stop = !scanFunctions.Reader(in tempKvIter, new RecordMetadata(tempKvIter.CurrentAddress, tempKvIter.ETag), numRecords, out _); return !stop; } continue; diff --git a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/TsavoriteThread.cs b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/TsavoriteThread.cs index ce1820c4627..113812cfb44 100644 --- a/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/TsavoriteThread.cs +++ b/libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/TsavoriteThread.cs @@ -115,7 +115,7 @@ internal unsafe Status InternalCompletePendingRequestFromContext pendingContext, out AsyncIOContext newRequest) where TSessionFunctionsWrapper : ISessionFunctionsWrapper { - Debug.Assert(epoch.ThisInstanceProtected(), "InternalCompletePendingRequestFromContext requires epoch acquision"); + Debug.Assert(epoch.ThisInstanceProtected(), "InternalCompletePendingRequestFromContext requires epoch acquisition"); newRequest = default; if (request.diskLogRecord.IsSet) @@ -142,7 +142,7 @@ ref pendingContext.input.Get(), ref pendingContext.output, pendingContext.userContext, status, - new RecordMetadata(pendingContext.logicalAddress)); + new RecordMetadata(pendingContext.logicalAddress, pendingContext.ETag)); } else if (pendingContext.type == OperationType.RMW) { @@ -151,7 +151,7 @@ ref pendingContext.input.Get(), ref pendingContext.output, pendingContext.userContext, status, - new RecordMetadata(pendingContext.logicalAddress)); + new RecordMetadata(pendingContext.logicalAddress, pendingContext.ETag)); } } diff --git a/libs/storage/Tsavorite/cs/src/core/TsavoriteLog/TsavoriteLog.cs b/libs/storage/Tsavorite/cs/src/core/TsavoriteLog/TsavoriteLog.cs index dfc7b24b2dc..eb853a9849c 100644 --- a/libs/storage/Tsavorite/cs/src/core/TsavoriteLog/TsavoriteLog.cs +++ b/libs/storage/Tsavorite/cs/src/core/TsavoriteLog/TsavoriteLog.cs @@ -92,7 +92,7 @@ public sealed class TsavoriteLog : IDisposable public byte[] RecoveredCookie; /// - /// Header size used by TsavoriteLog + /// Header size used by TsavoriteLog, for entryLength and possibly checkSum /// public int HeaderSize => headerSize; @@ -199,7 +199,7 @@ private TsavoriteLog(TsavoriteLogSettings logSettings, bool syncRecover, ILogger if (logSettings.LogCommitManager == null) disposeLogCommitManager = true; - // Reserve 8 byte checksum in header if requested + // Reserve 8 byte checksum in header if requested, in addition to the entry length logChecksum = logSettings.LogChecksum; headerSize = logChecksum == LogChecksumType.PerEntry ? 12 : 4; getMemory = logSettings.GetMemory; @@ -742,7 +742,8 @@ public unsafe bool TryEnqueue(byte[] entry, out long logicalAddress) epoch.Resume(); - if (commitNum == long.MaxValue) throw new TsavoriteException("Attempting to enqueue into a completed log"); + if (commitNum == long.MaxValue) + throw new TsavoriteException("Attempting to enqueue into a completed log"); if (!allocator.TryAllocateRetryNow(allocatedLength, out logicalAddress)) { diff --git a/libs/storage/Tsavorite/cs/src/core/TsavoriteLog/TsavoriteLogIterator.cs b/libs/storage/Tsavorite/cs/src/core/TsavoriteLog/TsavoriteLogIterator.cs index 288aaab7f8c..72f283dddd1 100644 --- a/libs/storage/Tsavorite/cs/src/core/TsavoriteLog/TsavoriteLogIterator.cs +++ b/libs/storage/Tsavorite/cs/src/core/TsavoriteLog/TsavoriteLogIterator.cs @@ -34,7 +34,7 @@ public class TsavoriteLogIterator : ScanIteratorBase, IDisposable /// Constructor internal unsafe TsavoriteLogIterator(TsavoriteLog tsavoriteLog, TsavoriteLogAllocatorImpl hlog, long beginAddress, long endAddress, GetMemory getMemory, DiskScanBufferingMode diskScanBufferingMode, LightEpoch epoch, int headerSize, bool scanUncommitted = false, ILogger logger = null) - : base(readBuffers: default, beginAddress == 0 ? hlog.GetFirstValidLogicalAddressOnPage(0) : beginAddress, endAddress, + : base(beginAddress == 0 ? hlog.GetFirstValidLogicalAddressOnPage(0) : beginAddress, endAddress, diskScanBufferingMode, InMemoryScanBufferingMode.NoBuffering, includeClosedRecords: false, epoch, hlog.LogPageSizeBits, logger: logger) { this.tsavoriteLog = tsavoriteLog; diff --git a/libs/storage/Tsavorite/cs/src/core/Utilities/PageAsyncResultTypes.cs b/libs/storage/Tsavorite/cs/src/core/Utilities/PageAsyncResultTypes.cs index f18abf0bb96..9c93addeabf 100644 --- a/libs/storage/Tsavorite/cs/src/core/Utilities/PageAsyncResultTypes.cs +++ b/libs/storage/Tsavorite/cs/src/core/Utilities/PageAsyncResultTypes.cs @@ -355,7 +355,7 @@ internal sealed class DiskReadCallbackContext { /// If we had separate Reads directly into multiple spans of a single byte[], such as across segments, this is a refcounted wrapper for the ; /// it is released after the write and if it is the final release, all spans have been written and the GCHandle is freed (and the object unpinned). - public RefCountedPinnedGCHandle refCountedGCHandle { get; private set; } + public RefCountedPinnedGCHandle refCountedGCHandle { get; private set; } /// Separate public Set() call so we ensure it is AddRef'd /// diff --git a/libs/storage/Tsavorite/cs/src/core/VarLen/RecordFieldInfo.cs b/libs/storage/Tsavorite/cs/src/core/VarLen/RecordFieldInfo.cs index 7af6bbfc45d..ba88267fdb3 100644 --- a/libs/storage/Tsavorite/cs/src/core/VarLen/RecordFieldInfo.cs +++ b/libs/storage/Tsavorite/cs/src/core/VarLen/RecordFieldInfo.cs @@ -39,4 +39,4 @@ public struct RecordFieldInfo public override string ToString() => $"KeySize {KeySize}, ValSize {ValueSize}, ValIsObj {ValueIsObject}, HasETag {HasETag}, HasExpir {HasExpiration}"; } -} +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/src/core/VarLen/SpanByte.cs b/libs/storage/Tsavorite/cs/src/core/VarLen/SpanByte.cs index 8a1bafb1dc6..0f28d0a84e7 100644 --- a/libs/storage/Tsavorite/cs/src/core/VarLen/SpanByte.cs +++ b/libs/storage/Tsavorite/cs/src/core/VarLen/SpanByte.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; namespace Tsavorite.core { @@ -132,11 +133,23 @@ public static void SerializeTo(this Span source, Span destination) /// Length-limited string representation of a Span [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToShortString(this ReadOnlySpan span, int maxLen = 20) - => span.Length > maxLen ? $"{span.Slice(0, maxLen).ToString()}..." : span.ToString(); + { + var len = Math.Min(span.Length, maxLen); + StringBuilder sb = new(); + for (var ii = 0; ii < len; ++ii) + { + if (ii > 0 && ii % 4 == 0) + _ = sb.Append(' '); + _ = sb.Append(span[ii].ToString("x2")); + } + if (span.Length > len) + _ = sb.Append("..."); + return sb.ToString(); + } /// Length-limited string representation of a Span [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ToShortString(this Span span, int maxLen = 20) - => span.Length > maxLen ? $"{span.Slice(0, maxLen).ToString()}..." : span.ToString(); + => ToShortString((ReadOnlySpan)span, maxLen); } } \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/test/LogRecordTests.cs b/libs/storage/Tsavorite/cs/test/LogRecordTests.cs index 1593cc95e55..a10e98212a8 100644 --- a/libs/storage/Tsavorite/cs/test/LogRecordTests.cs +++ b/libs/storage/Tsavorite/cs/test/LogRecordTests.cs @@ -26,7 +26,7 @@ unsafe class LogRecordTests #pragma warning disable IDE1006 // Naming Styles const int initialKeyLen = 10; const int initialValueLen = 40; - const int initialVarbyteSize = 3; // indicator byte, and 1 byte each for key and value len + const int initialVarbyteSize = MinLengthMetadataBytes; const int initialOptionalSize = sizeof(long) * 2; const int maxInlineKeySize = 64; @@ -124,7 +124,7 @@ public unsafe void VarbyteWordTests() Assert.That(valueLengthBytes, Is.EqualTo(3)); VerifyKeyAndValue(); - // Test 3- and 4-byte valueLengthByte boundary with 3-keyLengthByte key + // Test 3-byte valueLengthByte boundary with 3-keyLengthByte key inputKeyLength = inputValueLength = (1 << 24) - 1; Assert.That(GetByteCount(inputValueLength), Is.EqualTo(3)); value = ConstructInlineVarbyteLengthWord(inputKeyLength, inputValueLength, flagBits: 0, out keyLengthBytes, out valueLengthBytes); @@ -132,18 +132,10 @@ public unsafe void VarbyteWordTests() Assert.That(valueLengthBytes, Is.EqualTo(3)); VerifyKeyAndValue(); + // Test past-max ValueLength (the actual value-setting code will convert this to overflow, so it will use only 4 bytes for ObjectId). inputValueLength = 1 << 24; Assert.That(GetByteCount(inputValueLength), Is.EqualTo(4)); - value = ConstructInlineVarbyteLengthWord(inputKeyLength, inputValueLength, flagBits: 0, out _ /*keyLengthBytes*/, out valueLengthBytes); - Assert.That(valueLengthBytes, Is.EqualTo(4)); - VerifyKeyAndValue(); - - // Test max ValueLength - inputValueLength = int.MaxValue; - Assert.That(GetByteCount(inputValueLength), Is.EqualTo(4)); - value = ConstructInlineVarbyteLengthWord(inputKeyLength, inputValueLength, flagBits: 0, out _ /*keyLengthBytes*/, out valueLengthBytes); - Assert.That(valueLengthBytes, Is.EqualTo(4)); - VerifyKeyAndValue(); + _ = Assert.Throws(() => _ = ConstructInlineVarbyteLengthWord(inputKeyLength, inputValueLength, flagBits: 0, out _ /*keyLengthBytes*/, out valueLengthBytes)); void VerifyKeyAndValue() { @@ -153,7 +145,7 @@ void VerifyKeyAndValue() Assert.That(outputKeyLength, Is.EqualTo(inputKeyLength)); Assert.That(outputValueLength, Is.EqualTo(inputValueLength)); Assert.That((long)keyPtr, Is.EqualTo((long)(ptr + NumIndicatorBytes + outputKeyLengthBytes + outputValueLengthBytes))); - Assert.That((long)keyLengthPtr, Is.EqualTo((long)(ptr + 1))); + Assert.That((long)keyLengthPtr, Is.EqualTo((long)(ptr + NumIndicatorBytes))); Assert.That((long)valuePtr, Is.EqualTo((long)(keyPtr + outputKeyLength))); Assert.That((long)valueLengthPtr, Is.EqualTo((long)(keyLengthPtr + keyLengthBytes))); @@ -278,8 +270,8 @@ public unsafe void ConversionTest() var sizeInfo = new RecordSizeInfo(); InitializeRecord(key, value, ref sizeInfo, out var logRecord, out var expectedFillerLengthAddress, out var expectedFillerLength, out long eTag, out long expiration); - // Convert to overflow. Because objectIdSize is the same as InlineLengthPrefixSize, our value space will shrink by the original value data size. - var offset = value.Length; + // Convert to overflow. Because objectIdSize is 4 bytes our value space will shrink by the original value data size less 4 bytes, but we will use 8 bytes for ObjectLogLogPosition. + var offset = value.Length - 4 - LogRecord.ObjectLogPositionSize; ConvertToOverflow(overflowValue, ref sizeInfo, ref logRecord, expectedFillerLengthAddress, expectedFillerLength, eTag, expiration, offset); RestoreToOriginal(value, ref sizeInfo, ref logRecord, expectedFillerLengthAddress, expectedFillerLength, eTag, expiration); diff --git a/libs/storage/Tsavorite/cs/test/GenericIterationTests.cs b/libs/storage/Tsavorite/cs/test/ObjectIterationTests.cs similarity index 61% rename from libs/storage/Tsavorite/cs/test/GenericIterationTests.cs rename to libs/storage/Tsavorite/cs/test/ObjectIterationTests.cs index a5c8c391a0a..34d71d1c35c 100644 --- a/libs/storage/Tsavorite/cs/test/GenericIterationTests.cs +++ b/libs/storage/Tsavorite/cs/test/ObjectIterationTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -#if LOGRECORD_TODO - using System; using System.Collections.Generic; using System.IO; @@ -14,15 +12,15 @@ namespace Tsavorite.test { - using ClassAllocator = GenericAllocator>>; - using ClassStoreFunctions = StoreFunctions>; + using ClassAllocator = ObjectAllocator>; + using ClassStoreFunctions = StoreFunctions; [TestFixture] - internal class GenericIterationTests + internal class ObjectIterationTests { - private TsavoriteKV store; - private ClientSession session; - private BasicContext bContext; + private TsavoriteKV store; + private ClientSession session; + private BasicContext bContext; private IDevice log, objlog; [SetUp] @@ -35,8 +33,8 @@ public void Setup() private void InternalSetup(bool largeMemory) { // Broke this out as we have different requirements by test. - log = Devices.CreateLogDevice(Path.Join(MethodTestDir, "GenericIterationTests.log"), deleteOnClose: true); - objlog = Devices.CreateLogDevice(Path.Join(MethodTestDir, "GenericIterationTests.obj.log"), deleteOnClose: true); + log = Devices.CreateLogDevice(Path.Join(MethodTestDir, "ObjectIterationTests.log"), deleteOnClose: true); + objlog = Devices.CreateLogDevice(Path.Join(MethodTestDir, "ObjectIterationTests.obj.log"), deleteOnClose: true); store = new(new() { @@ -46,10 +44,10 @@ private void InternalSetup(bool largeMemory) MutableFraction = 0.1, MemorySize = 1L << (largeMemory ? 25 : 14), PageSize = 1L << (largeMemory ? 20 : 9) - }, StoreFunctions.Create(new MyKey.Comparer(), () => new MyKeySerializer(), () => new MyValueSerializer(), DefaultRecordDisposer.Instance) + }, StoreFunctions.Create(new TestObjectKey.Comparer(), () => new TestObjectValue.Serializer(), DefaultRecordDisposer.Instance) , (allocatorSettings, storeFunctions) => new(allocatorSettings, storeFunctions) ); - session = store.NewSession(new MyFunctionsDelete()); + session = store.NewSession(new TestObjectFunctionsDelete()); bContext = session.BasicContext; } @@ -68,17 +66,18 @@ public void TearDown() DeleteDirectory(MethodTestDir); } - internal struct GenericPushIterationTestFunctions : IScanIteratorFunctions + internal struct ObjectPushIterationTestFunctions : IScanIteratorFunctions { internal int keyMultToValue; internal long numRecords; internal int stopAt; - public bool Reader(ref MyKey key, ref MyValue value, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) + public bool Reader(in TSourceLogRecord logRecord, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) + where TSourceLogRecord : ISourceLogRecord { cursorRecordResult = CursorRecordResult.Accept; // default; not used here if (keyMultToValue > 0) - ClassicAssert.AreEqual(key.key * keyMultToValue, value.value); + ClassicAssert.AreEqual(logRecord.Key.AsRef().key * keyMultToValue, ((TestObjectValue)logRecord.ValueObject).value); return stopAt != ++numRecords; } @@ -91,10 +90,10 @@ public readonly void OnStop(bool completed, long numberOfRecords) { } [Category(TsavoriteKVTestCategory)] [Category(SmokeTestCategory)] - public void GenericIterationBasicTest([Values] ScanIteratorType scanIteratorType) + public void ObjectIterationBasicTest([Values] ScanIteratorType scanIteratorType) { InternalSetup(largeMemory: false); - GenericPushIterationTestFunctions scanIteratorFunctions = new(); + ObjectPushIterationTestFunctions scanIteratorFunctions = new(); const int totalRecords = 2000; @@ -106,8 +105,8 @@ void iterateAndVerify(int keyMultToValue, int expectedRecs) if (scanIteratorType == ScanIteratorType.Pull) { using var iter = session.Iterate(); - while (iter.GetNext(out var recordInfo)) - _ = scanIteratorFunctions.Reader(ref iter.GetKey(), ref iter.GetValue(), default, default, out _); + while (iter.GetNext()) + _ = scanIteratorFunctions.Reader(in iter, default, default, out _); } else ClassicAssert.IsTrue(session.Iterate(ref scanIteratorFunctions), $"Failed to complete push iteration; numRecords = {scanIteratorFunctions.numRecords}"); @@ -118,48 +117,48 @@ void iterateAndVerify(int keyMultToValue, int expectedRecs) // Initial population for (int i = 0; i < totalRecords; i++) { - var key1 = new MyKey { key = i }; - var value = new MyValue { value = i }; - _ = bContext.Upsert(ref key1, ref value); + var key1 = new TestObjectKey { key = i }; + var value = new TestObjectValue { value = i }; + _ = bContext.Upsert(SpanByte.FromPinnedVariable(ref key1), value); } iterateAndVerify(1, totalRecords); for (int i = 0; i < totalRecords; i++) { - var key1 = new MyKey { key = i }; - var value = new MyValue { value = 2 * i }; - _ = bContext.Upsert(ref key1, ref value); + var key1 = new TestObjectKey { key = i }; + var value = new TestObjectValue { value = 2 * i }; + _ = bContext.Upsert(SpanByte.FromPinnedVariable(ref key1), value); } iterateAndVerify(2, totalRecords); for (int i = totalRecords / 2; i < totalRecords; i++) { - var key1 = new MyKey { key = i }; - var value = new MyValue { value = i }; - _ = bContext.Upsert(ref key1, ref value); + var key1 = new TestObjectKey { key = i }; + var value = new TestObjectValue { value = i }; + _ = bContext.Upsert(SpanByte.FromPinnedVariable(ref key1), value); } iterateAndVerify(0, totalRecords); for (int i = 0; i < totalRecords; i += 2) { - var key1 = new MyKey { key = i }; - var value = new MyValue { value = i }; - _ = bContext.Upsert(ref key1, ref value); + var key1 = new TestObjectKey { key = i }; + var value = new TestObjectValue { value = i }; + _ = bContext.Upsert(SpanByte.FromPinnedVariable(ref key1), value); } iterateAndVerify(0, totalRecords); for (int i = 0; i < totalRecords; i += 2) { - var key1 = new MyKey { key = i }; - _ = bContext.Delete(ref key1); + var key1 = new TestObjectKey { key = i }; + _ = bContext.Delete(SpanByte.FromPinnedVariable(ref key1)); } iterateAndVerify(0, totalRecords / 2); for (int i = 0; i < totalRecords; i++) { - var key1 = new MyKey { key = i }; - var value = new MyValue { value = 3 * i }; - _ = bContext.Upsert(ref key1, ref value); + var key1 = new TestObjectKey { key = i }; + var value = new TestObjectValue { value = 3 * i }; + _ = bContext.Upsert(SpanByte.FromPinnedVariable(ref key1), value); } iterateAndVerify(3, totalRecords); @@ -171,10 +170,10 @@ void iterateAndVerify(int keyMultToValue, int expectedRecs) [Category(TsavoriteKVTestCategory)] [Category(SmokeTestCategory)] - public void GenericIterationPushStopTest() + public void ObjectIterationPushStopTest() { InternalSetup(largeMemory: false); - GenericPushIterationTestFunctions scanIteratorFunctions = new(); + ObjectPushIterationTestFunctions scanIteratorFunctions = new(); const int totalRecords = 2000; var start = store.Log.TailAddress; @@ -193,9 +192,9 @@ void scanAndVerify(int stopAt, bool useScan) // Initial population for (int i = 0; i < totalRecords; i++) { - var key1 = new MyKey { key = i }; - var value = new MyValue { value = i }; - _ = bContext.Upsert(ref key1, ref value); + var key1 = new TestObjectKey { key = i }; + var value = new TestObjectValue { value = i }; + _ = bContext.Upsert(SpanByte.FromPinnedVariable(ref key1), value); } scanAndVerify(42, useScan: true); @@ -205,7 +204,7 @@ void scanAndVerify(int stopAt, bool useScan) [Test] [Category(TsavoriteKVTestCategory)] [Category(SmokeTestCategory)] - public unsafe void GenericIterationPushLockTest([Values(1, 4)] int scanThreads, [Values(1, 4)] int updateThreads, [Values] ScanMode scanMode) + public unsafe void ObjectIterationPushLockTest([Values(1, 4)] int scanThreads, [Values(1, 4)] int updateThreads, [Values] ScanMode scanMode) { InternalSetup(largeMemory: true); @@ -214,8 +213,8 @@ public unsafe void GenericIterationPushLockTest([Values(1, 4)] int scanThreads, void LocalScan(int i) { - using var session = store.NewSession(new MyFunctionsDelete()); - GenericPushIterationTestFunctions scanIteratorFunctions = new(); + using var session = store.NewSession(new TestObjectFunctionsDelete()); + ObjectPushIterationTestFunctions scanIteratorFunctions = new(); if (scanMode == ScanMode.Scan) ClassicAssert.IsTrue(store.Log.Scan(ref scanIteratorFunctions, start, store.Log.TailAddress), $"Failed to complete push scan; numRecords = {scanIteratorFunctions.numRecords}"); @@ -226,21 +225,21 @@ void LocalScan(int i) void LocalUpdate(int tid) { - using var session = store.NewSession(new MyFunctionsDelete()); + using var session = store.NewSession(new TestObjectFunctionsDelete()); for (int i = 0; i < totalRecords; i++) { - var key1 = new MyKey { key = i }; - var value = new MyValue { value = (tid + 1) * i }; - _ = bContext.Upsert(ref key1, ref value); + var key1 = new TestObjectKey { key = i }; + var value = new TestObjectValue { value = (tid + 1) * i }; + _ = bContext.Upsert(SpanByte.FromPinnedVariable(ref key1), value); } } { // Initial population for (int i = 0; i < totalRecords; i++) { - var key1 = new MyKey { key = i }; - var value = new MyValue { value = i }; - _ = bContext.Upsert(ref key1, ref value); + var key1 = new TestObjectKey { key = i }; + var value = new TestObjectValue { value = i }; + _ = bContext.Upsert(SpanByte.FromPinnedVariable(ref key1), value); } } @@ -257,6 +256,4 @@ void LocalUpdate(int tid) Task.WaitAll([.. tasks]); } } -} - -#endif // LOGRECORD_TODO +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/test/ObjectRecoveryTest2.cs b/libs/storage/Tsavorite/cs/test/ObjectRecoveryTest2.cs index 15561cfa271..6beb9dbb5b7 100644 --- a/libs/storage/Tsavorite/cs/test/ObjectRecoveryTest2.cs +++ b/libs/storage/Tsavorite/cs/test/ObjectRecoveryTest2.cs @@ -153,118 +153,6 @@ private void Read(ClientSession - { - public override void Serialize(ref MyKey key) - { - var bytes = System.Text.Encoding.UTF8.GetBytes(key.name); - writer.Write(4 + bytes.Length); - writer.Write(key.key); - writer.Write(bytes); - } - - public override void Deserialize(out MyKey key) - { - key = new MyKey(); - var size = reader.ReadInt32(); - key.key = reader.ReadInt32(); - var bytes = new byte[size - 4]; - _ = reader.Read(bytes, 0, size - 4); - key.name = System.Text.Encoding.UTF8.GetString(bytes); - - } - } - - public class MyValueSerializer : BinaryObjectSerializer - { - public override void Serialize(ref MyValue value) - { - var bytes = System.Text.Encoding.UTF8.GetBytes(value.value); - writer.Write(bytes.Length); - writer.Write(bytes); - } - - public override void Deserialize(out MyValue value) - { - value = new MyValue(); - var size = reader.ReadInt32(); - var bytes = new byte[size]; - _ = reader.Read(bytes, 0, size); - value.value = System.Text.Encoding.UTF8.GetString(bytes); - } - } - - public class MyKey - { - public int key; - public string name; - - public struct Comparer : IKeyComparer - { - public readonly long GetHashCode64(ref MyKey key) => Utility.GetHashCode(key.key); - public readonly bool Equals(ref MyKey key1, ref MyKey key2) => key1.key == key2.key && key1.name == key2.name; - } - } - - public class MyValue { public string value; } - public class MyInput { public string value; } - public class MyOutput { public MyValue value; } - - public class MyContext - { - private Status _status; - private MyOutput _g1; - - internal void Populate(ref Status status, ref MyOutput g1) - { - _status = status; - _g1 = g1; - } - internal void FinalizeRead(ref Status status, ref MyOutput g1) - { - status = _status; - g1 = _g1; - } - } - - - public class MyFunctions : SessionFunctionsBase - { - public override bool InitialUpdater(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput output, ref RMWInfo rmwInfo, ref RecordInfo recordInfo) { value.value = input.value; return true; } - public override bool NeedCopyUpdate(ref MyKey key, ref MyInput input, ref MyValue oldValue, ref MyOutput output, ref RMWInfo rmwInfo) => true; - public override bool CopyUpdater(ref MyKey key, ref MyInput input, ref MyValue oldValue, ref MyValue newValue, ref MyOutput output, ref RMWInfo rmwInfo, ref RecordInfo recordInfo) { newValue = oldValue; return true; } - public override bool InPlaceUpdater(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput output, ref RMWInfo rmwInfo, ref RecordInfo recordInfo) - { - if (value.value.Length < input.value.Length) - return false; - value.value = input.value; - return true; - } - - - public override bool Reader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst, ref ReadInfo readInfo) - { - dst.value = value; - return true; - } - - public override bool InitialWriter(ref MyKey key, ref MyInput input, ref MyValue src, ref MyValue dst, ref MyOutput output, ref UpsertInfo upsertInfo, ref RecordInfo recordInfo) { dst = src; return true; } - - public override bool InPlaceWriter(ref MyKey key, ref MyInput input, ref MyValue src, ref MyValue dst, ref MyOutput output, ref UpsertInfo upsertInfo, ref RecordInfo recordInfo) - { - if (src == null) - return false; - - if (dst.value.Length != src.value.Length) - return false; - - dst = src; - return true; - } - - public override void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyOutput output, MyContext ctx, Status status, RecordMetadata recordMetadata) => ctx.Populate(ref status, ref output); - } } #endif // LOGRECORD_TODO diff --git a/libs/storage/Tsavorite/cs/test/ObjectTestTypes.cs b/libs/storage/Tsavorite/cs/test/ObjectTestTypes.cs index 5144a2c8607..4eb823f1dfa 100644 --- a/libs/storage/Tsavorite/cs/test/ObjectTestTypes.cs +++ b/libs/storage/Tsavorite/cs/test/ObjectTestTypes.cs @@ -11,7 +11,7 @@ namespace Tsavorite.test { public enum TestValueStyle : byte { None, Inline, Overflow, Object }; - public struct TestObjectKey + public struct TestObjectKey { public int key; diff --git a/libs/storage/Tsavorite/cs/test/ReadCacheChainTests.cs b/libs/storage/Tsavorite/cs/test/ReadCacheChainTests.cs index 62f438f9a90..d92260a6914 100644 --- a/libs/storage/Tsavorite/cs/test/ReadCacheChainTests.cs +++ b/libs/storage/Tsavorite/cs/test/ReadCacheChainTests.cs @@ -206,7 +206,7 @@ internal static (long logicalAddress, long physicalAddress) GetHashChain { - var log = isReadCache ? store.readCacheBase : store.hlogBase; + var log = isReadCache ? store.readcacheBase : store.hlogBase; var info = LogRecord.GetInfo(physicalAddress); var la = info.PreviousAddress; isReadCache = IsReadCache(la); - log = isReadCache ? store.readCacheBase : store.hlogBase; + log = isReadCache ? store.readcacheBase : store.hlogBase; var pa = log.GetPhysicalAddress(la); recordKey = PinnedSpanByte.FromPinnedSpan(LogRecord.GetInlineKey(pa)); // Must return PinnedSpanByte to avoid scope issues with ReadOnlySpan invalid = LogRecord.GetInfo(pa).Invalid; diff --git a/libs/storage/Tsavorite/cs/test/SpanByteIterationTests.cs b/libs/storage/Tsavorite/cs/test/SpanByteIterationTests.cs index f47b8528671..0bdca06d2f6 100644 --- a/libs/storage/Tsavorite/cs/test/SpanByteIterationTests.cs +++ b/libs/storage/Tsavorite/cs/test/SpanByteIterationTests.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -#if LOGRECORD_TODO - using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Threading.Tasks; using NUnit.Framework; using NUnit.Framework.Legacy; @@ -14,12 +13,12 @@ namespace Tsavorite.test { - using SpanByteStoreFunctions = StoreFunctions; + using SpanByteStoreFunctions = StoreFunctions; [TestFixture] internal class SpanByteIterationTests { - private TsavoriteKV> store; + private TsavoriteKV> store; private IDevice log; // Note: We always set value.length to 2, which includes both VLValue members; we are not exercising the "Variable Length" aspect here. @@ -42,19 +41,20 @@ public void TearDown() DeleteDirectory(MethodTestDir); } - internal struct SpanBytePushIterationTestFunctions : IScanIteratorFunctions + internal struct SpanBytePushIterationTestFunctions : IScanIteratorFunctions { internal int keyMultToValue; internal long numRecords; internal int stopAt; - public unsafe bool Reader(ref SpanByte key, ref SpanByte value, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) + public bool Reader(in TSourceLogRecord logRecord, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) + where TSourceLogRecord : ISourceLogRecord { cursorRecordResult = CursorRecordResult.Accept; // default; not used here if (keyMultToValue > 0) { - var keyItem = key.AsSpan()[0]; - var valueItem = value.AsSpan()[0]; + var keyItem = MemoryMarshal.Cast(logRecord.Key)[0]; + var valueItem = MemoryMarshal.Cast(logRecord.ValueSpan)[0]; ClassicAssert.AreEqual(keyItem * keyMultToValue, valueItem); } return stopAt != ++numRecords; @@ -65,14 +65,15 @@ public readonly void OnException(Exception exception, long numberOfRecords) { } public readonly void OnStop(bool completed, long numberOfRecords) { } } - internal struct IterationCollisionTestFunctions : IScanIteratorFunctions + internal struct IterationCollisionTestFunctions : IScanIteratorFunctions { internal List keys; - public IterationCollisionTestFunctions() => keys = new(); + public IterationCollisionTestFunctions() => keys = []; - public unsafe bool Reader(ref SpanByte key, ref SpanByte value, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) + public readonly bool Reader(in TSourceLogRecord logRecord, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) + where TSourceLogRecord : ISourceLogRecord { - keys.Add(*(long*)key.ToPointer()); + keys.Add(MemoryMarshal.Cast(logRecord.Key)[0]); cursorRecordResult = CursorRecordResult.Accept; // default; not used here return true; } @@ -95,11 +96,11 @@ public unsafe void SpanByteIterationBasicTest([Values] DeviceType deviceType, [V MemorySize = 1L << 15, PageSize = 1L << 9, SegmentSize = 1L << 22 - }, StoreFunctions.Create() + }, StoreFunctions.Create(SpanByteComparer.Instance, SpanByteRecordDisposer.Instance) , (allocatorSettings, storeFunctions) => new(allocatorSettings, storeFunctions) ); - using var session = store.NewSession(new VLVectorFunctions()); + using var session = store.NewSession(new VLVectorFunctions()); var bContext = session.BasicContext; SpanBytePushIterationTestFunctions scanIteratorFunctions = new(); @@ -114,8 +115,8 @@ void iterateAndVerify(int keyMultToValue, int expectedRecs) if (scanIteratorType == ScanIteratorType.Pull) { using var iter = session.Iterate(); - while (iter.GetNext(out var recordInfo)) - _ = scanIteratorFunctions.Reader(ref iter.GetKey(), ref iter.GetValue(), default, default, out _); + while (iter.GetNext()) + _ = scanIteratorFunctions.Reader(in iter, default, default, out _); } else ClassicAssert.IsTrue(session.Iterate(ref scanIteratorFunctions), $"Failed to complete push iteration; numRecords = {scanIteratorFunctions.numRecords}"); @@ -126,15 +127,15 @@ void iterateAndVerify(int keyMultToValue, int expectedRecs) // Note: We only have a single value element; we are not exercising the "Variable Length" aspect here. Span keySpan = stackalloc long[1]; Span valueSpan = stackalloc int[1]; - var key = keySpan.AsSpanByte(); - var value = valueSpan.AsSpanByte(); + var key = MemoryMarshal.Cast(keySpan); + var value = MemoryMarshal.Cast(valueSpan); // Initial population for (int i = 0; i < totalRecords; i++) { keySpan[0] = i; valueSpan[0] = i; - _ = bContext.Upsert(ref key, ref value); + _ = bContext.Upsert(key, value); } iterateAndVerify(1, totalRecords); @@ -142,7 +143,7 @@ void iterateAndVerify(int keyMultToValue, int expectedRecs) { keySpan[0] = i; valueSpan[0] = i * 2; - _ = bContext.Upsert(ref key, ref value); + _ = bContext.Upsert(key, value); } iterateAndVerify(2, totalRecords); @@ -150,7 +151,7 @@ void iterateAndVerify(int keyMultToValue, int expectedRecs) { keySpan[0] = i; valueSpan[0] = i; - _ = bContext.Upsert(ref key, ref value); + _ = bContext.Upsert(key, value); } iterateAndVerify(0, totalRecords); @@ -158,14 +159,14 @@ void iterateAndVerify(int keyMultToValue, int expectedRecs) { keySpan[0] = i; valueSpan[0] = i; - _ = bContext.Upsert(ref key, ref value); + _ = bContext.Upsert(key, value); } iterateAndVerify(0, totalRecords); for (int i = 0; i < totalRecords; i += 2) { keySpan[0] = i; - _ = bContext.Delete(ref key); + _ = bContext.Delete(key); } iterateAndVerify(0, totalRecords / 2); @@ -173,7 +174,7 @@ void iterateAndVerify(int keyMultToValue, int expectedRecs) { keySpan[0] = i; valueSpan[0] = i * 3; - _ = bContext.Upsert(ref key, ref value); + _ = bContext.Upsert(key, value); } iterateAndVerify(3, totalRecords); @@ -194,11 +195,11 @@ public void SpanByteIterationPushStopTest([Values] DeviceType deviceType) MemorySize = 1L << 15, PageSize = 1L << 9, SegmentSize = 1L << 22 - }, StoreFunctions.Create() + }, StoreFunctions.Create(SpanByteComparer.Instance, SpanByteRecordDisposer.Instance) , (allocatorSettings, storeFunctions) => new(allocatorSettings, storeFunctions) ); - using var session = store.NewSession(new VLVectorFunctions()); + using var session = store.NewSession(new VLVectorFunctions()); var bContext = session.BasicContext; SpanBytePushIterationTestFunctions scanIteratorFunctions = new(); @@ -219,15 +220,15 @@ void scanAndVerify(int stopAt, bool useScan) // Note: We only have a single value element; we are not exercising the "Variable Length" aspect here. Span keySpan = stackalloc long[1]; Span valueSpan = stackalloc int[1]; - var key = keySpan.AsSpanByte(); - var value = valueSpan.AsSpanByte(); + var key = MemoryMarshal.Cast(keySpan); + var value = MemoryMarshal.Cast(valueSpan); // Initial population for (int i = 0; i < totalRecords; i++) { keySpan[0] = i; valueSpan[0] = i; - _ = bContext.Upsert(ref key, ref value); + _ = bContext.Upsert(key, value); } scanAndVerify(42, useScan: true); @@ -249,7 +250,7 @@ public unsafe void SpanByteIterationPushLockTest([Values(1, 4)] int scanThreads, MemorySize = 1L << 25, PageSize = 1L << 19, SegmentSize = 1L << 22 - }, StoreFunctions.Create() + }, StoreFunctions.Create(SpanByteComparer.Instance, SpanByteRecordDisposer.Instance) , (allocatorSettings, storeFunctions) => new(allocatorSettings, storeFunctions) ); @@ -258,7 +259,7 @@ public unsafe void SpanByteIterationPushLockTest([Values(1, 4)] int scanThreads, void LocalScan(int i) { - using var session = store.NewSession(new VLVectorFunctions()); + using var session = store.NewSession(new VLVectorFunctions()); SpanBytePushIterationTestFunctions scanIteratorFunctions = new(); if (scanMode == ScanMode.Scan) ClassicAssert.IsTrue(store.Log.Scan(ref scanIteratorFunctions, start, store.Log.TailAddress), $"Failed to complete push scan; numRecords = {scanIteratorFunctions.numRecords}"); @@ -272,16 +273,16 @@ void LocalUpdate(int tid) // Note: We only have a single value element; we are not exercising the "Variable Length" aspect here. Span keySpan = stackalloc long[1]; Span valueSpan = stackalloc int[1]; - var key = keySpan.AsSpanByte(); - var value = valueSpan.AsSpanByte(); + var key = MemoryMarshal.Cast(keySpan); + var value = MemoryMarshal.Cast(valueSpan); - using var session = store.NewSession(new VLVectorFunctions()); + using var session = store.NewSession(new VLVectorFunctions()); var bContext = session.BasicContext; for (int i = 0; i < totalRecords; i++) { keySpan[0] = i; valueSpan[0] = i * (tid + 1); - _ = bContext.Upsert(ref key, ref value); + _ = bContext.Upsert(key, value); } } @@ -290,16 +291,16 @@ void LocalUpdate(int tid) // Note: We only have a single value element; we are not exercising the "Variable Length" aspect here. Span keySpan = stackalloc long[1]; Span valueSpan = stackalloc int[1]; - var key = keySpan.AsSpanByte(); - var value = valueSpan.AsSpanByte(); + var key = MemoryMarshal.Cast(keySpan); + var value = MemoryMarshal.Cast(valueSpan); - using var session = store.NewSession(new VLVectorFunctions()); + using var session = store.NewSession(new VLVectorFunctions()); var bContext = session.BasicContext; for (int i = 0; i < totalRecords; i++) { keySpan[0] = i; valueSpan[0] = i; - _ = bContext.Upsert(ref key, ref value); + _ = bContext.Upsert(key, value); } } @@ -316,6 +317,4 @@ void LocalUpdate(int tid) Task.WaitAll([.. tasks]); } } -} - -#endif // LOGRECORD_TODO +} \ No newline at end of file diff --git a/libs/storage/Tsavorite/cs/test/SpanByteLogCompactionTests.cs b/libs/storage/Tsavorite/cs/test/SpanByteLogCompactionTests.cs index 3ada15f2b68..4ab4f48f9b3 100644 --- a/libs/storage/Tsavorite/cs/test/SpanByteLogCompactionTests.cs +++ b/libs/storage/Tsavorite/cs/test/SpanByteLogCompactionTests.cs @@ -321,7 +321,7 @@ public void SpanByteLogCompactionCustomFunctionsTest1([Values] CompactionType co [Test] [Category("TsavoriteKV")] [Category("Compaction")] - public void SpanByteLogCompactionCustomFunctionsTest2([Values] CompactionType compactionType, [Values] bool flushAndEvict) + public void SpanByteLogCompactionCustomFunctionsTest2([Values] CompactionType compactionType, [Values(FlushMode.ReadOnly, FlushMode.OnDisk)] FlushMode flushMode) { // Update: irrelevant as session compaction no longer uses Copy/CopyInPlace // This test checks if CopyInPlace returning false triggers call to Copy @@ -347,7 +347,7 @@ public void SpanByteLogCompactionCustomFunctionsTest2([Values] CompactionType co status = bContext.Read(key, ref input, ref output, 0); Debug.Assert(status.Found); - if (flushAndEvict) + if (flushMode == FlushMode.OnDisk) store.Log.FlushAndEvict(true); else store.Log.Flush(true); diff --git a/libs/storage/Tsavorite/cs/test/VLVector.cs b/libs/storage/Tsavorite/cs/test/VLVector.cs index 266a3a9a7ae..49d345b7b18 100644 --- a/libs/storage/Tsavorite/cs/test/VLVector.cs +++ b/libs/storage/Tsavorite/cs/test/VLVector.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -#if LOGRECORD_TODO - using System; -using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using NUnit.Framework.Legacy; using Tsavorite.core; @@ -13,37 +11,29 @@ namespace Tsavorite.test // Extension class for SpanByte to wrap a non-byte Span internal static unsafe class VLVector { - // Wrap a SpanByte around a Span of (usually) non-byte type, e.g.: + // Wrap a SpanByte around a Span of non-byte type, e.g.: // Span valueSpan = stackalloc int[numElem]; // for (var ii = 0; ii < numElem; ++ii) valueSpan[ii] = someInt; // var valueSpanByte = valueSpan.AsSpanByte(); - public static SpanByte AsSpanByte(this Span span) where T : unmanaged - => new SpanByte(span.Length * sizeof(T), (IntPtr)Unsafe.AsPointer(ref span[0])); - - public static SpanByte AsSpanByte(this ReadOnlySpan span) where T : unmanaged - { - fixed (T* ptr = span) - { - return new SpanByte(span.Length * sizeof(T), (IntPtr)ptr); - } - } + public static PinnedSpanByte FromPinnedSpan(this Span span) where T : unmanaged + => PinnedSpanByte.FromPinnedSpan(MemoryMarshal.Cast(span)); - public static Span AsSpan(this ref SpanByte sb) where T : unmanaged - => new Span(sb.MetadataSize + sb.ToPointer(), (sb.Length - sb.MetadataSize) / sizeof(T)); + internal static T[] ToArray(this Span byteSpan) where T : unmanaged + => MemoryMarshal.Cast(byteSpan).ToArray(); - internal static T[] ToArray(this ref SpanByte spanByte) where T : unmanaged - => AsSpan(ref spanByte).ToArray(); + internal static T[] ToArray(this ReadOnlySpan byteSpan) where T : unmanaged + => MemoryMarshal.Cast(byteSpan).ToArray(); } - public class VLVectorFunctions : SpanByteFunctions + public class VLVectorFunctions : SessionFunctionsBase { - public override void RMWCompletionCallback(ref SpanByte key, ref SpanByte input, ref int[] output, Empty ctx, Status status, RecordMetadata recordMetadata) + public override void RMWCompletionCallback(ref DiskLogRecord diskLogRecord, ref PinnedSpanByte input, ref int[] output, Empty ctx, Status status, RecordMetadata recordMetadata) { ClassicAssert.IsTrue(status.Found); ClassicAssert.IsTrue(status.Record.CopyUpdated); } - public override void ReadCompletionCallback(ref SpanByte key, ref SpanByte input, ref int[] output, Empty ctx, Status status, RecordMetadata recordMetadata) + public override void ReadCompletionCallback(ref DiskLogRecord diskLogRecord, ref PinnedSpanByte input, ref int[] output, Empty ctx, Status status, RecordMetadata recordMetadata) { ClassicAssert.IsTrue(status.Found); for (int i = 0; i < output.Length; i++) @@ -51,23 +41,12 @@ public override void ReadCompletionCallback(ref SpanByte key, ref SpanByte input } // Read functions - public override bool Reader(ref SpanByte key, ref SpanByte input, ref SpanByte value, ref int[] dst, ref ReadInfo readInfo) + public override bool Reader(in TSourceLogRecord srcLogRecord, ref PinnedSpanByte input, ref int[] output, ref ReadInfo readInfo) { - dst = value.ToArray(); + output = srcLogRecord.ValueSpan.ToArray(); return true; } - // Upsert functions - public override bool InitialWriter(ref SpanByte key, ref SpanByte input, ref SpanByte src, ref SpanByte dst, ref int[] output, ref UpsertInfo upsertInfo, ref RecordInfo recordInfo) - => base.InitialWriter(ref key, ref input, ref src, ref dst, ref output, ref upsertInfo, reason, ref recordInfo); - - public override bool InPlaceWriter(ref SpanByte key, ref SpanByte input, ref SpanByte src, ref SpanByte dst, ref int[] output, ref UpsertInfo upsertInfo, ref RecordInfo recordInfo) - { - if (src.Length != dst.Length) - return false; - return base.InPlaceWriter(ref key, ref input, ref src, ref dst, ref output, ref upsertInfo, ref recordInfo); - } + // Upsert functions are unchanged from SessionFunctionsBase } } - -#endif // LOGRECORD_TODO diff --git a/main/GarnetServer/Extensions/ReadWriteTxn.cs b/main/GarnetServer/Extensions/ReadWriteTxn.cs index 16778b83735..445a9ce4412 100644 --- a/main/GarnetServer/Extensions/ReadWriteTxn.cs +++ b/main/GarnetServer/Extensions/ReadWriteTxn.cs @@ -24,8 +24,8 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce api.GET(GetNextArg(ref procInput, ref offset), out PinnedSpanByte key1); if (key1.ReadOnlySpan.SequenceEqual("wrong_string"u8)) return false; - AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, false); - AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, false); + AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, StoreType.Main); + AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, StoreType.Main); return true; } diff --git a/main/GarnetServer/Extensions/SampleDeleteTxn.cs b/main/GarnetServer/Extensions/SampleDeleteTxn.cs index 01866fdd820..adffaad40fa 100644 --- a/main/GarnetServer/Extensions/SampleDeleteTxn.cs +++ b/main/GarnetServer/Extensions/SampleDeleteTxn.cs @@ -32,12 +32,12 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce var offset = 0; var mainStoreKey = GetNextArg(ref procInput, ref offset); - AddKey(mainStoreKey, LockType.Exclusive, false); + AddKey(mainStoreKey, LockType.Exclusive, StoreType.Main); var sortedSet1Key = GetNextArg(ref procInput, ref offset); if (sortedSet1Key.Length > 0) { - AddKey(sortedSet1Key, LockType.Exclusive, true); + AddKey(sortedSet1Key, LockType.Exclusive, StoreType.Object); } GetNextArg(ref procInput, ref offset); // sortedSet1Entry @@ -45,7 +45,7 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce var sortedSet2Key = GetNextArg(ref procInput, ref offset); if (sortedSet2Key.Length > 0) { - AddKey(sortedSet2Key, LockType.Exclusive, true); + AddKey(sortedSet2Key, LockType.Exclusive, StoreType.Object); } return true; @@ -57,7 +57,7 @@ public override void Main(TGarnetApi api, ref CustomProcedureInput p var mainStoreKey = GetNextArg(ref procInput, ref offset); - api.DELETE(mainStoreKey, StoreType.Main); + api.DELETE(mainStoreKey); var sortedSet1Key = GetNextArg(ref procInput, ref offset); var sortedSet1Entry = GetNextArg(ref procInput, ref offset); diff --git a/main/GarnetServer/Extensions/SampleUpdateTxn.cs b/main/GarnetServer/Extensions/SampleUpdateTxn.cs index ac132ef507b..f7762836c9c 100644 --- a/main/GarnetServer/Extensions/SampleUpdateTxn.cs +++ b/main/GarnetServer/Extensions/SampleUpdateTxn.cs @@ -34,12 +34,12 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce var mainStoreKey = GetNextArg(ref procInput, ref offset); GetNextArg(ref procInput, ref offset); // mainStoreValue - AddKey(mainStoreKey, LockType.Exclusive, false); + AddKey(mainStoreKey, LockType.Exclusive, StoreType.Main); var sortedSet1Key = GetNextArg(ref procInput, ref offset); if (sortedSet1Key.Length > 0) { - AddKey(sortedSet1Key, LockType.Exclusive, true); + AddKey(sortedSet1Key, LockType.Exclusive, StoreType.Object); } GetNextArg(ref procInput, ref offset); // sortedSet1Entry @@ -48,7 +48,7 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce var sortedSet2Key = GetNextArg(ref procInput, ref offset); if (sortedSet2Key.Length > 0) { - AddKey(sortedSet2Key, LockType.Exclusive, true); + AddKey(sortedSet2Key, LockType.Exclusive, StoreType.Object); } return true; diff --git a/playground/CommandInfoUpdater/CommandDocsUpdater.cs b/playground/CommandInfoUpdater/CommandDocsUpdater.cs index 2aab4d4ebf0..9d77a94df1c 100644 --- a/playground/CommandInfoUpdater/CommandDocsUpdater.cs +++ b/playground/CommandInfoUpdater/CommandDocsUpdater.cs @@ -70,7 +70,7 @@ public static bool TryUpdateCommandDocs(string outputDir, int respServerPort, IP IDictionary queriedCommandsDocs = new Dictionary(); var commandsToQuery = commandsToAdd.Keys.Select(k => k.Command) - .Where(c => updatedCommandsInfo.ContainsKey(c) && !updatedCommandsInfo[c].IsInternal).ToArray(); + .Where(c => updatedCommandsInfo.ContainsKey(c) || (updatedCommandsInfo[c].SubCommands?.Length > 0 && !updatedCommandsInfo[c].IsInternal)).ToArray(); if (commandsToQuery.Length > 0) { for (var i = 0; i < commandsToQuery.Length; i += QUERY_CMD_BATCH_SIZE) diff --git a/playground/CommandInfoUpdater/CommandInfoUpdater.cs b/playground/CommandInfoUpdater/CommandInfoUpdater.cs index a96d70dda2c..ca9cc0947af 100644 --- a/playground/CommandInfoUpdater/CommandInfoUpdater.cs +++ b/playground/CommandInfoUpdater/CommandInfoUpdater.cs @@ -15,7 +15,7 @@ namespace CommandInfoUpdater /// public class CommandInfoUpdater { - const int QUERY_CMD_BATCH_SIZE = 10; + const int QUERY_CMD_BATCH_SIZE = 1; private static readonly string CommandInfoFileName = "RespCommandsInfo.json"; private static readonly string GarnetCommandInfoJsonPath = "GarnetCommandsInfo.json"; @@ -62,7 +62,7 @@ public static bool TryUpdateCommandInfo(string outputDir, int respServerPort, IP IDictionary queriedCommandsInfo = new Dictionary(); var commandsToQuery = commandsToAdd.Keys.Select(k => k.Command) - .Where(c => !garnetCommandsInfo.ContainsKey(c) || !garnetCommandsInfo[c].IsInternal).ToArray(); + .Where(c => !garnetCommandsInfo.ContainsKey(c) || (garnetCommandsInfo[c].SubCommands?.Length > 0 && !garnetCommandsInfo[c].IsInternal)).ToArray(); if (commandsToQuery.Length > 0) { @@ -131,6 +131,26 @@ public static bool TryUpdateCommandInfo(string outputDir, int respServerPort, IP } } + // Update store types + foreach (var sc in commandsToAdd.Keys) + { + if (!additionalCommandsInfo.TryGetValue(sc.Command, out var commandInfo)) + continue; + + commandInfo.StoreType = sc.StoreType; + + if (commandInfo.SubCommands == null) + continue; + + foreach (var subCommandInfo in commandInfo.SubCommands) + { + if (sc.SubCommands.TryGetValue(subCommandInfo.Name, out var scSubCommand)) + { + subCommandInfo.StoreType = scSubCommand.StoreType; + } + } + } + updatedCommandsInfo = GetUpdatedCommandsInfo(existingCommandsInfo, commandsToAdd, commandsToRemove, additionalCommandsInfo); @@ -175,9 +195,9 @@ private static unsafe bool TryGetCommandsInfo(string[] commandsToQuery, int resp } // Get a map of supported commands to Garnet's RespCommand & ArrayCommand for the parser - var supportedCommands = new ReadOnlyDictionary( + var supportedCommands = new ReadOnlyDictionary( SupportedCommand.SupportedCommandsFlattenedMap.ToDictionary(kvp => kvp.Key, - kvp => kvp.Value.RespCommand, StringComparer.OrdinalIgnoreCase)); + kvp => (kvp.Value.RespCommand, kvp.Value.StoreType), StringComparer.OrdinalIgnoreCase)); // Parse the response fixed (byte* respPtr = response) @@ -259,6 +279,7 @@ private static IReadOnlyDictionary GetUpdatedCommandsI AclCategories = existingCommand.AclCategories, Tips = existingCommand.Tips, KeySpecifications = existingCommand.KeySpecifications, + StoreType = existingCommand.StoreType, SubCommands = remainingSubCommands == null || remainingSubCommands.Length == 0 ? null : [.. existingCommand.SubCommands.Where(sc => remainingSubCommands.Contains(sc.Name))] @@ -315,6 +336,7 @@ private static IReadOnlyDictionary GetUpdatedCommandsI AclCategories = baseCommand.AclCategories, Tips = baseCommand.Tips, KeySpecifications = baseCommand.KeySpecifications, + StoreType = baseCommand.StoreType, SubCommands = updatedSubCommands?.ToArray() }; diff --git a/playground/CommandInfoUpdater/CommonUtils.cs b/playground/CommandInfoUpdater/CommonUtils.cs index f3b28a6da3f..aa669dbbb8e 100644 --- a/playground/CommandInfoUpdater/CommonUtils.cs +++ b/playground/CommandInfoUpdater/CommonUtils.cs @@ -130,7 +130,7 @@ .. supportedCommand.SubCommands if (subCommandsToAdd.Length > 0) { commandsToAdd.Add( - new SupportedCommand(supportedCommand.Command, supportedCommand.RespCommand, subCommandsToAdd), false); + new SupportedCommand(supportedCommand.Command, supportedCommand.RespCommand, supportedCommand.StoreType, subCommandsToAdd), false); } } @@ -162,7 +162,7 @@ .. supportedCommand.SubCommands if (subCommandsToRemove.Length > 0) { commandsToRemove.Add( - new SupportedCommand(existingCommand.Key, existingCommand.Value.Command, subCommandsToRemove), false); + new SupportedCommand(existingCommand.Key, existingCommand.Value.Command, StoreType.None, subCommandsToRemove), false); } } diff --git a/playground/CommandInfoUpdater/GarnetCommandsDocs.json b/playground/CommandInfoUpdater/GarnetCommandsDocs.json index 039746b3aad..2ce569c4f83 100644 --- a/playground/CommandInfoUpdater/GarnetCommandsDocs.json +++ b/playground/CommandInfoUpdater/GarnetCommandsDocs.json @@ -40,164 +40,672 @@ ] }, { - "Command": "CLIENT_KILL", - "Name": "CLIENT|KILL", - "Summary": "Terminates open connections.", + "Command": "CLIENT", + "Name": "CLIENT", + "Summary": "A container for client connection commands.", "Group": "Connection", - "Complexity": "O(N) where N is the number of client connections", + "Complexity": "Depends on subcommand.", + "SubCommands": [ + { + "Command": "CLIENT_KILL", + "Name": "CLIENT|KILL", + "Summary": "Terminates open connections.", + "Group": "Connection", + "Complexity": "O(N) where N is the number of client connections", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "FILTER", + "Type": "OneOf", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "OLD-FORMAT", + "DisplayText": "ip:port", + "Type": "String" + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "NEW-FORMAT", + "Type": "OneOf", + "ArgumentFlags": "Multiple", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "CLIENT-ID", + "DisplayText": "client-id", + "Type": "Integer", + "Token": "ID", + "ArgumentFlags": "Optional" + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "CLIENT-TYPE", + "Type": "OneOf", + "Token": "TYPE", + "ArgumentFlags": "Optional", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NORMAL", + "DisplayText": "normal", + "Type": "PureToken", + "Token": "NORMAL" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "MASTER", + "DisplayText": "master", + "Type": "PureToken", + "Token": "MASTER" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "SLAVE", + "DisplayText": "slave", + "Type": "PureToken", + "Token": "SLAVE" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "REPLICA", + "DisplayText": "replica", + "Type": "PureToken", + "Token": "REPLICA" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "PUBSUB", + "DisplayText": "pubsub", + "Type": "PureToken", + "Token": "PUBSUB" + } + ] + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "USERNAME", + "DisplayText": "username", + "Type": "String", + "Token": "USER", + "ArgumentFlags": "Optional" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "ADDR", + "DisplayText": "ip:port", + "Type": "String", + "Token": "ADDR", + "ArgumentFlags": "Optional" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "LADDR", + "DisplayText": "ip:port", + "Type": "String", + "Token": "LADDR", + "ArgumentFlags": "Optional" + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "SKIPME", + "Type": "OneOf", + "Token": "SKIPME", + "ArgumentFlags": "Optional", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "YES", + "DisplayText": "yes", + "Type": "PureToken", + "Token": "YES" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NO", + "DisplayText": "no", + "Type": "PureToken", + "Token": "NO" + } + ] + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "MAXAGE", + "DisplayText": "maxage", + "Type": "Integer", + "Token": "MAXAGE", + "ArgumentFlags": "Optional" + } + ] + } + ] + } + ] + } + ] + }, + { + "Command": "COMMITAOF", + "Name": "COMMITAOF", + "Group": "Server", + "Summary": "Commit to append-only file.", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "DBID", + "DisplayText": "dbid", + "Type": "Integer", + "Token": "DBID", + "ArgumentFlags": "Optional" + } + ] + }, + { + "Command": "COSCAN", + "Name": "COSCAN", + "Group": "Generic", + "Summary": "Iterates over members of a collection object.", + "Complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "KEY", + "DisplayText": "key", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "CURSOR", + "DisplayText": "cursor", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "PATTERN", + "DisplayText": "pattern", + "Type": "Pattern", + "Token": "MATCH", + "ArgumentFlags": "Optional" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "COUNT", + "DisplayText": "count", + "Type": "Integer", + "Token": "COUNT", + "ArgumentFlags": "Optional" + } + ] + }, + { + "Command": "DELIFGREATER", + "Name": "DELIFGREATER", + "Summary": "Deletes a key only if the provided Etag is strictly greater than the existing Etag for the key.", + "Group": "Generic", + "Complexity": "O(1)", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "KEY", + "DisplayText": "key", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "ETAG", + "DisplayText": "etag", + "Type": "Integer" + } + ] + }, + { + "Command": "FORCEGC", + "Name": "FORCEGC", + "Summary": "Forces garbage collection.", + "Group": "Server" + }, + { + "Command": "HCOLLECT", + "Name": "HCOLLECT", + "Summary": "Manually trigger deletion of expired fields from memory", + "Group": "Hash" + }, + { + "Command": "HEXPIRE", + "Name": "HEXPIRE", + "Summary": "Set expiry for hash field using relative time to expire (seconds)", + "Group": "Hash", + "Complexity": "O(N) where N is the number of specified fields", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "KEY", + "DisplayText": "key", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "SECONDS", + "DisplayText": "seconds", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "CONDITION", + "Type": "OneOf", + "ArgumentFlags": "Optional", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NX", + "DisplayText": "nx", + "Type": "PureToken", + "Token": "NX" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "XX", + "DisplayText": "xx", + "Type": "PureToken", + "Token": "XX" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "GT", + "DisplayText": "gt", + "Type": "PureToken", + "Token": "GT" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "LT", + "DisplayText": "lt", + "Type": "PureToken", + "Token": "LT" + } + ] + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "FIELDS", + "Type": "Block", + "Token": "FIELDS", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NUMFIELDS", + "DisplayText": "numfields", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "FIELD", + "DisplayText": "field", + "Type": "String", + "ArgumentFlags": "Multiple" + } + ] + } + ] + }, + { + "Command": "HEXPIREAT", + "Name": "HEXPIREAT", + "Summary": "Set expiry for hash field using an absolute Unix timestamp (seconds)", + "Group": "Hash", + "Complexity": "O(N) where N is the number of specified fields", "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "KEY", + "DisplayText": "key", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "UNIX-TIME-SECONDS", + "DisplayText": "unix-time-seconds", + "Type": "UnixTime" + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "CONDITION", + "Type": "OneOf", + "ArgumentFlags": "Optional", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NX", + "DisplayText": "nx", + "Type": "PureToken", + "Token": "NX" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "XX", + "DisplayText": "xx", + "Type": "PureToken", + "Token": "XX" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "GT", + "DisplayText": "gt", + "Type": "PureToken", + "Token": "GT" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "LT", + "DisplayText": "lt", + "Type": "PureToken", + "Token": "LT" + } + ] + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "FIELDS", + "Type": "Block", + "Token": "FIELDS", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NUMFIELDS", + "DisplayText": "numfields", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "FIELD", + "DisplayText": "field", + "Type": "String", + "ArgumentFlags": "Multiple" + } + ] + } + ] + }, + { + "Command": "HEXPIRETIME", + "Name": "HEXPIRETIME", + "Summary": "Returns the expiration time of a hash field as a Unix timestamp, in seconds.", + "Group": "Hash", + "Complexity": "O(N) where N is the number of specified fields", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "KEY", + "DisplayText": "key", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "FIELDS", + "Type": "Block", + "Token": "FIELDS", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NUMFIELDS", + "DisplayText": "numfields", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "FIELD", + "DisplayText": "field", + "Type": "String", + "ArgumentFlags": "Multiple" + } + ] + } + ] + }, + { + "Command": "HPERSIST", + "Name": "HPERSIST", + "Summary": "Removes the expiration time for each specified field", + "Group": "Hash", + "Complexity": "O(N) where N is the number of specified fields", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "KEY", + "DisplayText": "key", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "FIELDS", + "Type": "Block", + "Token": "FIELDS", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NUMFIELDS", + "DisplayText": "numfields", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "FIELD", + "DisplayText": "field", + "Type": "String", + "ArgumentFlags": "Multiple" + } + ] + } + ] + }, + { + "Command": "HPEXPIRE", + "Name": "HPEXPIRE", + "Summary": "Set expiry for hash field using relative time to expire (milliseconds)", + "Group": "Hash", + "Complexity": "O(N) where N is the number of specified fields", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "KEY", + "DisplayText": "key", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "MILLISECONDS", + "DisplayText": "milliseconds", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "CONDITION", + "Type": "OneOf", + "ArgumentFlags": "Optional", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NX", + "DisplayText": "nx", + "Type": "PureToken", + "Token": "NX" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "XX", + "DisplayText": "xx", + "Type": "PureToken", + "Token": "XX" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "GT", + "DisplayText": "gt", + "Type": "PureToken", + "Token": "GT" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "LT", + "DisplayText": "lt", + "Type": "PureToken", + "Token": "LT" + } + ] + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "FIELDS", + "Type": "Block", + "Token": "FIELDS", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NUMFIELDS", + "DisplayText": "numfields", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "FIELD", + "DisplayText": "field", + "Type": "String", + "ArgumentFlags": "Multiple" + } + ] + } + ] + }, + { + "Command": "HPEXPIREAT", + "Name": "HPEXPIREAT", + "Summary": "Set expiry for hash field using an absolute Unix timestamp (milliseconds)", + "Group": "Hash", + "Complexity": "O(N) where N is the number of specified fields", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "KEY", + "DisplayText": "key", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "UNIX-TIME-MILLISECONDS", + "DisplayText": "unix-time-milliseconds", + "Type": "UnixTime" + }, { "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "FILTER", + "Name": "CONDITION", "Type": "OneOf", + "ArgumentFlags": "Optional", "Arguments": [ { "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "OLD-FORMAT", - "DisplayText": "ip:port", - "Type": "String" + "Name": "NX", + "DisplayText": "nx", + "Type": "PureToken", + "Token": "NX" }, { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "NEW-FORMAT", - "Type": "OneOf", - "ArgumentFlags": "Multiple", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "CLIENT-ID", - "DisplayText": "client-id", - "Type": "Integer", - "Token": "ID", - "ArgumentFlags": "Optional" - }, - { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "CLIENT-TYPE", - "Type": "OneOf", - "Token": "TYPE", - "ArgumentFlags": "Optional", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "NORMAL", - "DisplayText": "normal", - "Type": "PureToken", - "Token": "NORMAL" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "MASTER", - "DisplayText": "master", - "Type": "PureToken", - "Token": "MASTER" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "SLAVE", - "DisplayText": "slave", - "Type": "PureToken", - "Token": "SLAVE" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "REPLICA", - "DisplayText": "replica", - "Type": "PureToken", - "Token": "REPLICA" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "PUBSUB", - "DisplayText": "pubsub", - "Type": "PureToken", - "Token": "PUBSUB" - } - ] - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "USERNAME", - "DisplayText": "username", - "Type": "String", - "Token": "USER", - "ArgumentFlags": "Optional" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "ADDR", - "DisplayText": "ip:port", - "Type": "String", - "Token": "ADDR", - "ArgumentFlags": "Optional" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "LADDR", - "DisplayText": "ip:port", - "Type": "String", - "Token": "LADDR", - "ArgumentFlags": "Optional" - }, - { - "TypeDiscriminator": "RespCommandContainerArgument", - "Name": "SKIPME", - "Type": "OneOf", - "Token": "SKIPME", - "ArgumentFlags": "Optional", - "Arguments": [ - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "YES", - "DisplayText": "yes", - "Type": "PureToken", - "Token": "YES" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "NO", - "DisplayText": "no", - "Type": "PureToken", - "Token": "NO" - } - ] - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "MAXAGE", - "DisplayText": "maxage", - "Type": "Integer", - "Token": "MAXAGE", - "ArgumentFlags": "Optional" - } - ] + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "XX", + "DisplayText": "xx", + "Type": "PureToken", + "Token": "XX" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "GT", + "DisplayText": "gt", + "Type": "PureToken", + "Token": "GT" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "LT", + "DisplayText": "lt", + "Type": "PureToken", + "Token": "LT" + } + ] + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "FIELDS", + "Type": "Block", + "Token": "FIELDS", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NUMFIELDS", + "DisplayText": "numfields", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "FIELD", + "DisplayText": "field", + "Type": "String", + "ArgumentFlags": "Multiple" } ] } ] }, { - "Command": "COMMITAOF", - "Name": "COMMITAOF", - "Group": "Server", - "Summary": "Commit to append-only file.", + "Command": "HPEXPIRETIME", + "Name": "HPEXPIRETIME", + "Summary": "Returns the expiration time of a hash field as a Unix timestamp, in msec.", + "Group": "Hash", + "Complexity": "O(N) where N is the number of specified fields", "Arguments": [ { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "DBID", - "DisplayText": "dbid", - "Type": "Integer", - "Token": "DBID", - "ArgumentFlags": "Optional" + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "KEY", + "DisplayText": "key", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "FIELDS", + "Type": "Block", + "Token": "FIELDS", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NUMFIELDS", + "DisplayText": "numfields", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "FIELD", + "DisplayText": "field", + "Type": "String", + "ArgumentFlags": "Multiple" + } + ] } ] }, { - "Command": "COSCAN", - "Name": "COSCAN", - "Group": "Generic", - "Summary": "Iterates over members of a collection object.", - "Complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.", + "Command": "HPTTL", + "Name": "HPTTL", + "Summary": "Returns the TTL in milliseconds of a hash field.", + "Group": "Hash", + "Complexity": "O(N) where N is the number of specified fields", "Arguments": [ { "TypeDiscriminator": "RespCommandKeyArgument", @@ -207,35 +715,34 @@ "KeySpecIndex": 0 }, { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "CURSOR", - "DisplayText": "cursor", - "Type": "Integer" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "PATTERN", - "DisplayText": "pattern", - "Type": "Pattern", - "Token": "MATCH", - "ArgumentFlags": "Optional" - }, - { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "COUNT", - "DisplayText": "count", - "Type": "Integer", - "Token": "COUNT", - "ArgumentFlags": "Optional" + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "FIELDS", + "Type": "Block", + "Token": "FIELDS", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NUMFIELDS", + "DisplayText": "numfields", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "FIELD", + "DisplayText": "field", + "Type": "String", + "ArgumentFlags": "Multiple" + } + ] } ] }, { - "Command": "DELIFGREATER", - "Name": "DELIFGREATER", - "Summary": "Deletes a key only if the provided Etag is strictly greater than the existing Etag for the key.", - "Group": "Generic", - "Complexity": "O(1)", + "Command": "HTTL", + "Name": "HTTL", + "Summary": "Returns the TTL in seconds of a hash field.", + "Group": "Hash", + "Complexity": "O(N) where N is the number of specified fields", "Arguments": [ { "TypeDiscriminator": "RespCommandKeyArgument", @@ -245,25 +752,28 @@ "KeySpecIndex": 0 }, { - "TypeDiscriminator": "RespCommandBasicArgument", - "Name": "ETAG", - "DisplayText": "etag", - "Type": "Integer" + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "FIELDS", + "Type": "Block", + "Token": "FIELDS", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NUMFIELDS", + "DisplayText": "numfields", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "FIELD", + "DisplayText": "field", + "Type": "String", + "ArgumentFlags": "Multiple" + } + ] } ] }, - { - "Command": "FORCEGC", - "Name": "FORCEGC", - "Summary": "Forces garbage collection.", - "Group": "Server" - }, - { - "Command": "HCOLLECT", - "Name": "HCOLLECT", - "Summary": "Manually trigger deletion of expired fields from memory", - "Group": "Hash" - }, { "Command": "SECONDARYOF", "Name": "SECONDARYOF", @@ -1441,5 +1951,42 @@ ] } ] + }, + { + "Command": "ZTTL", + "Name": "ZTTL", + "Summary": "Returns the TTL in seconds of a sorted set member.", + "Group": "SortedSet", + "Complexity": "O(N) where N is the number of specified members", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandKeyArgument", + "Name": "KEY", + "DisplayText": "key", + "Type": "Key", + "KeySpecIndex": 0 + }, + { + "TypeDiscriminator": "RespCommandContainerArgument", + "Name": "MEMBERS", + "Type": "Block", + "Token": "MEMBERS", + "Arguments": [ + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "NUMMEMBERS", + "DisplayText": "nummembers", + "Type": "Integer" + }, + { + "TypeDiscriminator": "RespCommandBasicArgument", + "Name": "MEMBER", + "DisplayText": "member", + "Type": "String", + "ArgumentFlags": "Multiple" + } + ] + } + ] } ] \ No newline at end of file diff --git a/playground/CommandInfoUpdater/GarnetCommandsInfo.json b/playground/CommandInfoUpdater/GarnetCommandsInfo.json index 9d8e4473ab3..52786d649d8 100644 --- a/playground/CommandInfoUpdater/GarnetCommandsInfo.json +++ b/playground/CommandInfoUpdater/GarnetCommandsInfo.json @@ -51,6 +51,14 @@ "KeySpecifications": null, "SubCommands": null }, + { + "Command": "CLUSTER_ATTACH_SYNC", + "Name": "CLUSTER|ATTACH_SYNC", + "IsInternal": true, + "Arity": 3, + "Flags": "Admin, NoMulti, NoScript", + "AclCategories": "Admin, Dangerous, Slow, Garnet" + }, { "Command": "CLUSTER_BANLIST", "Name": "CLUSTER|BANLIST", @@ -321,6 +329,14 @@ "AclCategories": "Admin, Dangerous, Slow, Garnet", "KeySpecifications": null, "SubCommands": null + }, + { + "Command": "CLUSTER_SYNC", + "Name": "CLUSTER|SYNC", + "IsInternal": true, + "Arity": 4, + "Flags": "Admin, NoMulti, NoScript", + "AclCategories": "Admin, Dangerous, Slow, Garnet" } ] }, @@ -443,31 +459,6 @@ "KeySpecifications": null, "SubCommands": null }, - { - "Command": "HCOLLECT", - "Name": "HCOLLECT", - "Arity": 2, - "Flags": "Admin, Write", - "FirstKey": 1, - "LastKey": 1, - "Step": 1, - "AclCategories": "Admin, Hash, Write, Garnet", - "KeySpecifications": [ - { - "BeginSearch": { - "TypeDiscriminator": "BeginSearchIndex", - "Index": 1 - }, - "FindKeys": { - "TypeDiscriminator": "FindKeysRange", - "LastKey": 0, - "KeyStep": 1, - "Limit": 0 - }, - "Flags": "RW, Access, Update" - } - ] - }, { "Command": "LATENCY", "Name": "LATENCY", @@ -690,6 +681,256 @@ } ] }, + { + "Command": "HCOLLECT", + "Name": "HCOLLECT", + "Arity": 2, + "Flags": "Admin, Write", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Admin, Hash, Write, Garnet", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RW, Access, Update" + } + ] + }, + { + "Command": "HEXPIRE", + "Name": "HEXPIRE", + "Arity": -6, + "Flags": "DenyOom, Fast, Write", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Hash, Fast, Write", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RW, Update" + } + ] + }, + { + "Command": "HEXPIREAT", + "Name": "HEXPIREAT", + "Arity": -6, + "Flags": "DenyOom, Fast, Write", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Hash, Fast, Write", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RW, Update" + } + ] + }, + { + "Command": "HPERSIST", + "Name": "HPERSIST", + "Arity": -5, + "Flags": "Fast, Write", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Hash, Fast, Write", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RW, Update" + } + ] + }, + { + "Command": "HEXPIRETIME", + "Name": "HEXPIRETIME", + "Arity": -5, + "Flags": "Fast, ReadOnly", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Hash, Fast, Read", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RO, Access" + } + ] + }, + { + "Command": "HPEXPIRE", + "Name": "HPEXPIRE", + "Arity": -6, + "Flags": "DenyOom, Fast, Write", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Hash, Fast, Write", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RW, Update" + } + ] + }, + { + "Command": "HPEXPIREAT", + "Name": "HPEXPIREAT", + "Arity": -6, + "Flags": "DenyOom, Fast, Write", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Hash, Fast, Write", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RW, Update" + } + ] + }, + { + "Command": "HPEXPIRETIME", + "Name": "HPEXPIRETIME", + "Arity": -5, + "Flags": "Fast, ReadOnly", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Hash, Fast, Read", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RO, Access" + } + ] + }, + { + "Command": "HPTTL", + "Name": "HPTTL", + "Arity": -5, + "Flags": "Fast, ReadOnly", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Hash, Fast, Read", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RO, Access" + } + ] + }, + { + "Command": "HTTL", + "Name": "HTTL", + "Arity": -5, + "Flags": "Fast, ReadOnly", + "FirstKey": 1, + "LastKey": 1, + "Step": 1, + "AclCategories": "Hash, Fast, Read", + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RO, Access" + } + ] + }, { "Command": "LASTSAVE", "Name": "LASTSAVE", diff --git a/playground/CommandInfoUpdater/RespCommandInfoParser.cs b/playground/CommandInfoUpdater/RespCommandInfoParser.cs index 1927b730cde..c34a4469f89 100644 --- a/playground/CommandInfoUpdater/RespCommandInfoParser.cs +++ b/playground/CommandInfoUpdater/RespCommandInfoParser.cs @@ -15,11 +15,11 @@ public class RespCommandInfoParser /// /// Pointer to current RESP chunk to read /// Pointer to end of RESP chunk to read - /// Mapping between command name and Garnet RespCommand and ArrayCommand values + /// Mapping between command name, Garnet RespCommand and StoreType /// Parsed RespCommandsInfo object /// Name of parent command, null if none /// True if parsing successful - public static unsafe bool TryReadFromResp(ref byte* ptr, byte* end, IReadOnlyDictionary supportedCommands, out RespCommandsInfo commandInfo, string parentCommand = null) + public static unsafe bool TryReadFromResp(ref byte* ptr, byte* end, IReadOnlyDictionary supportedCommands, out RespCommandsInfo commandInfo, string parentCommand = null) { commandInfo = default; @@ -98,9 +98,10 @@ public static unsafe bool TryReadFromResp(ref byte* ptr, byte* end, IReadOnlyDic subCommands.Add(commandInfo); } + var supportedCommand = supportedCommands.GetValueOrDefault(name, (RespCommand.NONE, StoreType.None)); commandInfo = new RespCommandsInfo() { - Command = supportedCommands.GetValueOrDefault(name, RespCommand.NONE), + Command = supportedCommand.Item1, Name = name.ToUpper(), IsInternal = false, Arity = arity, @@ -111,6 +112,7 @@ public static unsafe bool TryReadFromResp(ref byte* ptr, byte* end, IReadOnlyDic AclCategories = aclCategories, Tips = tips.Length == 0 ? null : tips, KeySpecifications = keySpecifications.Length == 0 ? null : keySpecifications, + StoreType = supportedCommand.Item2, SubCommands = subCommands.Count == 0 ? null : [.. subCommands.OrderBy(sc => sc.Name)] }; diff --git a/playground/CommandInfoUpdater/SupportedCommand.cs b/playground/CommandInfoUpdater/SupportedCommand.cs index 89d9a8da159..a1a61b79234 100644 --- a/playground/CommandInfoUpdater/SupportedCommand.cs +++ b/playground/CommandInfoUpdater/SupportedCommand.cs @@ -12,7 +12,7 @@ namespace CommandInfoUpdater public class SupportedCommand { private static readonly SupportedCommand[] AllSupportedCommands = [ - new("ACL", RespCommand.ACL, + new("ACL", RespCommand.ACL, StoreType.None, [ new("ACL|CAT", RespCommand.ACL_CAT), new("ACL|DELUSER", RespCommand.ACL_DELUSER), @@ -26,25 +26,25 @@ public class SupportedCommand new("ACL|WHOAMI", RespCommand.ACL_WHOAMI), ]), new("EXPDELSCAN", RespCommand.EXPDELSCAN), - new("APPEND", RespCommand.APPEND), + new("APPEND", RespCommand.APPEND, StoreType.Main), new("ASKING", RespCommand.ASKING), new("ASYNC", RespCommand.ASYNC), new("AUTH", RespCommand.AUTH), new("BGSAVE", RespCommand.BGSAVE), - new("BITCOUNT", RespCommand.BITCOUNT), - new("BITFIELD", RespCommand.BITFIELD), - new("BITFIELD_RO", RespCommand.BITFIELD_RO), - new("BITOP", RespCommand.BITOP), - new("BITPOS", RespCommand.BITPOS), - new("BLPOP", RespCommand.BLPOP), - new("BRPOP", RespCommand.BRPOP), - new("BLMOVE", RespCommand.BLMOVE), - new("BRPOPLPUSH", RespCommand.BRPOPLPUSH), - new("BZMPOP", RespCommand.BZMPOP), - new("BZPOPMAX", RespCommand.BZPOPMAX), - new("BZPOPMIN", RespCommand.BZPOPMIN), - new("BLMPOP", RespCommand.BLMPOP), - new("CLIENT", RespCommand.CLIENT, + new("BITCOUNT", RespCommand.BITCOUNT, StoreType.Main), + new("BITFIELD", RespCommand.BITFIELD, StoreType.Main), + new("BITFIELD_RO", RespCommand.BITFIELD_RO, StoreType.Main), + new("BITOP", RespCommand.BITOP, StoreType.Main), + new("BITPOS", RespCommand.BITPOS, StoreType.Main), + new("BLPOP", RespCommand.BLPOP, StoreType.Object), + new("BRPOP", RespCommand.BRPOP, StoreType.Object), + new("BLMOVE", RespCommand.BLMOVE, StoreType.Object), + new("BRPOPLPUSH", RespCommand.BRPOPLPUSH, StoreType.Object), + new("BZMPOP", RespCommand.BZMPOP, StoreType.Object), + new("BZPOPMAX", RespCommand.BZPOPMAX, StoreType.Object), + new("BZPOPMIN", RespCommand.BZPOPMIN, StoreType.Object), + new("BLMPOP", RespCommand.BLMPOP, StoreType.Object), + new("CLIENT", RespCommand.CLIENT, StoreType.None, [ new("CLIENT|ID", RespCommand.CLIENT_ID), new("CLIENT|INFO", RespCommand.CLIENT_INFO), @@ -55,7 +55,7 @@ public class SupportedCommand new("CLIENT|SETINFO", RespCommand.CLIENT_SETINFO), new("CLIENT|UNBLOCK", RespCommand.CLIENT_UNBLOCK), ]), - new("CLUSTER", RespCommand.CLUSTER, + new("CLUSTER", RespCommand.CLUSTER, StoreType.None, [ new("CLUSTER|ADDSLOTS", RespCommand.CLUSTER_ADDSLOTS), new("CLUSTER|ADDSLOTSRANGE", RespCommand.CLUSTER_ADDSLOTSRANGE), @@ -103,7 +103,7 @@ public class SupportedCommand new("CLUSTER|SLOTSTATE", RespCommand.CLUSTER_SLOTSTATE), new("CLUSTER|SYNC", RespCommand.CLUSTER_SYNC), ]), - new("COMMAND", RespCommand.COMMAND, + new("COMMAND", RespCommand.COMMAND, StoreType.None, [ new("COMMAND|INFO", RespCommand.COMMAND_INFO), new("COMMAND|COUNT", RespCommand.COMMAND_COUNT), @@ -112,135 +112,134 @@ public class SupportedCommand new("COMMAND|GETKEYSANDFLAGS", RespCommand.COMMAND_GETKEYSANDFLAGS), ]), new("COMMITAOF", RespCommand.COMMITAOF), - new("CONFIG", RespCommand.CONFIG, + new("CONFIG", RespCommand.CONFIG, StoreType.None, [ new("CONFIG|GET", RespCommand.CONFIG_GET), new("CONFIG|SET", RespCommand.CONFIG_SET), new("CONFIG|REWRITE", RespCommand.CONFIG_REWRITE), ]), - new("COSCAN", RespCommand.COSCAN), - new("CustomRawStringCmd", RespCommand.CustomRawStringCmd), - new("CustomObjCmd", RespCommand.CustomObjCmd), + new("COSCAN", RespCommand.COSCAN, StoreType.Object), + new("CustomRawStringCmd", RespCommand.CustomRawStringCmd, StoreType.Main), + new("CustomObjCmd", RespCommand.CustomObjCmd, StoreType.Object), new("CustomTxn", RespCommand.CustomTxn), new("CustomProcedure", RespCommand.CustomProcedure), new("DBSIZE", RespCommand.DBSIZE), new("DEBUG", RespCommand.DEBUG), - new("DECR", RespCommand.DECR), - new("DECRBY", RespCommand.DECRBY), - new("DEL", RespCommand.DEL), - new("DELIFEXPIM", RespCommand.DELIFEXPIM), - new("DELIFGREATER", RespCommand.DELIFGREATER), + new("DECR", RespCommand.DECR, StoreType.Main), + new("DECRBY", RespCommand.DECRBY, StoreType.Main), + new("DEL", RespCommand.DEL, StoreType.All), + new("DELIFGREATER", RespCommand.DELIFGREATER, StoreType.Main), new("DISCARD", RespCommand.DISCARD), - new("DUMP", RespCommand.DUMP), + new("DUMP", RespCommand.DUMP, StoreType.All), new("ECHO", RespCommand.ECHO), new("EXEC", RespCommand.EXEC), - new("EXISTS", RespCommand.EXISTS), - new("EXPIRE", RespCommand.EXPIRE), - new("EXPIREAT", RespCommand.EXPIREAT), - new("EXPIRETIME", RespCommand.EXPIRETIME), + new("EXISTS", RespCommand.EXISTS, StoreType.All), + new("EXPIRE", RespCommand.EXPIRE, StoreType.All), + new("EXPIREAT", RespCommand.EXPIREAT, StoreType.All), + new("EXPIRETIME", RespCommand.EXPIRETIME, StoreType.All), new("FAILOVER", RespCommand.FAILOVER), new("FLUSHALL", RespCommand.FLUSHALL), new("FLUSHDB", RespCommand.FLUSHDB), new("FORCEGC", RespCommand.FORCEGC), - new("GEOADD", RespCommand.GEOADD), - new("GEODIST", RespCommand.GEODIST), - new("GEOHASH", RespCommand.GEOHASH), - new("GEOPOS", RespCommand.GEOPOS), - new("GEORADIUS", RespCommand.GEORADIUS), - new("GEORADIUS_RO", RespCommand.GEORADIUS_RO), - new("GEORADIUSBYMEMBER", RespCommand.GEORADIUSBYMEMBER), - new("GEORADIUSBYMEMBER_RO", RespCommand.GEORADIUSBYMEMBER_RO), - new("GEOSEARCH", RespCommand.GEOSEARCH), - new("GEOSEARCHSTORE", RespCommand.GEOSEARCHSTORE), - new("GET", RespCommand.GET), - new("GETEX", RespCommand.GETEX), - new("GETBIT", RespCommand.GETBIT), - new("GETDEL", RespCommand.GETDEL), - new("GETIFNOTMATCH", RespCommand.GETIFNOTMATCH), - new("GETRANGE", RespCommand.GETRANGE), - new("GETWITHETAG", RespCommand.GETWITHETAG), - new("GETSET", RespCommand.GETSET), - new("HCOLLECT", RespCommand.HCOLLECT), - new("HDEL", RespCommand.HDEL), + new("GEOADD", RespCommand.GEOADD, StoreType.Object), + new("GEODIST", RespCommand.GEODIST, StoreType.Object), + new("GEOHASH", RespCommand.GEOHASH, StoreType.Object), + new("GEOPOS", RespCommand.GEOPOS, StoreType.Object), + new("GEORADIUS", RespCommand.GEORADIUS, StoreType.Object), + new("GEORADIUS_RO", RespCommand.GEORADIUS_RO, StoreType.Object), + new("GEORADIUSBYMEMBER", RespCommand.GEORADIUSBYMEMBER, StoreType.Object), + new("GEORADIUSBYMEMBER_RO", RespCommand.GEORADIUSBYMEMBER_RO, StoreType.Object), + new("GEOSEARCH", RespCommand.GEOSEARCH, StoreType.Object), + new("GEOSEARCHSTORE", RespCommand.GEOSEARCHSTORE, StoreType.Object), + new("GET", RespCommand.GET, StoreType.Main), + new("GETEX", RespCommand.GETEX, StoreType.Main), + new("GETBIT", RespCommand.GETBIT, StoreType.Main), + new("GETDEL", RespCommand.GETDEL, StoreType.Main), + new("GETIFNOTMATCH", RespCommand.GETIFNOTMATCH, StoreType.Main), + new("GETRANGE", RespCommand.GETRANGE, StoreType.Main), + new("GETWITHETAG", RespCommand.GETWITHETAG, StoreType.Main), + new("GETSET", RespCommand.GETSET, StoreType.Main), + new("HCOLLECT", RespCommand.HCOLLECT, StoreType.Object), + new("HDEL", RespCommand.HDEL, StoreType.Object), new("HELLO", RespCommand.HELLO), - new("HEXISTS", RespCommand.HEXISTS), - new("HEXPIRE", RespCommand.HEXPIRE), - new("HPEXPIRE", RespCommand.HPEXPIRE), - new("HEXPIREAT", RespCommand.HEXPIREAT), - new("HPEXPIREAT", RespCommand.HPEXPIREAT), - new("HTTL", RespCommand.HTTL), - new("HPTTL", RespCommand.HPTTL), - new("HEXPIRETIME", RespCommand.HEXPIRETIME), - new("HPEXPIRETIME", RespCommand.HPEXPIRETIME), - new("HPERSIST", RespCommand.HPERSIST), - new("HGET", RespCommand.HGET), - new("HGETALL", RespCommand.HGETALL), - new("HINCRBY", RespCommand.HINCRBY), - new("HINCRBYFLOAT", RespCommand.HINCRBYFLOAT), - new("HKEYS", RespCommand.HKEYS), - new("HLEN", RespCommand.HLEN), - new("HMGET", RespCommand.HMGET), - new("HMSET", RespCommand.HMSET), - new("HRANDFIELD", RespCommand.HRANDFIELD), - new("HSCAN", RespCommand.HSCAN), - new("HSET", RespCommand.HSET), - new("HSETNX", RespCommand.HSETNX), - new("HSTRLEN", RespCommand.HSTRLEN), - new("HVALS", RespCommand.HVALS), - new("INCR", RespCommand.INCR), - new("INCRBY", RespCommand.INCRBY), - new("INCRBYFLOAT", RespCommand.INCRBYFLOAT), + new("HEXISTS", RespCommand.HEXISTS, StoreType.Object), + new("HEXPIRE", RespCommand.HEXPIRE, StoreType.Object), + new("HPEXPIRE", RespCommand.HPEXPIRE, StoreType.Object), + new("HEXPIREAT", RespCommand.HEXPIREAT, StoreType.Object), + new("HPEXPIREAT", RespCommand.HPEXPIREAT, StoreType.Object), + new("HTTL", RespCommand.HTTL, StoreType.Object), + new("HPTTL", RespCommand.HPTTL, StoreType.Object), + new("HEXPIRETIME", RespCommand.HEXPIRETIME, StoreType.Object), + new("HPEXPIRETIME", RespCommand.HPEXPIRETIME, StoreType.Object), + new("HPERSIST", RespCommand.HPERSIST, StoreType.Object), + new("HGET", RespCommand.HGET, StoreType.Object), + new("HGETALL", RespCommand.HGETALL, StoreType.Object), + new("HINCRBY", RespCommand.HINCRBY, StoreType.Object), + new("HINCRBYFLOAT", RespCommand.HINCRBYFLOAT, StoreType.Object), + new("HKEYS", RespCommand.HKEYS, StoreType.Object), + new("HLEN", RespCommand.HLEN, StoreType.Object), + new("HMGET", RespCommand.HMGET, StoreType.Object), + new("HMSET", RespCommand.HMSET, StoreType.Object), + new("HRANDFIELD", RespCommand.HRANDFIELD, StoreType.Object), + new("HSCAN", RespCommand.HSCAN, StoreType.Object), + new("HSET", RespCommand.HSET, StoreType.Object), + new("HSETNX", RespCommand.HSETNX, StoreType.Object), + new("HSTRLEN", RespCommand.HSTRLEN, StoreType.Object), + new("HVALS", RespCommand.HVALS, StoreType.Object), + new("INCR", RespCommand.INCR, StoreType.Main), + new("INCRBY", RespCommand.INCRBY, StoreType.Main), + new("INCRBYFLOAT", RespCommand.INCRBYFLOAT, StoreType.Main), new("INFO", RespCommand.INFO), new("KEYS", RespCommand.KEYS), - new("LCS", RespCommand.LCS), + new("LCS", RespCommand.LCS, StoreType.Main), new("LASTSAVE", RespCommand.LASTSAVE), - new("LATENCY", RespCommand.LATENCY, + new("LATENCY", RespCommand.LATENCY, StoreType.None, [ new("LATENCY|HELP", RespCommand.LATENCY_HELP), new("LATENCY|HISTOGRAM", RespCommand.LATENCY_HISTOGRAM), new("LATENCY|RESET", RespCommand.LATENCY_RESET), ]), - new("LINDEX", RespCommand.LINDEX), - new("LINSERT", RespCommand.LINSERT), - new("LLEN", RespCommand.LLEN), - new("LMOVE", RespCommand.LMOVE), - new("LMPOP", RespCommand.LMPOP), - new("LPOP", RespCommand.LPOP), - new("LPOS", RespCommand.LPOS), - new("LPUSH", RespCommand.LPUSH), - new("LPUSHX", RespCommand.LPUSHX), - new("LRANGE", RespCommand.LRANGE), - new("LREM", RespCommand.LREM), - new("LSET", RespCommand.LSET), - new("LTRIM", RespCommand.LTRIM), - new("MEMORY", RespCommand.MEMORY, + new("LINDEX", RespCommand.LINDEX, StoreType.Object), + new("LINSERT", RespCommand.LINSERT, StoreType.Object), + new("LLEN", RespCommand.LLEN, StoreType.Object), + new("LMOVE", RespCommand.LMOVE, StoreType.Object), + new("LMPOP", RespCommand.LMPOP, StoreType.Object), + new("LPOP", RespCommand.LPOP, StoreType.Object), + new("LPOS", RespCommand.LPOS, StoreType.Object), + new("LPUSH", RespCommand.LPUSH, StoreType.Object), + new("LPUSHX", RespCommand.LPUSHX, StoreType.Object), + new("LRANGE", RespCommand.LRANGE, StoreType.Object), + new("LREM", RespCommand.LREM, StoreType.Object), + new("LSET", RespCommand.LSET, StoreType.Object), + new("LTRIM", RespCommand.LTRIM, StoreType.Object), + new("MEMORY", RespCommand.MEMORY, StoreType.None, [ new("MEMORY|USAGE", RespCommand.MEMORY_USAGE), ]), - new("MGET", RespCommand.MGET), + new("MGET", RespCommand.MGET, StoreType.Main), new("MIGRATE", RespCommand.MIGRATE), new("PURGEBP", RespCommand.PURGEBP), - new("MODULE", RespCommand.MODULE, + new("MODULE", RespCommand.MODULE, StoreType.None, [ new("MODULE|LOADCS", RespCommand.MODULE_LOADCS), ]), new("MONITOR", RespCommand.MONITOR), - new("MSET", RespCommand.MSET), - new("MSETNX", RespCommand.MSETNX), + new("MSET", RespCommand.MSET, StoreType.Main), + new("MSETNX", RespCommand.MSETNX, StoreType.Main), new("MULTI", RespCommand.MULTI), - new("PERSIST", RespCommand.PERSIST), - new("PEXPIRE", RespCommand.PEXPIRE), - new("PEXPIREAT", RespCommand.PEXPIREAT), - new("PEXPIRETIME", RespCommand.PEXPIRETIME), - new("PFADD", RespCommand.PFADD), - new("PFCOUNT", RespCommand.PFCOUNT), - new("PFMERGE", RespCommand.PFMERGE), + new("PERSIST", RespCommand.PERSIST, StoreType.All), + new("PEXPIRE", RespCommand.PEXPIRE, StoreType.All), + new("PEXPIREAT", RespCommand.PEXPIREAT, StoreType.All), + new("PEXPIRETIME", RespCommand.PEXPIRETIME, StoreType.All), + new("PFADD", RespCommand.PFADD, StoreType.Main), + new("PFCOUNT", RespCommand.PFCOUNT, StoreType.Main), + new("PFMERGE", RespCommand.PFMERGE, StoreType.Main), new("PING", RespCommand.PING), - new("PSETEX", RespCommand.PSETEX), + new("PSETEX", RespCommand.PSETEX, StoreType.Main), new("PSUBSCRIBE", RespCommand.PSUBSCRIBE), - new("PTTL", RespCommand.PTTL), + new("PTTL", RespCommand.PTTL, StoreType.All), new("PUBLISH", RespCommand.PUBLISH), - new("PUBSUB", RespCommand.PUBSUB, + new("PUBSUB", RespCommand.PUBSUB, StoreType.None, [ new("PUBSUB|CHANNELS", RespCommand.PUBSUB_CHANNELS), new("PUBSUB|NUMPAT", RespCommand.PUBSUB_NUMPAT), @@ -251,112 +250,112 @@ public class SupportedCommand new("QUIT", RespCommand.QUIT), new("READONLY", RespCommand.READONLY), new("READWRITE", RespCommand.READWRITE), - new("RENAME", RespCommand.RENAME), - new("RESTORE", RespCommand.RESTORE), - new("RENAMENX", RespCommand.RENAMENX), + new("RENAME", RespCommand.RENAME, StoreType.All), + new("RESTORE", RespCommand.RESTORE, StoreType.All), + new("RENAMENX", RespCommand.RENAMENX, StoreType.All), new("REPLICAOF", RespCommand.REPLICAOF), new("ROLE", RespCommand.ROLE), - new("RPOP", RespCommand.RPOP), - new("RPOPLPUSH", RespCommand.RPOPLPUSH), - new("RPUSH", RespCommand.RPUSH), - new("RPUSHX", RespCommand.RPUSHX), + new("RPOP", RespCommand.RPOP, StoreType.Object), + new("RPOPLPUSH", RespCommand.RPOPLPUSH, StoreType.Object), + new("RPUSH", RespCommand.RPUSH, StoreType.Object), + new("RPUSHX", RespCommand.RPUSHX, StoreType.Object), new("RUNTXP", RespCommand.RUNTXP), - new("SADD", RespCommand.SADD), - new("SCARD", RespCommand.SCARD), + new("SADD", RespCommand.SADD, StoreType.Object), + new("SCARD", RespCommand.SCARD, StoreType.Object), new("SAVE", RespCommand.SAVE), - new("SCAN", RespCommand.SCAN), - new("SDIFF", RespCommand.SDIFF), - new("SDIFFSTORE", RespCommand.SDIFFSTORE), + new("SCAN", RespCommand.SCAN, StoreType.All), + new("SDIFF", RespCommand.SDIFF, StoreType.Object), + new("SDIFFSTORE", RespCommand.SDIFFSTORE, StoreType.Object), new("SECONDARYOF", RespCommand.SECONDARYOF), new("SELECT", RespCommand.SELECT), - new("SET", RespCommand.SET), - new("SETBIT", RespCommand.SETBIT), - new("SETEX", RespCommand.SETEX), - new("SETIFMATCH", RespCommand.SETIFMATCH), - new("SETIFGREATER", RespCommand.SETIFGREATER), - new("SETNX", RespCommand.SETNX), - new("SETRANGE", RespCommand.SETRANGE), - new("SISMEMBER", RespCommand.SISMEMBER), + new("SET", RespCommand.SET, StoreType.Main), + new("SETBIT", RespCommand.SETBIT, StoreType.Main), + new("SETEX", RespCommand.SETEX, StoreType.Main), + new("SETIFMATCH", RespCommand.SETIFMATCH, StoreType.Main), + new("SETIFGREATER", RespCommand.SETIFGREATER, StoreType.Main), + new("SETNX", RespCommand.SETNX, StoreType.Main), + new("SETRANGE", RespCommand.SETRANGE, StoreType.Main), + new("SISMEMBER", RespCommand.SISMEMBER, StoreType.Object), new("SLAVEOF", RespCommand.SECONDARYOF), - new("SLOWLOG", RespCommand.SLOWLOG, + new("SLOWLOG", RespCommand.SLOWLOG, StoreType.None, [ new("SLOWLOG|GET", RespCommand.SLOWLOG_GET), new("SLOWLOG|LEN", RespCommand.SLOWLOG_LEN), new("SLOWLOG|RESET", RespCommand.SLOWLOG_RESET), new("SLOWLOG|HELP", RespCommand.SLOWLOG_HELP), ]), - new("SMEMBERS", RespCommand.SMEMBERS), - new("SMISMEMBER", RespCommand.SMISMEMBER), - new("SMOVE", RespCommand.SMOVE), - new("SPOP", RespCommand.SPOP), + new("SMEMBERS", RespCommand.SMEMBERS, StoreType.Object), + new("SMISMEMBER", RespCommand.SMISMEMBER, StoreType.Object), + new("SMOVE", RespCommand.SMOVE, StoreType.Object), + new("SPOP", RespCommand.SPOP, StoreType.Object), new("SPUBLISH", RespCommand.SPUBLISH), - new("SRANDMEMBER", RespCommand.SRANDMEMBER), - new("SREM", RespCommand.SREM), - new("SSCAN", RespCommand.SSCAN), - new("STRLEN", RespCommand.STRLEN), + new("SRANDMEMBER", RespCommand.SRANDMEMBER, StoreType.Object), + new("SREM", RespCommand.SREM, StoreType.Object), + new("SSCAN", RespCommand.SSCAN, StoreType.Object), + new("STRLEN", RespCommand.STRLEN, StoreType.Main), new("SUBSCRIBE", RespCommand.SUBSCRIBE), new("SSUBSCRIBE", RespCommand.SSUBSCRIBE), - new("SUBSTR", RespCommand.SUBSTR), - new("SUNION", RespCommand.SUNION), - new("SUNIONSTORE", RespCommand.SUNIONSTORE), - new("SINTER", RespCommand.SINTER), - new("SINTERCARD", RespCommand.SINTERCARD), - new("SINTERSTORE", RespCommand.SINTERSTORE), + new("SUBSTR", RespCommand.SUBSTR, StoreType.Main), + new("SUNION", RespCommand.SUNION, StoreType.Object), + new("SUNIONSTORE", RespCommand.SUNIONSTORE, StoreType.Object), + new("SINTER", RespCommand.SINTER, StoreType.Object), + new("SINTERCARD", RespCommand.SINTERCARD, StoreType.Object), + new("SINTERSTORE", RespCommand.SINTERSTORE, StoreType.Object), new("SWAPDB", RespCommand.SWAPDB), new("TIME", RespCommand.TIME), - new("TTL", RespCommand.TTL), - new("TYPE", RespCommand.TYPE), - new("UNLINK", RespCommand.UNLINK), + new("TTL", RespCommand.TTL, StoreType.All), + new("TYPE", RespCommand.TYPE, StoreType.All), + new("UNLINK", RespCommand.UNLINK, StoreType.All), new("UNSUBSCRIBE", RespCommand.UNSUBSCRIBE), new("UNWATCH", RespCommand.UNWATCH), new("WATCH", RespCommand.WATCH), new("WATCHMS", RespCommand.WATCHMS), new("WATCHOS", RespCommand.WATCHOS), - new("ZADD", RespCommand.ZADD), - new("ZCARD", RespCommand.ZCARD), - new("ZCOUNT", RespCommand.ZCOUNT), - new("ZDIFF", RespCommand.ZDIFF), - new("ZDIFFSTORE", RespCommand.ZDIFFSTORE), - new("ZINCRBY", RespCommand.ZINCRBY), - new("ZINTER", RespCommand.ZINTER), - new("ZINTERCARD", RespCommand.ZINTERCARD), - new("ZINTERSTORE", RespCommand.ZINTERSTORE), - new("ZLEXCOUNT", RespCommand.ZLEXCOUNT), - new("ZMSCORE", RespCommand.ZMSCORE), - new("ZMPOP", RespCommand.ZMPOP), - new("ZPOPMAX", RespCommand.ZPOPMAX), - new("ZPOPMIN", RespCommand.ZPOPMIN), - new("ZRANDMEMBER", RespCommand.ZRANDMEMBER), - new("ZRANGE", RespCommand.ZRANGE), - new("ZRANGEBYLEX", RespCommand.ZRANGEBYLEX), - new("ZRANGEBYSCORE", RespCommand.ZRANGEBYSCORE), - new("ZRANGESTORE", RespCommand.ZRANGESTORE), - new("ZRANK", RespCommand.ZRANK), - new("ZREM", RespCommand.ZREM), - new("ZREMRANGEBYLEX", RespCommand.ZREMRANGEBYLEX), - new("ZREMRANGEBYRANK", RespCommand.ZREMRANGEBYRANK), - new("ZREMRANGEBYSCORE", RespCommand.ZREMRANGEBYSCORE), - new("ZREVRANGE", RespCommand.ZREVRANGE), - new("ZREVRANGEBYLEX", RespCommand.ZREVRANGEBYLEX), - new("ZREVRANGEBYSCORE", RespCommand.ZREVRANGEBYSCORE), - new("ZREVRANK", RespCommand.ZREVRANK), - new("ZSCAN", RespCommand.ZSCAN), - new("ZSCORE", RespCommand.ZSCORE), - new("ZEXPIRE", RespCommand.HEXPIRE), - new("ZPEXPIRE", RespCommand.HPEXPIRE), - new("ZEXPIREAT", RespCommand.HEXPIREAT), - new("ZPEXPIREAT", RespCommand.HPEXPIREAT), - new("ZTTL", RespCommand.HTTL), - new("ZPTTL", RespCommand.HPTTL), - new("ZEXPIRETIME", RespCommand.HEXPIRETIME), - new("ZPEXPIRETIME", RespCommand.HPEXPIRETIME), - new("ZPERSIST", RespCommand.HPERSIST), - new("ZCOLLECT", RespCommand.HPERSIST), - new("ZUNION", RespCommand.ZUNION), - new("ZUNIONSTORE", RespCommand.ZUNIONSTORE), + new("ZADD", RespCommand.ZADD, StoreType.Object), + new("ZCARD", RespCommand.ZCARD, StoreType.Object), + new("ZCOUNT", RespCommand.ZCOUNT, StoreType.Object), + new("ZDIFF", RespCommand.ZDIFF, StoreType.Object), + new("ZDIFFSTORE", RespCommand.ZDIFFSTORE, StoreType.Object), + new("ZINCRBY", RespCommand.ZINCRBY, StoreType.Object), + new("ZINTER", RespCommand.ZINTER, StoreType.Object), + new("ZINTERCARD", RespCommand.ZINTERCARD, StoreType.Object), + new("ZINTERSTORE", RespCommand.ZINTERSTORE, StoreType.Object), + new("ZLEXCOUNT", RespCommand.ZLEXCOUNT, StoreType.Object), + new("ZMSCORE", RespCommand.ZMSCORE, StoreType.Object), + new("ZMPOP", RespCommand.ZMPOP, StoreType.Object), + new("ZPOPMAX", RespCommand.ZPOPMAX, StoreType.Object), + new("ZPOPMIN", RespCommand.ZPOPMIN, StoreType.Object), + new("ZRANDMEMBER", RespCommand.ZRANDMEMBER, StoreType.Object), + new("ZRANGE", RespCommand.ZRANGE, StoreType.Object), + new("ZRANGEBYLEX", RespCommand.ZRANGEBYLEX, StoreType.Object), + new("ZRANGEBYSCORE", RespCommand.ZRANGEBYSCORE, StoreType.Object), + new("ZRANGESTORE", RespCommand.ZRANGESTORE, StoreType.Object), + new("ZRANK", RespCommand.ZRANK, StoreType.Object), + new("ZREM", RespCommand.ZREM, StoreType.Object), + new("ZREMRANGEBYLEX", RespCommand.ZREMRANGEBYLEX, StoreType.Object), + new("ZREMRANGEBYRANK", RespCommand.ZREMRANGEBYRANK, StoreType.Object), + new("ZREMRANGEBYSCORE", RespCommand.ZREMRANGEBYSCORE, StoreType.Object), + new("ZREVRANGE", RespCommand.ZREVRANGE, StoreType.Object), + new("ZREVRANGEBYLEX", RespCommand.ZREVRANGEBYLEX, StoreType.Object), + new("ZREVRANGEBYSCORE", RespCommand.ZREVRANGEBYSCORE, StoreType.Object), + new("ZREVRANK", RespCommand.ZREVRANK, StoreType.Object), + new("ZSCAN", RespCommand.ZSCAN, StoreType.Object), + new("ZSCORE", RespCommand.ZSCORE, StoreType.Object), + new("ZEXPIRE", RespCommand.ZEXPIRE, StoreType.Object), + new("ZPEXPIRE", RespCommand.ZPEXPIRE, StoreType.Object), + new("ZEXPIREAT", RespCommand.ZEXPIREAT, StoreType.Object), + new("ZPEXPIREAT", RespCommand.ZPEXPIREAT, StoreType.Object), + new("ZTTL", RespCommand.ZTTL, StoreType.Object), + new("ZPTTL", RespCommand.ZPTTL, StoreType.Object), + new("ZEXPIRETIME", RespCommand.ZEXPIRETIME, StoreType.Object), + new("ZPEXPIRETIME", RespCommand.ZPEXPIRETIME, StoreType.Object), + new("ZPERSIST", RespCommand.ZPERSIST, StoreType.Object), + new("ZCOLLECT", RespCommand.ZCOLLECT, StoreType.Object), + new("ZUNION", RespCommand.ZUNION, StoreType.Object), + new("ZUNIONSTORE", RespCommand.ZUNIONSTORE, StoreType.Object), new("EVAL", RespCommand.EVAL), new("EVALSHA", RespCommand.EVALSHA), - new("SCRIPT", RespCommand.SCRIPT, + new("SCRIPT", RespCommand.SCRIPT, StoreType.None, [ new("SCRIPT|EXISTS", RespCommand.SCRIPT_EXISTS), new("SCRIPT|FLUSH", RespCommand.SCRIPT_FLUSH), @@ -419,6 +418,11 @@ public class SupportedCommand /// public RespCommand RespCommand { get; set; } + /// + /// Store type that the command operates on (None/Main/Object/All). Default: None for commands without key arguments. + /// + public StoreType StoreType { get; set; } + /// /// Default constructor provided for JSON serialization /// @@ -432,12 +436,14 @@ public SupportedCommand() /// /// Supported command name /// RESP Command enum + /// Store type that the command operates on (None/Main/Object/All). Default: None for commands without key arguments. /// List of supported sub-command names (optional) - public SupportedCommand(string command, RespCommand respCommand = RespCommand.NONE, IEnumerable subCommands = null) : this() + public SupportedCommand(string command, RespCommand respCommand = RespCommand.NONE, StoreType storeType = StoreType.None, IEnumerable subCommands = null) : this() { Command = command; SubCommands = subCommands?.ToDictionary(sc => sc.Command, sc => sc); RespCommand = respCommand; + StoreType = storeType; } } } \ No newline at end of file diff --git a/test/Garnet.fuzz/Targets/GarnetEndToEnd.cs b/test/Garnet.fuzz/Targets/GarnetEndToEnd.cs index 5217e269fe5..031f071e713 100644 --- a/test/Garnet.fuzz/Targets/GarnetEndToEnd.cs +++ b/test/Garnet.fuzz/Targets/GarnetEndToEnd.cs @@ -358,17 +358,14 @@ private static EmbeddedRespServer CreateServer() { ThreadPoolMinThreads = 100, SegmentSize = "1g", - ObjectStoreSegmentSize = "1g", EnableStorageTier = true, LogDir = LogDir.FullName, CheckpointDir = CheckpointDir.FullName, EndPoints = [new IPEndPoint(IPAddress.Loopback, 1234)], DisablePubSub = false, - DisableObjects = false, EnableDebugCommand = ConnectionProtectionOption.Yes, Recover = false, IndexSize = "1m", - ObjectStoreIndexSize = "16k", EnableCluster = true, CleanClusterConfig = true, ClusterTimeout = -1, diff --git a/test/Garnet.test.cluster/ClusterManagementTests.cs b/test/Garnet.test.cluster/ClusterManagementTests.cs index 20b87d1a3cb..4e36e8befcf 100644 --- a/test/Garnet.test.cluster/ClusterManagementTests.cs +++ b/test/Garnet.test.cluster/ClusterManagementTests.cs @@ -396,7 +396,6 @@ public void ClusterRestartNodeDropGossip() context.nodes[restartingNode] = context.CreateInstance( context.clusterTestUtils.GetEndPoint(restartingNode), - disableObjects: true, tryRecover: false, enableAOF: true, timeout: 60, diff --git a/test/Garnet.test.cluster/ClusterMigrateTests.cs b/test/Garnet.test.cluster/ClusterMigrateTests.cs index 9a2f70d2fe3..65c7bbd02f0 100644 --- a/test/Garnet.test.cluster/ClusterMigrateTests.cs +++ b/test/Garnet.test.cluster/ClusterMigrateTests.cs @@ -1908,7 +1908,7 @@ public void ClusterMigrateWrite() var sourceNodeIndex = 0; var targetNodeIndex = 1; var nodes_count = 2; - context.CreateInstances(nodes_count, disableObjects: true); + context.CreateInstances(nodes_count); context.CreateConnection(); _ = context.clusterTestUtils.AddDelSlotsRange(sourceNodeIndex, [(0, 16383)], addslot: true, logger: context.logger); @@ -1977,7 +1977,7 @@ public void ClusterMigrateSetCopyUpdate(CancellationToken cancellationToken) var sourceNodeIndex = 0; var targetNodeIndex = 1; var nodes_count = 2; - context.CreateInstances(nodes_count, disableObjects: true); + context.CreateInstances(nodes_count); context.CreateConnection(); _ = context.clusterTestUtils.AddDelSlotsRange(sourceNodeIndex, [(0, 16383)], addslot: true, logger: context.logger); @@ -2050,7 +2050,7 @@ public void ClusterMigrateCustomProcDelRMW(CancellationToken cancellationToken) var sourceNodeIndex = 0; var targetNodeIndex = 1; var nodes_count = 2; - context.CreateInstances(nodes_count, disableObjects: true); + context.CreateInstances(nodes_count); context.CreateConnection(); _ = context.clusterTestUtils.AddDelSlotsRange(sourceNodeIndex, [(0, 16383)], addslot: true, logger: context.logger); diff --git a/test/Garnet.test.cluster/ClusterNegativeTests.cs b/test/Garnet.test.cluster/ClusterNegativeTests.cs index 8807958cac4..446ab643ae8 100644 --- a/test/Garnet.test.cluster/ClusterNegativeTests.cs +++ b/test/Garnet.test.cluster/ClusterNegativeTests.cs @@ -163,7 +163,7 @@ public void ClusterExceptionInjectionAtPrimarySyncSession([Values] bool enableDi var primaryIndex = 0; var replicaIndex = 1; var nodes_count = 2; - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, enableDisklessSync: enableDisklessSync, timeout: timeout); + context.CreateInstances(nodes_count, enableAOF: true, enableDisklessSync: enableDisklessSync, timeout: timeout); context.CreateConnection(); _ = context.clusterTestUtils.AddDelSlotsRange(primaryIndex, [(0, 16383)], addslot: true, logger: context.logger); @@ -193,7 +193,7 @@ public void ClusterFailoverDuringRecovery(CancellationToken cancellationToken) var primaryIndex = 0; var replicaIndex = 1; var nodes_count = 2; - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, enableDisklessSync: true, timeout: timeout, replicaDisklessSyncDelay: 10); + context.CreateInstances(nodes_count, enableAOF: true, enableDisklessSync: true, timeout: timeout, replicaDisklessSyncDelay: 10); context.CreateConnection(); _ = context.clusterTestUtils.AddDelSlotsRange(primaryIndex, [(0, 16383)], addslot: true, logger: context.logger); @@ -260,7 +260,7 @@ public void ClusterCheckpointAcquireTest([Values] bool fastAofTruncate, [Values] var primaryIndex = 0; var replicaIndex = 1; var nodes_count = 2; - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, timeout: timeout, FastAofTruncate: fastAofTruncate, OnDemandCheckpoint: onDemandCheckpoint, CommitFrequencyMs: fastAofTruncate ? -1 : 0); + context.CreateInstances(nodes_count, enableAOF: true, timeout: timeout, FastAofTruncate: fastAofTruncate, OnDemandCheckpoint: onDemandCheckpoint, CommitFrequencyMs: fastAofTruncate ? -1 : 0); context.CreateConnection(); _ = context.clusterTestUtils.AddDelSlotsRange(primaryIndex, [(0, 16383)], addslot: true, logger: context.logger); @@ -297,7 +297,6 @@ public void ClusterReplicaAttachIntenseWrite(CancellationToken cancellationToken var nodes_count = 2; context.CreateInstances( nodes_count, - disableObjects: false, enableAOF: true, timeout: timeout, OnDemandCheckpoint: true, @@ -373,7 +372,7 @@ public void ClusterFailedToAddAofSyncTask() var primaryIndex = 0; var replicaIndex = 1; var nodes_count = 2; - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, timeout: timeout); + context.CreateInstances(nodes_count, enableAOF: true, timeout: timeout); context.CreateConnection(); _ = context.clusterTestUtils.AddDelSlotsRange(primaryIndex, [(0, 16383)], addslot: true, logger: context.logger); @@ -423,7 +422,7 @@ public void ClusterReplicaSyncTimeoutTest() var primaryIndex = 0; var replicaIndex = 1; var nodes_count = 2; - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, timeout: timeout, replicaSyncTimeout: 1); + context.CreateInstances(nodes_count, enableAOF: true, timeout: timeout, replicaSyncTimeout: 1); context.CreateConnection(); _ = context.clusterTestUtils.AddDelSlotsRange(primaryIndex, [(0, 16383)], addslot: true, logger: context.logger); @@ -472,7 +471,6 @@ public async Task ClusterParallelFailoverOnDistinctShards(CancellationToken canc var nodes_count = 4; context.CreateInstances( nodes_count, - disableObjects: false, enableAOF: true, timeout: timeout, OnDemandCheckpoint: true, diff --git a/test/Garnet.test.cluster/ClusterTestContext.cs b/test/Garnet.test.cluster/ClusterTestContext.cs index b15a2506304..da28e01eb55 100644 --- a/test/Garnet.test.cluster/ClusterTestContext.cs +++ b/test/Garnet.test.cluster/ClusterTestContext.cs @@ -151,7 +151,6 @@ public void RegisterCustomTxn(string name, Func proc /// /// /// - /// /// /// /// @@ -185,7 +184,6 @@ public void CreateInstances( bool enableCluster = true, bool cleanClusterConfig = true, bool tryRecover = false, - bool disableObjects = false, bool lowMemory = false, string memorySize = default, string pageSize = default, @@ -234,7 +232,6 @@ public void CreateInstances( (nodes, nodeOptions) = TestUtils.CreateGarnetCluster( TestFolder, disablePubSub: disablePubSub, - disableObjects: disableObjects, enableCluster: enableCluster, endpoints: endpoints, enableAOF: enableAOF, @@ -294,7 +291,6 @@ public void CreateInstances( /// /// /// - /// /// /// /// @@ -321,7 +317,6 @@ public GarnetServer CreateInstance( bool cleanClusterConfig = true, bool disableEpochCollision = false, bool tryRecover = false, - bool disableObjects = false, bool lowMemory = false, string MemorySize = default, string PageSize = default, @@ -350,7 +345,6 @@ public GarnetServer CreateInstance( endpoint, enableCluster: enableCluster, disablePubSub: true, - disableObjects: disableObjects, enableAOF: enableAOF, timeout: timeout, gossipDelay: gossipDelay, diff --git a/test/Garnet.test.cluster/CustomProcs/ClusterDelRmw.cs b/test/Garnet.test.cluster/CustomProcs/ClusterDelRmw.cs index 5c2d97d0e72..d782a57f6f6 100644 --- a/test/Garnet.test.cluster/CustomProcs/ClusterDelRmw.cs +++ b/test/Garnet.test.cluster/CustomProcs/ClusterDelRmw.cs @@ -14,7 +14,7 @@ sealed class ClusterDelRmw : CustomTransactionProcedure public override bool Prepare(TGarnetReadApi api, ref CustomProcedureInput procInput) { var offset = 0; - AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, isObject: false); + AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, StoreType.Main); return true; } diff --git a/test/Garnet.test.cluster/RedirectTests/TestClusterProc.cs b/test/Garnet.test.cluster/RedirectTests/TestClusterProc.cs index 90a8c7bfd0e..3115b2e2252 100644 --- a/test/Garnet.test.cluster/RedirectTests/TestClusterProc.cs +++ b/test/Garnet.test.cluster/RedirectTests/TestClusterProc.cs @@ -30,9 +30,9 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce var getB = GetNextArg(ref procInput, ref offset); var getC = GetNextArg(ref procInput, ref offset); - AddKey(getA, LockType.Shared, isObject: false); - AddKey(getB, LockType.Shared, isObject: false); - AddKey(getC, LockType.Shared, isObject: false); + AddKey(getA, LockType.Shared, StoreType.Main); + AddKey(getB, LockType.Shared, StoreType.Main); + AddKey(getC, LockType.Shared, StoreType.Main); return true; } @@ -103,9 +103,9 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce var setB = GetNextArg(ref procInput, ref offset); var setC = GetNextArg(ref procInput, ref offset); - AddKey(getA, LockType.Shared, isObject: false); - AddKey(setB, LockType.Exclusive, isObject: false); - AddKey(setC, LockType.Exclusive, isObject: false); + AddKey(getA, LockType.Shared, StoreType.Main); + AddKey(setB, LockType.Exclusive, StoreType.Main); + AddKey(setC, LockType.Exclusive, StoreType.Main); return true; } diff --git a/test/Garnet.test.cluster/ReplicationTests/ClusterReplicationBaseTests.cs b/test/Garnet.test.cluster/ReplicationTests/ClusterReplicationBaseTests.cs index d85d1305aae..d5326137f46 100644 --- a/test/Garnet.test.cluster/ReplicationTests/ClusterReplicationBaseTests.cs +++ b/test/Garnet.test.cluster/ReplicationTests/ClusterReplicationBaseTests.cs @@ -114,7 +114,7 @@ public void ClusterSRTest([Values] bool disableObjects) var primary_count = 1; var nodes_count = primary_count + primary_count * replica_count; ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: disableObjects, enableAOF: true, useTLS: useTLS); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS); context.CreateConnection(useTLS: useTLS); var (shards, _) = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -151,7 +151,7 @@ public void ClusterSRNoCheckpointRestartSecondary([Values] bool performRMW, [Val var primary_count = 1; var nodes_count = primary_count + (primary_count * replica_count); ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: disableObjects, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); var (shards, _) = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -195,7 +195,6 @@ public void ClusterSRNoCheckpointRestartSecondary([Values] bool performRMW, [Val // Restart secondary context.nodes[1] = context.CreateInstance( context.clusterTestUtils.GetEndPoint(1), - disableObjects: disableObjects, tryRecover: true, enableAOF: true, timeout: timeout, @@ -217,7 +216,7 @@ public void ClusterSRPrimaryCheckpoint([Values] bool performRMW, [Values] bool d var primary_count = 1; var nodes_count = primary_count + (primary_count * replica_count); ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: disableObjects, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); var (shards, _) = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -268,7 +267,6 @@ public void ClusterSRPrimaryCheckpoint([Values] bool performRMW, [Values] bool d // Restart secondary context.nodes[1] = context.CreateInstance( context.clusterTestUtils.GetEndPoint(1), - disableObjects: disableObjects, tryRecover: true, enableAOF: true, timeout: timeout, @@ -316,7 +314,7 @@ void ClusterSRPrimaryCheckpointRetrieve(bool performRMW, bool disableObjects, bo var primary_count = 1; var nodes_count = primary_count + primary_count * replica_count; ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: disableObjects, lowMemory: lowMemory, segmentSize: manySegments ? "4k" : "1g", DisableStorageTier: disableStorageTier, EnableIncrementalSnapshots: incrementalSnapshots, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, lowMemory: lowMemory, segmentSize: manySegments ? "4k" : "1g", DisableStorageTier: disableStorageTier, EnableIncrementalSnapshots: incrementalSnapshots, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); var (shards, _) = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -364,7 +362,6 @@ void ClusterSRPrimaryCheckpointRetrieve(bool performRMW, bool disableObjects, bo // Restart secondary context.nodes[replicaIndex] = context.CreateInstance( context.clusterTestUtils.GetEndPoint(replicaIndex), - disableObjects: disableObjects, tryRecover: true, enableAOF: true, timeout: timeout, @@ -392,7 +389,7 @@ public void ClusterSRAddReplicaAfterPrimaryCheckpoint([Values] bool performRMW, var primary_count = 1; var nodes_count = primary_count + (primary_count * replica_count); ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, tryRecover: true, disableObjects: disableObjects, lowMemory: lowMemory, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, tryRecover: true, lowMemory: lowMemory, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); ClassicAssert.AreEqual("OK", context.clusterTestUtils.AddDelSlotsRange(0, new List<(int, int)>() { (0, 16383) }, true, context.logger)); @@ -451,7 +448,7 @@ public void ClusterSRPrimaryRestart([Values] bool performRMW, [Values] bool disa var primary_count = 1; var nodes_count = primary_count + (primary_count * replica_count); ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, tryRecover: true, disableObjects: disableObjects, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, tryRecover: true, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); ClassicAssert.AreEqual("OK", context.clusterTestUtils.AddDelSlotsRange(0, new List<(int, int)>() { (0, 16383) }, true, context.logger)); @@ -490,7 +487,6 @@ public void ClusterSRPrimaryRestart([Values] bool performRMW, [Values] bool disa // Restart Primary context.nodes[0] = context.CreateInstance( context.clusterTestUtils.GetEndPoint(0), - disableObjects: disableObjects, tryRecover: true, enableAOF: true, timeout: timeout, @@ -543,7 +539,7 @@ public void ClusterSRRedirectWrites() public void ClusterSRReplicaOfTest([Values] bool performRMW) { var nodes_count = 2; - context.CreateInstances(nodes_count, tryRecover: true, disableObjects: true, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, tryRecover: true, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); ClassicAssert.AreEqual("OK", context.clusterTestUtils.AddDelSlotsRange(0, [(0, 16383)], true, context.logger)); @@ -582,7 +578,7 @@ public void ClusterReplicationSimpleFailover([Values] bool performRMW, [Values] var primary_count = 1; var nodes_count = primary_count + (primary_count * replica_count); ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: true, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); var (shards, _) = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -655,7 +651,7 @@ public void ClusterFailoverAttachReplicas([Values] bool performRMW, [Values] boo var primary_count = 1; var nodes_count = primary_count + (primary_count * replica_count); ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: true, EnableIncrementalSnapshots: enableIncrementalSnapshots, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, EnableIncrementalSnapshots: enableIncrementalSnapshots, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); var (shards, _) = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -736,7 +732,7 @@ public void ClusterReplicationCheckpointCleanupTest([Values] bool performRMW, [V var primary_count = 1; var nodes_count = primary_count + (primary_count * replica_count); ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, tryRecover: true, disableObjects: disableObjects, lowMemory: true, segmentSize: "4k", EnableIncrementalSnapshots: enableIncrementalSnapshots, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, useNativeDeviceLinux: true); + context.CreateInstances(nodes_count, tryRecover: true, lowMemory: true, segmentSize: "4k", EnableIncrementalSnapshots: enableIncrementalSnapshots, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, useNativeDeviceLinux: true); context.CreateConnection(useTLS: useTLS); ClassicAssert.AreEqual("OK", context.clusterTestUtils.AddDelSlotsRange(0, [(0, 16383)], true, context.logger)); context.clusterTestUtils.BumpEpoch(0, logger: context.logger); @@ -774,7 +770,7 @@ public void ClusterMainMemoryReplicationAttachReplicas() var primary_count = 1; var nodes_count = primary_count + (primary_count * replica_count); ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: true, FastAofTruncate: true, OnDemandCheckpoint: true, CommitFrequencyMs: -1, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, FastAofTruncate: true, OnDemandCheckpoint: true, CommitFrequencyMs: -1, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); ClassicAssert.AreEqual("OK", context.clusterTestUtils.AddDelSlotsRange(0, new List<(int, int)>() { (0, 16383) }, true)); @@ -818,7 +814,7 @@ public void ClusterDontKnowReplicaFailTest([Values] bool performRMW, [Values] bo var primary_count = 1; var nodes_count = primary_count + (primary_count * replica_count); ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: true, FastAofTruncate: MainMemoryReplication, OnDemandCheckpoint: onDemandCheckpoint, CommitFrequencyMs: -1, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, FastAofTruncate: MainMemoryReplication, OnDemandCheckpoint: onDemandCheckpoint, CommitFrequencyMs: -1, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); var primaryNodeIndex = 0; @@ -919,7 +915,7 @@ void ClusterDivergentReplicasTest(bool performRMW, bool disableObjects, bool ckp var primary_count = 1; var nodes_count = primary_count + (primary_count * replica_count); ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: disableObjects, FastAofTruncate: mainMemoryReplication, CommitFrequencyMs: mainMemoryReplication ? -1 : 0, OnDemandCheckpoint: mainMemoryReplication, FastCommit: fastCommit, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, FastAofTruncate: mainMemoryReplication, CommitFrequencyMs: mainMemoryReplication ? -1 : 0, OnDemandCheckpoint: mainMemoryReplication, FastCommit: fastCommit, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); _ = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -1053,7 +1049,7 @@ public void ClusterReplicateFails() ServerCredential userCreds = new(UserName, Password, IsAdmin: true, UsedForClusterAuth: false, IsClearText: true); context.GenerateCredentials([userCreds, clusterCreds]); - context.CreateInstances(2, disableObjects: true, disablePubSub: true, enableAOF: true, clusterCreds: clusterCreds, useAcl: true, FastAofTruncate: true, CommitFrequencyMs: -1, asyncReplay: asyncReplay); + context.CreateInstances(2, disablePubSub: true, enableAOF: true, clusterCreds: clusterCreds, useAcl: true, FastAofTruncate: true, CommitFrequencyMs: -1, asyncReplay: asyncReplay); var primaryEndpoint = (IPEndPoint)context.endpoints.First(); var replicaEndpoint = (IPEndPoint)context.endpoints.Last(); @@ -1083,7 +1079,7 @@ public void ClusterReplicationCheckpointAlignmentTest([Values] bool performRMW) var primaryNodeIndex = 0; var replicaNodeIndex = 1; ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: false, FastAofTruncate: true, CommitFrequencyMs: -1, OnDemandCheckpoint: true, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); + context.CreateInstances(nodes_count, FastAofTruncate: true, CommitFrequencyMs: -1, OnDemandCheckpoint: true, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay); context.CreateConnection(useTLS: useTLS); _ = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -1124,7 +1120,6 @@ public void ClusterReplicationCheckpointAlignmentTest([Values] bool performRMW) // Restart primary and do not recover context.nodes[primaryNodeIndex] = context.CreateInstance( context.clusterTestUtils.GetEndPoint(primaryNodeIndex), - disableObjects: true, tryRecover: false, enableAOF: true, FastAofTruncate: true, @@ -1139,7 +1134,6 @@ public void ClusterReplicationCheckpointAlignmentTest([Values] bool performRMW) // Restart secondary and recover context.nodes[replicaNodeIndex] = context.CreateInstance( context.clusterTestUtils.GetEndPoint(replicaNodeIndex), - disableObjects: true, tryRecover: true, enableAOF: true, FastAofTruncate: true, @@ -1194,7 +1188,7 @@ public void ClusterReplicationLua([Values] bool luaTransactionMode) var replicaNodeIndex = 1; ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, enableLua: true, luaTransactionMode: luaTransactionMode); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, enableLua: true, luaTransactionMode: luaTransactionMode); context.CreateConnection(useTLS: useTLS); _ = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -1225,7 +1219,7 @@ public void ClusterReplicationStoredProc([Values] bool enableDisklessSync, [Valu var expectedKeys = new[] { "X", "Y" }; ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, enableDisklessSync: enableDisklessSync); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, enableDisklessSync: enableDisklessSync); context.CreateConnection(useTLS: useTLS); var primaryServer = context.clusterTestUtils.GetServer(primaryNodeIndex); @@ -1290,7 +1284,7 @@ public void ClusterReplicationManualCheckpointing() var nodes_count = primary_count + primary_count * replica_count; ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, useTLS: true, tryRecover: false, FastAofTruncate: true, CommitFrequencyMs: -1); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: true, tryRecover: false, FastAofTruncate: true, CommitFrequencyMs: -1); context.CreateConnection(useTLS: true); var (shards, _) = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -1363,7 +1357,7 @@ public async Task ReplicaSyncTaskFaultsRecoverAsync(ExceptionInjectionType fault var nodes_count = primary_count + primary_count * replica_count; ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, useTLS: true, tryRecover: false, FastAofTruncate: true, CommitFrequencyMs: -1, clusterReplicationReestablishmentTimeout: 1); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: true, tryRecover: false, FastAofTruncate: true, CommitFrequencyMs: -1, clusterReplicationReestablishmentTimeout: 1); context.CreateConnection(useTLS: true); var (shards, _) = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -1441,7 +1435,7 @@ public async Task ClusterReplicationMultiRestartRecover() ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, cleanClusterConfig: false); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, cleanClusterConfig: false); context.CreateConnection(useTLS: useTLS); _ = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -1489,7 +1483,6 @@ void RestartRecover(int iteration) context.nodes[replicaNodeIndex].Dispose(false); context.nodes[replicaNodeIndex] = context.CreateInstance( context.clusterTestUtils.GetEndPoint(replicaNodeIndex), - disableObjects: false, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, @@ -1510,7 +1503,7 @@ public async Task ReplicasRestartAsReplicasAsync(CancellationToken cancellation) var nodes_count = primary_count + primary_count * replica_count; ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, useTLS: true, tryRecover: false, FastAofTruncate: true, CommitFrequencyMs: -1, clusterReplicationReestablishmentTimeout: 1); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: true, tryRecover: false, FastAofTruncate: true, CommitFrequencyMs: -1, clusterReplicationReestablishmentTimeout: 1); context.CreateConnection(useTLS: true); var (shards, _) = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -1570,7 +1563,6 @@ public async Task PrimaryUnavailableRecoveryAsync(ExceptionInjectionType faultTy nodes_count, tryRecover: true, disablePubSub: false, - disableObjects: false, enableAOF: true, AofMemorySize: "128m", CommitFrequencyMs: -1, @@ -1900,7 +1892,7 @@ public void ClusterReplicationDivergentHistoryWithoutCheckpoint() var replicaNodeIndex = 1; ClassicAssert.IsTrue(primary_count > 0); - context.CreateInstances(nodes_count, disableObjects: false, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, cleanClusterConfig: false); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, cleanClusterConfig: false); context.CreateConnection(useTLS: useTLS); _ = context.clusterTestUtils.SimpleSetupCluster(primary_count, replica_count, logger: context.logger); @@ -1926,7 +1918,6 @@ public void ClusterReplicationDivergentHistoryWithoutCheckpoint() // Restart primary and do not recover context.nodes[primaryNodeIndex] = context.CreateInstance( context.clusterTestUtils.GetEndPoint(primaryNodeIndex), - disableObjects: false, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, @@ -1943,7 +1934,6 @@ public void ClusterReplicationDivergentHistoryWithoutCheckpoint() // Restart replica with recover to be ahead of primary but without checkpoint context.nodes[replicaNodeIndex] = context.CreateInstance( context.clusterTestUtils.GetEndPoint(replicaNodeIndex), - disableObjects: false, enableAOF: true, useTLS: useTLS, asyncReplay: asyncReplay, diff --git a/test/Garnet.test.cluster/ReplicationTests/ClusterReplicationDisklessSyncTests.cs b/test/Garnet.test.cluster/ReplicationTests/ClusterReplicationDisklessSyncTests.cs index 9a6c75dbe15..3cdf1122a51 100644 --- a/test/Garnet.test.cluster/ReplicationTests/ClusterReplicationDisklessSyncTests.cs +++ b/test/Garnet.test.cluster/ReplicationTests/ClusterReplicationDisklessSyncTests.cs @@ -109,7 +109,7 @@ public void ClusterEmptyReplicaDisklessSync([Values] bool disableObjects, [Value var nodes_count = 2; var primaryIndex = 0; var replicaIndex = 1; - context.CreateInstances(nodes_count, disableObjects: disableObjects, enableAOF: true, useTLS: useTLS, enableDisklessSync: true, timeout: timeout); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, enableDisklessSync: true, timeout: timeout); context.CreateConnection(useTLS: useTLS); // Setup primary and introduce it to future replica @@ -151,7 +151,7 @@ public void ClusterAofReplayDisklessSync([Values] bool disableObjects, [Values] var nodes_count = 2; var primaryIndex = 0; var replicaIndex = 1; - context.CreateInstances(nodes_count, disableObjects: disableObjects, enableAOF: true, useTLS: useTLS, enableDisklessSync: true, timeout: timeout, replicaDisklessSyncFullSyncAofThreshold: forceFullSync ? "1k" : string.Empty); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, enableDisklessSync: true, timeout: timeout, replicaDisklessSyncFullSyncAofThreshold: forceFullSync ? "1k" : string.Empty); context.CreateConnection(useTLS: useTLS); // Setup primary and introduce it to future replica @@ -209,7 +209,7 @@ public void ClusterDBVersionAlignmentDisklessSync([Values] bool disableObjects, var primaryIndex = 0; var replicaOneIndex = 1; var replicaTwoIndex = 2; - context.CreateInstances(nodes_count, disableObjects: disableObjects, enableAOF: true, useTLS: useTLS, enableDisklessSync: true, timeout: timeout); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, enableDisklessSync: true, timeout: timeout); context.CreateConnection(useTLS: useTLS); // Setup primary and introduce it to future replica @@ -301,7 +301,7 @@ public void ClusterDisklessSyncParallelAttach([Values] bool disableObjects, [Val var replicaOneIndex = 1; var replicaTwoIndex = 2; var replicaThreeIndex = 3; - context.CreateInstances(nodes_count, disableObjects: disableObjects, enableAOF: true, useTLS: useTLS, enableDisklessSync: true, timeout: timeout); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, enableDisklessSync: true, timeout: timeout); context.CreateConnection(useTLS: useTLS); // Setup primary and introduce it to future replica @@ -344,7 +344,7 @@ public void ClusterDisklessSyncFailover([Values] bool disableObjects, [Values] b int[] nOffsets = [primary, replicaOne, replicaTwo]; - context.CreateInstances(nodes_count, disableObjects: disableObjects, enableAOF: true, useTLS: useTLS, enableDisklessSync: true, timeout: timeout); + context.CreateInstances(nodes_count, enableAOF: true, useTLS: useTLS, enableDisklessSync: true, timeout: timeout); context.CreateConnection(useTLS: useTLS); // Setup primary and introduce it to future replica diff --git a/test/Garnet.test.cluster/ReplicationTests/ClusterResetDuringReplicationTests.cs b/test/Garnet.test.cluster/ReplicationTests/ClusterResetDuringReplicationTests.cs new file mode 100644 index 00000000000..539b21a7272 --- /dev/null +++ b/test/Garnet.test.cluster/ReplicationTests/ClusterResetDuringReplicationTests.cs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#if DEBUG +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Garnet.common; +using Microsoft.Extensions.Logging; +using NUnit.Framework; +using NUnit.Framework.Legacy; + +namespace Garnet.test.cluster.ReplicationTests +{ + /// + /// These tests simulate scenarios where a replica gets stuck or is in replication attach and verify that + /// CLUSTER RESET HARD can properly cancel ongoing operations and allow the replica to be reused. + /// + [NonParallelizable] + public class ClusterResetDuringReplicationTests + { + ClusterTestContext context; + + readonly int createInstanceTimeout = (int)System.TimeSpan.FromSeconds(30).TotalSeconds; + const int testTimeout = 120_000; + + readonly Dictionary monitorTests = []; + + [SetUp] + public void Setup() + { + context = new ClusterTestContext(); + context.Setup(monitorTests, testTimeoutSeconds: testTimeout); + } + + [TearDown] + public void TearDown() + { + context?.TearDown(); + } + + /// + /// Test CLUSTER RESET HARD functionality during diskless replication attach. + /// This test simulates a scenario where a replica gets stuck while attaching to a primary + /// and verifies that CLUSTER RESET HARD can properly cancel the operation and reset the node. + /// + [Test, Order(1), CancelAfter(testTimeout)] + [Category("REPLICATION")] + public async Task ClusterResetHardDuringDisklessReplicationAttach(CancellationToken cancellationToken) + { + var primaryIndex = 0; + var replicaIndex = 1; + var nodes_count = 2; + + // Create instances with diskless sync enabled + context.CreateInstances(nodes_count, enableAOF: true, enableDisklessSync: true, timeout: createInstanceTimeout); + context.CreateConnection(); + + // Setup primary + _ = context.clusterTestUtils.AddDelSlotsRange(primaryIndex, [(0, 16383)], addslot: true, logger: context.logger); + context.clusterTestUtils.SetConfigEpoch(primaryIndex, primaryIndex + 1, logger: context.logger); + context.clusterTestUtils.SetConfigEpoch(replicaIndex, replicaIndex + 1, logger: context.logger); + context.clusterTestUtils.Meet(primaryIndex, replicaIndex, logger: context.logger); + + // Ensure nodes know each other + context.clusterTestUtils.WaitUntilNodeIsKnown(primaryIndex, replicaIndex, logger: context.logger); + + try + { + ExceptionInjectionHelper.EnableException(ExceptionInjectionType.Replication_InProgress_During_Diskless_Replica_Attach_Sync); + + var resp = context.clusterTestUtils.ClusterReplicate(replicaNodeIndex: replicaIndex, primaryNodeIndex: primaryIndex, failEx: false, async: true, logger: context.logger); + + await Task.Delay(1000, cancellationToken); + + // Verify that the replica is in a replicating state + var replicationInfo = context.clusterTestUtils.GetReplicationInfo(replicaIndex, [ReplicationInfoItem.RECOVER_STATUS], logger: context.logger); + ClassicAssert.AreEqual("ClusterReplicate", replicationInfo[0].Item2); + + // Issuing CLUSTER RESET HARD while replication is ongoing/stuck. + var resetResp = context.clusterTestUtils.ClusterReset(replicaIndex, soft: false, expiry: 60, logger: context.logger); + ClassicAssert.AreEqual("OK", resetResp); + + // Verify that the node is no longer in recovery state + replicationInfo = context.clusterTestUtils.GetReplicationInfo(replicaIndex, [ReplicationInfoItem.RECOVER_STATUS], logger: context.logger); + ClassicAssert.AreEqual("NoRecovery", replicationInfo[0].Item2); + + // Verify the node role is back to PRIMARY (default after reset) + var role = context.clusterTestUtils.RoleCommand(replicaIndex, logger: context.logger); + ClassicAssert.AreEqual("master", role.Value); + } + finally + { + ExceptionInjectionHelper.DisableException(ExceptionInjectionType.Replication_InProgress_During_Diskless_Replica_Attach_Sync); + } + } + + /// + /// Test CLUSTER RESET HARD functionality during diskbased replication attach. + /// This test simulates a scenario where a replica gets stuck while attaching to a primary + /// and verifies that CLUSTER RESET HARD can properly cancel the operation and reset the node. + /// + [Test, Order(2), CancelAfter(testTimeout)] + [Category("REPLICATION")] + public async Task ClusterResetHardDuringDiskBasedReplicationAttach(CancellationToken cancellationToken) + { + var primaryIndex = 0; + var replicaIndex = 1; + var nodes_count = 2; + + // (diskless sync is false) + context.CreateInstances(nodes_count, enableAOF: true, enableDisklessSync: false, timeout: createInstanceTimeout); + context.CreateConnection(); + + // Setup primary + _ = context.clusterTestUtils.AddDelSlotsRange(primaryIndex, [(0, 16383)], addslot: true, logger: context.logger); + context.clusterTestUtils.SetConfigEpoch(primaryIndex, primaryIndex + 1, logger: context.logger); + context.clusterTestUtils.SetConfigEpoch(replicaIndex, replicaIndex + 1, logger: context.logger); + context.clusterTestUtils.Meet(primaryIndex, replicaIndex, logger: context.logger); + + context.clusterTestUtils.WaitUntilNodeIsKnown(primaryIndex, replicaIndex, logger: context.logger); + + try + { + ExceptionInjectionHelper.EnableException(ExceptionInjectionType.Replication_InProgress_During_DiskBased_Replica_Attach_Sync); + + var resp = context.clusterTestUtils.ClusterReplicate(replicaNodeIndex: replicaIndex, primaryNodeIndex: primaryIndex, failEx: false, async: true, logger: context.logger); + + await Task.Delay(1000, cancellationToken); + + // Verify that the replica is in a replicating state + var replicationInfo = context.clusterTestUtils.GetReplicationInfo(replicaIndex, [ReplicationInfoItem.RECOVER_STATUS], logger: context.logger); + ClassicAssert.AreEqual("ClusterReplicate", replicationInfo[0].Item2); + + // Issueing CLUSTER RESET HARD while replication is ongoing/stuck. + var resetResp = context.clusterTestUtils.ClusterReset(replicaIndex, soft: false, expiry: 60, logger: context.logger); + ClassicAssert.AreEqual("OK", resetResp); + + // Verify that the node is no longer in recovery state + replicationInfo = context.clusterTestUtils.GetReplicationInfo(replicaIndex, [ReplicationInfoItem.RECOVER_STATUS], logger: context.logger); + ClassicAssert.AreEqual("NoRecovery", replicationInfo[0].Item2); + + // Verify the node role is back to PRIMARY (default after reset) + var role = context.clusterTestUtils.RoleCommand(replicaIndex, logger: context.logger); + ClassicAssert.AreEqual("master", role.Value); + } + finally + { + ExceptionInjectionHelper.DisableException(ExceptionInjectionType.Replication_InProgress_During_DiskBased_Replica_Attach_Sync); + } + } + } +} +#endif \ No newline at end of file diff --git a/test/Garnet.test/AofFinalizeDoubleReplayTxn.cs b/test/Garnet.test/AofFinalizeDoubleReplayTxn.cs new file mode 100644 index 00000000000..2c88df77f1d --- /dev/null +++ b/test/Garnet.test/AofFinalizeDoubleReplayTxn.cs @@ -0,0 +1,28 @@ +using Garnet.common; +using Garnet.server; +using Tsavorite.core; + +namespace Garnet.test +{ + // Test transaction used to make sure we are not doing double replay of items enqueued to AOF at finalize step + // AOFFINDOUBLEREP KEY + public class AofFinalizeDoubleReplayTxn : CustomTransactionProcedure + { + public override bool Prepare(TGarnetReadApi api, ref CustomProcedureInput procInput) + { + int offset = 0; + AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, StoreType.Main); + return true; + } + public override void Main(TGarnetApi api, ref CustomProcedureInput procInput, ref MemoryResult output) + { + // No-op + } + + public override void Finalize(TGarnetApi api, ref CustomProcedureInput procInput, ref MemoryResult output) + { + int offset = 0; + api.Increment(GetNextArg(ref procInput, ref offset), out _); + } + } +} \ No newline at end of file diff --git a/test/Garnet.test/CacheSizeTrackerTests.cs b/test/Garnet.test/CacheSizeTrackerTests.cs index 556b48e8cfd..5ce9b3a7849 100644 --- a/test/Garnet.test/CacheSizeTrackerTests.cs +++ b/test/Garnet.test/CacheSizeTrackerTests.cs @@ -11,24 +11,24 @@ namespace Garnet.test { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; [TestFixture] public class CacheSizeTrackerTests { GarnetServer server; - TsavoriteKV objStore; + TsavoriteKV store; CacheSizeTracker cacheSizeTracker; [SetUp] public void Setup() { TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, memorySize: "2k", pageSize: "512", lowMemory: true, objectStoreIndexSize: "1k", objectStoreHeapMemorySize: "3k"); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, memorySize: "2k", pageSize: "512", lowMemory: true, indexSize: "1k", heapMemorySize: "3k"); server.Start(); - objStore = server.Provider.StoreWrapper.objectStore; - cacheSizeTracker = server.Provider.StoreWrapper.objectStoreSizeTracker; + store = server.Provider.StoreWrapper.store; + cacheSizeTracker = server.Provider.StoreWrapper.sizeTracker; } [TearDown] @@ -84,7 +84,7 @@ public void IncreaseEmptyPageCountTest() ClassicAssert.AreEqual(NumRecords * MemorySizePerEntry, cacheSizeTracker.mainLogTracker.LogHeapSizeBytes); // Wait for up to 3x resize task delay for the resizing to happen - if (!epcEvent.Wait(TimeSpan.FromSeconds(3 * LogSizeTracker.ResizeTaskDelaySeconds))) + if (!epcEvent.Wait(TimeSpan.FromSeconds(3 * LogSizeTracker.ResizeTaskDelaySeconds))) Assert.Fail("Timeout occurred. Resizing did not happen within the specified time."); } @@ -92,10 +92,10 @@ public void IncreaseEmptyPageCountTest() public void ReadCacheIncreaseEmptyPageCountTest() { server?.Dispose(); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, memorySize: "1k", pageSize: "512", lowMemory: true, objectStoreIndexSize: "1k", objectStoreReadCacheHeapMemorySize: "1k", enableObjectStoreReadCache: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, memorySize: "1k", pageSize: "512", lowMemory: true, indexSize: "1k", readCacheHeapMemorySize: "1k", enableReadCache: true); server.Start(); - objStore = server.Provider.StoreWrapper.objectStore; - cacheSizeTracker = server.Provider.StoreWrapper.objectStoreSizeTracker; + store = server.Provider.StoreWrapper.store; + cacheSizeTracker = server.Provider.StoreWrapper.sizeTracker; var readCacheEmptyPageCountIncrements = 0; var readCacheEpcEvent = new ManualResetEventSlim(false); @@ -126,10 +126,10 @@ public void ReadCacheIncreaseEmptyPageCountTest() // K/V lengths fit into a single byte each, so the record size is: RecordInfo, MinLengthMetadataSize, keyLength, valueLength; the total rounded up to record alignment. // ValueLength is 4 for the ObjectId, so this becomes 8 + 3 + (10 or 11) + 4 totalling 25 or 26, both rounding up to 32 which is a even divisor for the page size. // First valid address is 64, and there are 25 total records. - var info = TestUtils.GetStoreAddressInfo(redis.GetServer(TestUtils.EndPoint), includeReadCache: true, isObjectStore: true); + var info = TestUtils.GetStoreAddressInfo(redis.GetServer(TestUtils.EndPoint), includeReadCache: true); ClassicAssert.AreEqual(64 + 32 * NumRecords, info.ReadCacheTailAddress); - if (!readCacheEpcEvent.Wait(TimeSpan.FromSeconds(3 * 3 * LogSizeTracker.ResizeTaskDelaySeconds))) + if (!readCacheEpcEvent.Wait(TimeSpan.FromSeconds(3 * 3 * LogSizeTracker.ResizeTaskDelaySeconds))) ClassicAssert.Fail("Timeout occurred. Resizing did not happen within the specified time."); ClassicAssert.AreEqual(1, readCacheEmptyPageCountIncrements); diff --git a/test/Garnet.test/CustomRespCommandsInfo.json b/test/Garnet.test/CustomRespCommandsInfo.json index 72524a6fb6d..6e631b285ad 100644 --- a/test/Garnet.test/CustomRespCommandsInfo.json +++ b/test/Garnet.test/CustomRespCommandsInfo.json @@ -9,7 +9,22 @@ "Step": 1, "AclCategories": "Read", "Tips": null, - "KeySpecifications": null, + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RM, Delete" + } + ], + "StoreType": "Main", "SubCommands": null }, { @@ -22,7 +37,22 @@ "Step": 1, "AclCategories": "Read", "Tips": null, - "KeySpecifications": null, + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 2 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": -1, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RO, Access" + } + ], + "StoreType": "Main", "SubCommands": null }, { @@ -35,7 +65,22 @@ "Step": 1, "AclCategories": "Read", "Tips": null, - "KeySpecifications": null, + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RO, Access" + } + ], + "StoreType": "Object", "SubCommands": null }, { @@ -48,7 +93,22 @@ "Step": 1, "AclCategories": "Write", "Tips": null, - "KeySpecifications": null, + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RW, Access, Update" + } + ], + "StoreType": "Object", "SubCommands": null }, { @@ -61,7 +121,35 @@ "Step": 1, "AclCategories": "Write", "Tips": null, - "KeySpecifications": null, + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 2 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 1, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "OW, Update" + }, + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RO, Access" + } + ], + "StoreType": "Main", "SubCommands": null }, { @@ -74,7 +162,22 @@ "Step": 1, "AclCategories": "Write", "Tips": null, - "KeySpecifications": null, + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RW, Access, Update" + } + ], + "StoreType": "Main", "SubCommands": null }, { @@ -87,7 +190,21 @@ "Step": 1, "AclCategories": "String, Write", "Tips": null, - "KeySpecifications": null, + "KeySpecifications": [ + { + "BeginSearch": { + "TypeDiscriminator": "BeginSearchIndex", + "Index": 1 + }, + "FindKeys": { + "TypeDiscriminator": "FindKeysRange", + "LastKey": 0, + "KeyStep": 1, + "Limit": 0 + }, + "Flags": "RW, Access, Update" + } + ], "SubCommands": null } ] \ No newline at end of file diff --git a/test/Garnet.test/DeleteTxn.cs b/test/Garnet.test/DeleteTxn.cs index 3c31b965fef..a77cb1216f6 100644 --- a/test/Garnet.test/DeleteTxn.cs +++ b/test/Garnet.test/DeleteTxn.cs @@ -19,7 +19,7 @@ sealed class DeleteTxn : CustomTransactionProcedure public override bool Prepare(TGarnetReadApi api, ref CustomProcedureInput procInput) { var offset = 0; - AddKey(GetNextArg(ref procInput.parseState, ref offset), LockType.Exclusive, false); + AddKey(GetNextArg(ref procInput.parseState, ref offset), LockType.Exclusive, StoreType.All); return true; } @@ -27,7 +27,7 @@ public override void Main(TGarnetApi api, ref CustomProcedureInput p { var offset = 0; var key = GetNextArg(ref procInput.parseState, ref offset); - api.DELETE(key, StoreType.Main); + api.DELETE(key); WriteSimpleString(ref output, "SUCCESS"); } } diff --git a/test/Garnet.test/ExpiredKeyDeletionTests.cs b/test/Garnet.test/ExpiredKeyDeletionTests.cs index b777c24ca61..ab06a924c12 100644 --- a/test/Garnet.test/ExpiredKeyDeletionTests.cs +++ b/test/Garnet.test/ExpiredKeyDeletionTests.cs @@ -101,11 +101,9 @@ private async Task TestExpiredKeyDeletionScanAsync(Func ex // Merge reviv stats across sessions server.Provider.StoreWrapper.store.DumpRevivificationStats(); - server.Provider.StoreWrapper.objectStore.DumpRevivificationStats(); // Check that revivification happened for expired record ClassicAssert.IsTrue(server.Provider.StoreWrapper.store.RevivificationManager.stats.successfulAdds > 0, "Active expiration did not revivify for main store as expected"); - ClassicAssert.IsTrue(server.Provider.StoreWrapper.objectStore.RevivificationManager.stats.successfulAdds > 0, "Active expiration did not revivify for obj store as expected"); // Post expired key deletion scan, expired records don't exist for sure. This can be fooled by passive expiration too, so check reviv metrics too CheckExistenceConditionOnAllKeys(db, tombstonedRecords, false, "All to be expired should no longer exist post gc"); diff --git a/test/Garnet.test/Extensions/RateLimiterTxn.cs b/test/Garnet.test/Extensions/RateLimiterTxn.cs index f9b45ac9ee1..757fc4a84d9 100644 --- a/test/Garnet.test/Extensions/RateLimiterTxn.cs +++ b/test/Garnet.test/Extensions/RateLimiterTxn.cs @@ -14,7 +14,7 @@ sealed class RateLimiterTxn : CustomTransactionProcedure public override bool Prepare(TGarnetReadApi api, ref CustomProcedureInput procInput) { int offset = 0; - AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, true); + AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, StoreType.Object); return true; } @@ -54,7 +54,7 @@ public override unsafe void Main(TGarnetApi api, ref CustomProcedure fixed (byte* timeInMicroSecondBytesPtr = timeInMicroSecondBytes) { api.SortedSetAdd(key, PinnedSpanByte.FromPinnedPointer(unixTimeInMilliSecondPtr, unixTimeInMilliSecondBytes.Length), PinnedSpanByte.FromPinnedPointer(timeInMicroSecondBytesPtr, timeInMicroSecondBytes.Length), out var _); - api.EXPIRE(key, TimeSpan.FromMilliseconds(slidingWindowInMilliSeconds), out var _, StoreType.Object); + api.EXPIRE(key, TimeSpan.FromMilliseconds(slidingWindowInMilliSeconds), out _); } } diff --git a/test/Garnet.test/Extensions/SortedSetCountTxn.cs b/test/Garnet.test/Extensions/SortedSetCountTxn.cs index 023a9653640..199d922b84e 100644 --- a/test/Garnet.test/Extensions/SortedSetCountTxn.cs +++ b/test/Garnet.test/Extensions/SortedSetCountTxn.cs @@ -12,7 +12,7 @@ sealed class SortedSetCountTxn : CustomTransactionProcedure public override bool Prepare(TGarnetReadApi api, ref CustomProcedureInput input) { int offset = 0; - AddKey(GetNextArg(ref input, ref offset), LockType.Shared, true); + AddKey(GetNextArg(ref input, ref offset), LockType.Shared, StoreType.Object); return true; } diff --git a/test/Garnet.test/Extensions/TxnCustomCmd.cs b/test/Garnet.test/Extensions/TxnCustomCmd.cs index 48646f46f7d..ce0ad587348 100644 --- a/test/Garnet.test/Extensions/TxnCustomCmd.cs +++ b/test/Garnet.test/Extensions/TxnCustomCmd.cs @@ -18,10 +18,10 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce var mainStoreKey = GetNextArg(ref procInput, ref offset); _ = GetNextArg(ref procInput, ref offset); // mainStoreValue - AddKey(mainStoreKey, LockType.Exclusive, false); + AddKey(mainStoreKey, LockType.Exclusive, StoreType.Main); var myDictKey = GetNextArg(ref procInput, ref offset); - AddKey(myDictKey, LockType.Exclusive, true); + AddKey(myDictKey, LockType.Exclusive, StoreType.Object); if (!ParseCustomObjectCommand("MYDICTSET", out customObjectCommand)) return false; diff --git a/test/Garnet.test/GarnetClientTests.cs b/test/Garnet.test/GarnetClientTests.cs index f69389dafa2..0610aa7e9c8 100644 --- a/test/Garnet.test/GarnetClientTests.cs +++ b/test/Garnet.test/GarnetClientTests.cs @@ -372,7 +372,7 @@ public async Task ShouldNotThrowExceptionForEmptyArrayResponseAsync() [Test] public async Task CanUseMGetTests([Values] bool disableObjectStore) { - using var server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: disableObjectStore); + using var server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir); server.Start(); using var db = TestUtils.GetGarnetClient(); diff --git a/test/Garnet.test/GarnetObjectTests.cs b/test/Garnet.test/GarnetObjectTests.cs index c99731e4718..3b69df8e6e2 100644 --- a/test/Garnet.test/GarnetObjectTests.cs +++ b/test/Garnet.test/GarnetObjectTests.cs @@ -10,13 +10,13 @@ namespace Garnet.test { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; [TestFixture] public class GarnetObjectTests { - TsavoriteKV store; + TsavoriteKV store; IDevice logDevice, objectLogDevice; [SetUp] @@ -173,7 +173,7 @@ private void CreateStore() }; store = new(kvSettings - , StoreFunctions.Create(new SpanByteComparer(), () => new MyGarnetObjectSerializer()) + , Tsavorite.core.StoreFunctions.Create(new SpanByteComparer(), () => new MyGarnetObjectSerializer()) , (allocatorSettings, storeFunctions) => new(allocatorSettings, storeFunctions)); } } diff --git a/test/Garnet.test/GarnetServerConfigTests.cs b/test/Garnet.test/GarnetServerConfigTests.cs index ad2f5de785b..288fb52ac1c 100644 --- a/test/Garnet.test/GarnetServerConfigTests.cs +++ b/test/Garnet.test/GarnetServerConfigTests.cs @@ -125,7 +125,7 @@ public void ImportExportConfigLocal() var binPaths = new[] { GetFullExtensionBinPath("Garnet.test"), GetFullExtensionBinPath("Garnet.test.cluster") }; var modules = new[] { Assembly.GetExecutingAssembly().Location }; - var args = new[] { "--config-export-path", configPath, "-p", "4m", "-m", "128m", "-s", "2g", "--index", "128m", "--recover", "--port", "53", "--reviv-obj-bin-record-count", "2", "--reviv-fraction", "0.5", "--reviv-bin-record-counts", "1,2,3", "--extension-bin-paths", string.Join(',', binPaths), "--loadmodulecs", string.Join(',', modules) }; + var args = new[] { "--config-export-path", configPath, "-p", "4m", "-m", "128m", "-s", "2g", "--index", "128m", "--recover", "--port", "53", "--reviv-fraction", "0.5", "--reviv-bin-record-counts", "1,2,3", "--extension-bin-paths", string.Join(',', binPaths), "--loadmodulecs", string.Join(',', modules) }; parseSuccessful = ServerSettingsManager.TryParseCommandLineArguments(args, out options, out invalidOptions, out optionsJson, out exitGracefully, silentMode: true); ClassicAssert.IsTrue(parseSuccessful); ClassicAssert.AreEqual(invalidOptions.Count, 0); @@ -133,7 +133,6 @@ public void ImportExportConfigLocal() ClassicAssert.AreEqual("128m", options.MemorySize); ClassicAssert.AreEqual("2g", options.SegmentSize); ClassicAssert.AreEqual(53, options.Port); - ClassicAssert.AreEqual(2, options.RevivObjBinRecordCount); ClassicAssert.AreEqual(0.5, options.RevivifiableFraction); CollectionAssert.AreEqual(new[] { 1, 2, 3 }, options.RevivBinRecordCounts); ClassicAssert.IsTrue(options.Recover); @@ -143,7 +142,7 @@ public void ImportExportConfigLocal() // Validate non-default configuration options nonDefaultOptions = JsonSerializer.Deserialize>(optionsJson); - ClassicAssert.AreEqual(10, nonDefaultOptions.Count); + ClassicAssert.AreEqual(9, nonDefaultOptions.Count); ClassicAssert.IsTrue(nonDefaultOptions.ContainsKey(nameof(Options.PageSize))); ClassicAssert.AreEqual("4m", ((JsonElement)nonDefaultOptions[nameof(Options.PageSize)]).GetString()); ClassicAssert.IsTrue(nonDefaultOptions.ContainsKey(nameof(Options.Port))); @@ -175,14 +174,14 @@ public void ImportExportConfigLocal() // Validate non-default configuration options nonDefaultOptions = JsonSerializer.Deserialize>(optionsJson); - ClassicAssert.AreEqual(10, nonDefaultOptions.Count); + ClassicAssert.AreEqual(9, nonDefaultOptions.Count); ClassicAssert.IsTrue(nonDefaultOptions.ContainsKey(nameof(Options.PageSize))); ClassicAssert.AreEqual("4m", ((JsonElement)nonDefaultOptions[nameof(Options.PageSize)]).GetString()); // Import from previous export command, include command line args, export to file // Check values from import path override values from default.conf, and values from command line override values from default.conf and import path binPaths = [GetFullExtensionBinPath("Garnet.test")]; - args = ["--config-import-path", configPath, "-p", "12m", "-s", "1g", "--recover", "false", "--index", "256m", "--port", "0", "--no-obj", "--aof", "--reviv-bin-record-counts", "4,5", "--extension-bin-paths", string.Join(',', binPaths)]; + args = ["--config-import-path", configPath, "-p", "12m", "-s", "1g", "--recover", "false", "--index", "256m", "--port", "0", "--aof", "--reviv-bin-record-counts", "4,5", "--extension-bin-paths", string.Join(',', binPaths)]; parseSuccessful = ServerSettingsManager.TryParseCommandLineArguments(args, out options, out invalidOptions, out optionsJson, out exitGracefully, silentMode: true); ClassicAssert.IsTrue(parseSuccessful); ClassicAssert.AreEqual(invalidOptions.Count, 0); @@ -191,14 +190,13 @@ public void ImportExportConfigLocal() ClassicAssert.AreEqual("1g", options.SegmentSize); ClassicAssert.AreEqual(0, options.Port); ClassicAssert.IsFalse(options.Recover); - ClassicAssert.IsTrue(options.DisableObjects); ClassicAssert.IsTrue(options.EnableAOF); CollectionAssert.AreEqual(new[] { 4, 5 }, options.RevivBinRecordCounts); CollectionAssert.AreEqual(binPaths, options.ExtensionBinPaths); // Validate non-default configuration options nonDefaultOptions = JsonSerializer.Deserialize>(optionsJson); - ClassicAssert.AreEqual(11, nonDefaultOptions.Count); + ClassicAssert.AreEqual(9, nonDefaultOptions.Count); ClassicAssert.IsTrue(nonDefaultOptions.ContainsKey(nameof(Options.PageSize))); ClassicAssert.AreEqual("12m", ((JsonElement)nonDefaultOptions[nameof(Options.PageSize)]).GetString()); ClassicAssert.IsTrue(nonDefaultOptions.ContainsKey(nameof(Options.Port))); @@ -210,8 +208,6 @@ public void ImportExportConfigLocal() ((JsonElement)nonDefaultOptions[nameof(Options.RevivBinRecordCounts)]).EnumerateArray() .Select(i => i.GetInt32())); ClassicAssert.IsFalse(nonDefaultOptions.ContainsKey(nameof(Options.Recover))); - ClassicAssert.IsTrue(nonDefaultOptions.ContainsKey(nameof(Options.DisableObjects))); - ClassicAssert.IsTrue(((JsonElement)nonDefaultOptions[nameof(Options.DisableObjects)]).GetBoolean()); // No import path, include command line args // Check that all invalid options flagged diff --git a/test/Garnet.test/IndexGrowthTests.cs b/test/Garnet.test/IndexGrowthTests.cs index 7b9b9a9fa96..949bdee4e37 100644 --- a/test/Garnet.test/IndexGrowthTests.cs +++ b/test/Garnet.test/IndexGrowthTests.cs @@ -70,10 +70,10 @@ public void IndexGrowthTest() [Test] public void ObjectStoreIndexGrowthTest() { - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true, objectStoreIndexSize: "64", objectStoreIndexMaxSize: "128", indexResizeFrequencySecs: indexResizeTaskDelaySeconds); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true, indexSize: "64", indexMaxSize: "128", indexResizeFrequencySecs: indexResizeTaskDelaySeconds); server.Start(); - var objectStore = server.Provider.StoreWrapper.objectStore; + var store = server.Provider.StoreWrapper.store; RedisKey[] keys = ["abcdkey", "bcdekey", "cdefkey", "defgkey", "efghkey", "fghikey", "ghijkey", "hijkkey"]; RedisValue[] values = ["abcdval", "bcdeval", "cdefval", "defgval", "efghval", "fghival", "ghijval", "hijkval"]; @@ -81,8 +81,8 @@ public void ObjectStoreIndexGrowthTest() using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true))) { var db = redis.GetDatabase(0); - ClassicAssert.AreEqual(0, objectStore.OverflowBucketAllocations); - ClassicAssert.AreEqual(1, objectStore.IndexSize); + ClassicAssert.AreEqual(0, store.OverflowBucketAllocations); + ClassicAssert.AreEqual(1, store.IndexSize); for (int i = 0; i < keys.Length; i++) { @@ -90,16 +90,16 @@ public void ObjectStoreIndexGrowthTest() } VerifyObjectStoreSetMembers(db, keys, values); - ClassicAssert.AreEqual(1, objectStore.OverflowBucketAllocations); + ClassicAssert.AreEqual(1, store.OverflowBucketAllocations); // Wait for the resizing to happen for (int waitCycles = 0; waitCycles < indexResizeWaitCycles; waitCycles++) { Thread.Sleep(TimeSpan.FromSeconds(indexResizeTaskDelaySeconds)); - if (objectStore.IndexSize > 1) break; + if (store.IndexSize > 1) break; } - ClassicAssert.AreEqual(2, objectStore.IndexSize); + ClassicAssert.AreEqual(2, store.IndexSize); VerifyObjectStoreSetMembers(db, keys, values); } } @@ -181,10 +181,10 @@ public void IndexGrowthTestWithDiskReadAndCheckpoint() [Test] public void ObjectStoreIndexGrowthTestWithDiskReadAndCheckpoint() { - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true, objectStoreIndexSize: "512", objectStoreIndexMaxSize: "1k", indexResizeFrequencySecs: indexResizeTaskDelaySeconds); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true, indexSize: "512", indexMaxSize: "1k", indexResizeFrequencySecs: indexResizeTaskDelaySeconds); server.Start(); - var objectStore = server.Provider.StoreWrapper.objectStore; + var store = server.Provider.StoreWrapper.store; RedisKey[] keys = ["abcdkey", "bcdekey", "cdefkey", "defgkey", "efghkey", "fghikey", "ghijkey", "hijkkey"]; RedisValue[] values = ["abcdval", "bcdeval", "cdefval", "defgval", "efghval", "fghival", "ghijval", "hijkval"]; @@ -193,7 +193,7 @@ public void ObjectStoreIndexGrowthTestWithDiskReadAndCheckpoint() using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true))) { var db = redis.GetDatabase(0); - ClassicAssert.AreEqual(8, objectStore.IndexSize); + ClassicAssert.AreEqual(8, store.IndexSize); for (int i = 0; i < keys.Length; i++) { @@ -216,10 +216,10 @@ public void ObjectStoreIndexGrowthTestWithDiskReadAndCheckpoint() for (int waitCycles = 0; waitCycles < indexResizeWaitCycles; waitCycles++) { Thread.Sleep(TimeSpan.FromSeconds(indexResizeTaskDelaySeconds)); - if (objectStore.IndexSize > 8) break; + if (store.IndexSize > 8) break; } - ClassicAssert.AreEqual(16, objectStore.IndexSize); + ClassicAssert.AreEqual(16, store.IndexSize); // Check if entry created before resizing is still accessible. VerifyObjectStoreSetMembers(db, keys, values); @@ -231,7 +231,7 @@ public void ObjectStoreIndexGrowthTestWithDiskReadAndCheckpoint() } server.Dispose(false); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, tryRecover: true, lowMemory: true, objectStoreIndexSize: "512", objectStoreIndexMaxSize: "1k"); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, tryRecover: true, lowMemory: true, indexSize: "512", indexMaxSize: "1k"); server.Start(); using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true))) diff --git a/test/Garnet.test/MultiDatabaseTests.cs b/test/Garnet.test/MultiDatabaseTests.cs index d1775e64e19..661f514eb07 100644 --- a/test/Garnet.test/MultiDatabaseTests.cs +++ b/test/Garnet.test/MultiDatabaseTests.cs @@ -32,7 +32,7 @@ public void MultiDatabaseBasicSelectTestSE() var db2Key1 = "db2:key1"; var db2Key2 = "db2:key2"; var db12Key1 = "db12:key1"; - var db12Key2 = "db12:key1"; + var db12Key2 = "db12:key2"; using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); var db1 = redis.GetDatabase(0); @@ -57,7 +57,7 @@ public void MultiDatabaseBasicSelectTestSE() ClassicAssert.IsFalse(db12.KeyExists(db1Key1)); ClassicAssert.IsFalse(db12.KeyExists(db1Key2)); - db2.StringSet(db12Key2, "db12:value2"); + db2.StringSet(db12Key1, "db12:value2"); db2.SetAdd(db12Key2, [new RedisValue("db12:val2"), new RedisValue("db12:val2")]); ClassicAssert.IsFalse(db12.KeyExists(db12Key1)); @@ -438,7 +438,7 @@ public void MultiDatabaseBasicSelectTestLC() var db1Key1 = "db1:key1"; var db1Key2 = "db1:key2"; var db2Key1 = "db2:key1"; - var db2Key2 = "db2:key1"; + var db2Key2 = "db2:key2"; using var lightClientRequest = TestUtils.CreateRequest(); diff --git a/test/Garnet.test/ObjectExpiryTxn.cs b/test/Garnet.test/ObjectExpiryTxn.cs index 090321f0964..7a69716028f 100644 --- a/test/Garnet.test/ObjectExpiryTxn.cs +++ b/test/Garnet.test/ObjectExpiryTxn.cs @@ -19,7 +19,7 @@ sealed class ObjectExpiryTxn : CustomTransactionProcedure public override bool Prepare(TGarnetReadApi api, ref CustomProcedureInput procInput) { var offset = 0; - AddKey(GetNextArg(ref procInput.parseState, ref offset), LockType.Exclusive, true); + AddKey(GetNextArg(ref procInput.parseState, ref offset), LockType.Exclusive, StoreType.Object); return true; } @@ -29,7 +29,7 @@ public override void Main(TGarnetApi api, ref CustomProcedureInput p var key = GetNextArg(ref procInput.parseState, ref offset); var expiryMs = GetNextArg(ref procInput.parseState, ref offset); - api.EXPIRE(key, expiryMs, out _, StoreType.Object); + api.EXPIRE(key, expiryMs, out _); WriteSimpleString(ref output, "SUCCESS"); } } diff --git a/test/Garnet.test/ReadCacheTests.cs b/test/Garnet.test/ReadCacheTests.cs index 4e6015bc543..2199d13a841 100644 --- a/test/Garnet.test/ReadCacheTests.cs +++ b/test/Garnet.test/ReadCacheTests.cs @@ -16,7 +16,7 @@ public class ReadCacheTests public void Setup() { TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, enableReadCache: true, enableObjectStoreReadCache: true, lowMemory: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, enableReadCache: true, lowMemory: true); server.Start(); } @@ -99,7 +99,7 @@ public void ObjectStoreReadCacheTest() using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true)); var db = redis.GetDatabase(0); var server = redis.GetServer(TestUtils.EndPoint); - var info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true, isObjectStore: true); + var info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true); // Start at tail address of 24 ClassicAssert.AreEqual(24, info.ReadCacheBeginAddress); @@ -113,7 +113,7 @@ public void ObjectStoreReadCacheTest() _ = db.ListRightPush(key, value); } - info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true, isObjectStore: true); + info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true); // Ensure data has spilled to disk ClassicAssert.Greater(info.HeadAddress, info.BeginAddress); @@ -124,13 +124,13 @@ public void ObjectStoreReadCacheTest() var key0 = $"objKey00000"; var value0 = db.ListGetByIndex(key0, 0); ClassicAssert.AreEqual("objVal00000", (string)value0); - info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true, isObjectStore: true); + info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true); ClassicAssert.AreEqual(24 + 24, info.ReadCacheTailAddress); // 24 bytes for one record // Issue read again to ensure read cache is not updated value0 = db.ListGetByIndex(key0, 0); ClassicAssert.AreEqual("objVal00000", (string)value0); - info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true, isObjectStore: true); + info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true); ClassicAssert.AreEqual(24 + 24, info.ReadCacheTailAddress); // Read more keys to update read cache @@ -140,7 +140,7 @@ public void ObjectStoreReadCacheTest() var value = db.ListGetByIndex(key, 0); ClassicAssert.AreEqual($"objVal{j:00000}", (string)value); } - info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true, isObjectStore: true); + info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true); ClassicAssert.AreEqual(24 + 24 * 40 + 8, info.ReadCacheTailAddress); // 24 bytes for 20 records + 8 bytes for page boundary alignment ClassicAssert.AreEqual(24, info.ReadCacheBeginAddress); // Read cache should not have been evicted yet @@ -151,7 +151,7 @@ public void ObjectStoreReadCacheTest() var value = db.ListGetByIndex(key, 0); ClassicAssert.AreEqual($"objVal{j:00000}", (string)value); } - info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true, isObjectStore: true); + info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true); ClassicAssert.Greater(info.ReadCacheBeginAddress, 24); // Read cache entries should have been evicted } } diff --git a/test/Garnet.test/RespAdminCommandsTests.cs b/test/Garnet.test/RespAdminCommandsTests.cs index 62fbaa70690..389e63c14b5 100644 --- a/test/Garnet.test/RespAdminCommandsTests.cs +++ b/test/Garnet.test/RespAdminCommandsTests.cs @@ -146,12 +146,12 @@ public void SeSaveTest() } [Test] - public void SeSaveRecoverTest([Values] bool disableObj, [Values] bool useAzure) + public void SeSaveRecoverTest([Values] bool useAzure) { if (useAzure) TestUtils.IgnoreIfNotRunningAzureTests(); server.Dispose(); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: disableObj, useAzureStorage: useAzure); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, useAzureStorage: useAzure); server.Start(); using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true))) @@ -358,10 +358,10 @@ public void SeSaveRecoverMultipleObjectsTest(int memorySize, int recoveryMemoryS } server.Dispose(false); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, tryRecover: true, lowMemory: true, memorySize: sizeToString(recoveryMemorySize), pageSize: sizeToString(pageSize), objectStoreHeapMemorySize: "64k"); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, tryRecover: true, lowMemory: true, memorySize: sizeToString(recoveryMemorySize), pageSize: sizeToString(pageSize), heapMemorySize: "64k"); server.Start(); - ClassicAssert.LessOrEqual(server.Provider.StoreWrapper.objectStore.MaxAllocatedPageCount, (recoveryMemorySize / pageSize) + 1); + ClassicAssert.LessOrEqual(server.Provider.StoreWrapper.store.MaxAllocatedPageCount, (recoveryMemorySize / pageSize) + 1); using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true))) { var db = redis.GetDatabase(0); @@ -383,10 +383,8 @@ public void SeSaveRecoverMultipleObjectsTest(int memorySize, int recoveryMemoryS [TestCase("5k", "64k")] public void SeSaveRecoverMultipleKeysTest(string memorySize, string recoveryMemorySize) { - bool disableObj = true; - server.Dispose(); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: disableObj, lowMemory: true, memorySize: memorySize, pageSize: "512", enableAOF: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true, memorySize: memorySize, pageSize: "512", enableAOF: true); server.Start(); using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true))) @@ -425,7 +423,7 @@ public void SeSaveRecoverMultipleKeysTest(string memorySize, string recoveryMemo } server.Dispose(false); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: disableObj, tryRecover: true, lowMemory: true, memorySize: recoveryMemorySize, pageSize: "512", enableAOF: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, tryRecover: true, lowMemory: true, memorySize: recoveryMemorySize, pageSize: "512", enableAOF: true); server.Start(); using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true))) diff --git a/test/Garnet.test/RespAofTests.cs b/test/Garnet.test/RespAofTests.cs index 15c13651a38..098ced7d5a4 100644 --- a/test/Garnet.test/RespAofTests.cs +++ b/test/Garnet.test/RespAofTests.cs @@ -830,6 +830,45 @@ public void AofCustomTxnRecoverTest() } } + // Tests that the transaction's finalize step is currently written once into AOF, and replayed twice during replay + [Test] + public void AofTransactionFinalizeStepTest() + { + const string txnName = "AOFFINDOUBLEREP"; + const string key = "key1"; + server.Dispose(false); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, enableAOF: true); + server.Register.NewTransactionProc(txnName, () => new AofFinalizeDoubleReplayTxn(), new RespCommandsInfo { Arity = 2 }); + server.Start(); + + int resultPostTxn = 0; + using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig())) + { + var db = redis.GetDatabase(0); + db.Execute(txnName, key); + + var res = db.StringGet(key); + resultPostTxn = (int)res; + } + + // so now commit AOF, kill server and force a replay + server.Store.CommitAOF(true); + server.Dispose(false); + + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, tryRecover: true, enableAOF: true); + server.Register.NewTransactionProc(txnName, () => new AofFinalizeDoubleReplayTxn(), new RespCommandsInfo { Arity = 2 }); + server.Start(); + + // post replay we should end up at the exact state. This test should break right away because currently we do double replay + using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig())) + { + var db = redis.GetDatabase(0); + var res = db.StringGet(key); + int resultAfterRecovery = (int)res; + ClassicAssert.AreEqual(resultPostTxn, resultAfterRecovery); + } + } + private static void ExpectedEtagTest(IDatabase db, string key, string expectedValue, long expected) { RedisResult res = db.Execute("GETWITHETAG", key); diff --git a/test/Garnet.test/RespCommandTests.cs b/test/Garnet.test/RespCommandTests.cs index f1a87a68414..742e737e62c 100644 --- a/test/Garnet.test/RespCommandTests.cs +++ b/test/Garnet.test/RespCommandTests.cs @@ -218,7 +218,7 @@ public void CommandTest() ClassicAssert.AreEqual(externalRespCommandsInfo.Count, results.Length); // Register custom commands - var customCommandsRegistered = RegisterCustomCommands(); + var customCommandsRegistered = RegisterCustomCommands(["DELIFM", "MGETIFPM", "MYDICTSET", "SETIFPM", "SETWPIFPGT"]); // Dynamically register custom commands var customCommandsRegisteredDyn = DynamicallyRegisterCustomCommands(db); @@ -246,7 +246,7 @@ public void CommandInfoTest() ClassicAssert.AreEqual(externalRespCommandsInfo.Count, results.Length); // Register custom commands - var customCommandsRegistered = RegisterCustomCommands(); + var customCommandsRegistered = RegisterCustomCommands(["DELIFM", "MGETIFPM", "MYDICTSET", "SETIFPM", "SETWPIFPGT"]); // Dynamically register custom commands var customCommandsRegisteredDyn = DynamicallyRegisterCustomCommands(db); @@ -329,7 +329,7 @@ public void CommandCountTest() ClassicAssert.AreEqual(externalRespCommandsInfo.Count, commandCount); // Register custom commands - var customCommandsRegistered = RegisterCustomCommands(); + var customCommandsRegistered = RegisterCustomCommands(["DELIFM", "MGETIFPM", "MYDICTSET", "SETIFPM", "SETWPIFPGT"]); // Dynamically register custom commands var customCommandsRegisteredDyn = DynamicallyRegisterCustomCommands(db); @@ -548,16 +548,32 @@ public void AofIndependentCommandsTest() } } - private string[] RegisterCustomCommands() + private string[] RegisterCustomCommands(List registerCommandsSubset = null) { - var registeredCommands = new[] { "SETIFPM", "MYDICTSET", "MGETIFPM" }; + var commands = new HashSet { "DELIFM", "MGETIFPM", "MYDICTGET", "MYDICTSET", "READWRITETX", "SETIFPM", "SETWPIFPGT" }; + if (registerCommandsSubset != null) + { + commands.IntersectWith(registerCommandsSubset); + CollectionAssert.IsNotEmpty(commands); + } var factory = new MyDictFactory(); - server.Register.NewCommand("SETIFPM", CommandType.ReadModifyWrite, new SetIfPMCustomCommand(), respCustomCommandsInfo["SETIFPM"], respCustomCommandsDocs["SETIFPM"]); - server.Register.NewCommand("MYDICTSET", CommandType.ReadModifyWrite, factory, new MyDictSet(), respCustomCommandsInfo["MYDICTSET"], respCustomCommandsDocs["MYDICTSET"]); - server.Register.NewTransactionProc("MGETIFPM", () => new MGetIfPM(), respCustomCommandsInfo["MGETIFPM"], respCustomCommandsDocs["MGETIFPM"]); - - return registeredCommands; + if (commands.Contains("DELIFM")) + server.Register.NewCommand("DELIFM", CommandType.ReadModifyWrite, new DeleteIfMatchCustomCommand(), respCustomCommandsInfo["DELIFM"], respCustomCommandsDocs["DELIFM"]); + if (commands.Contains("MGETIFPM")) + server.Register.NewTransactionProc("MGETIFPM", () => new MGetIfPM(), respCustomCommandsInfo["MGETIFPM"], respCustomCommandsDocs["MGETIFPM"]); + if (commands.Contains("MYDICTGET")) + server.Register.NewCommand("MYDICTGET", CommandType.Read, factory, new MyDictGet(), respCustomCommandsInfo["MYDICTGET"], respCustomCommandsDocs["MYDICTGET"]); + if (commands.Contains("MYDICTSET")) + server.Register.NewCommand("MYDICTSET", CommandType.ReadModifyWrite, factory, new MyDictSet(), respCustomCommandsInfo["MYDICTSET"], respCustomCommandsDocs["MYDICTSET"]); + if (commands.Contains("READWRITETX")) + server.Register.NewTransactionProc("READWRITETX", () => new ReadWriteTxn(), respCustomCommandsInfo["READWRITETX"], respCustomCommandsDocs["READWRITETX"]); + if (commands.Contains("SETIFPM")) + server.Register.NewCommand("SETIFPM", CommandType.ReadModifyWrite, new SetIfPMCustomCommand(), respCustomCommandsInfo["SETIFPM"], respCustomCommandsDocs["SETIFPM"]); + if (commands.Contains("SETWPIFPGT")) + server.Register.NewCommand("SETWPIFPGT", CommandType.ReadModifyWrite, new SetWPIFPGTCustomCommand(), respCustomCommandsInfo["SETWPIFPGT"], respCustomCommandsDocs["SETWPIFPGT"]); + + return commands.ToArray(); } private (string, string, string) CreateTestLibrary() @@ -695,67 +711,92 @@ private void VerifyCommandDocs(string cmdName, RedisResult result) /// Test COMMAND GETKEYS command with various command signatures /// [Test] - [TestCase("SET", new[] { "mykey" }, new[] { "mykey", "value" }, Description = "Simple SET command")] - [TestCase("MSET", new[] { "key1", "key2" }, new[] { "key1", "value1", "key2", "value2" }, Description = "Multiple SET pairs")] - [TestCase("MGET", new[] { "key1", "key2", "key3" }, new[] { "key1", "key2", "key3" }, Description = "Multiple GET keys")] - [TestCase("ZUNIONSTORE", new[] { "destination", "key1", "key2" }, new[] { "destination", "2", "key1", "key2" }, Description = "ZUNIONSTORE with multiple source keys")] - [TestCase("EVAL", new[] { "key1", "key2" }, new[] { "return redis.call('GET', KEYS[1])", "2", "key1", "key2" }, Description = "EVAL with multiple keys")] - [TestCase("EXPIRE", new[] { "mykey" }, new[] { "mykey", "100", "NX" }, Description = "EXPIRE with NX option")] - [TestCase("MIGRATE", new[] { "key1", "key2" }, new[] { "127.0.0.1", "6379", "", "0", "5000", "KEYS", "key1", "key2" }, Description = "MIGRATE with multiple keys")] - [TestCase("GEOSEARCHSTORE", new[] { "dst", "src" }, new[] { "dst", "src", "FROMMEMBER", "member", "COUNT", "10", "ASC" }, Description = "GEOSEARCHSTORE with options")] - public void CommandGetKeysTest(string command, string[] expectedKeys, string[] args) + [TestCase("SET", new[] { "mykey", "value" }, false, new[] { "mykey" })] + [TestCase("MSET", new[] { "key1", "value1", "key2", "value2" }, false, new[] { "key1", "key2" })] + [TestCase("MGET", new[] { "key1", "key2", "key3" }, false, new[] { "key1", "key2", "key3" })] + [TestCase("ZUNIONSTORE", new[] { "destination", "2", "key1", "key2" }, false, new[] { "destination", "key1", "key2" })] + [TestCase("EVAL", new[] { "return redis.call('GET', KEYS[1])", "2", "key1", "key2" }, false, new[] { "key1", "key2" })] + [TestCase("EXPIRE", new[] { "mykey", "100", "NX" }, false, new[] { "mykey" })] + [TestCase("MIGRATE", new[] { "127.0.0.1", "6379", "", "0", "5000", "KEYS", "key1", "key2" }, false, new[] { "key1", "key2" })] + [TestCase("GEOSEARCHSTORE", new[] { "dst", "src", "FROMMEMBER", "member", "COUNT", "10", "ASC" }, false, new[] { "dst", "src" })] + [TestCase("DELIFM", new[] { "mykey", "value" }, true, new[] { "mykey" })] + [TestCase("MGETIFPM", new[] { "prefix", "key1", "key2", "key3" }, true, new[] { "key1", "key2", "key3" })] + [TestCase("MYDICTGET", new[] { "mykey", "key1" }, true, new[] { "mykey" })] + [TestCase("MYDICTSET", new[] { "mykey", "key1", "val1" }, true, new[] { "mykey" })] + [TestCase("READWRITETX", new[] { "readkey", "writekey1", "writekey2" }, true, new[] { "readkey", "writekey1", "writekey2" })] + [TestCase("SETIFPM", new[] { "mykey", "myvalue", "prefix" }, true, new[] { "mykey" })] + [TestCase("SETWPIFPGT", new[] { "mykey", "myvalue", "prefix" }, true, new[] { "mykey" })] + public void CommandGetKeysTest(string command, string[] args, bool isCustomCmd, string[] expectedKeys) { using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); var db = redis.GetDatabase(0); - var cmdArgs = new object[] { "GETKEYS", command }.Union(args).ToArray(); - var results = (RedisResult[])db.Execute("COMMAND", cmdArgs); + if (isCustomCmd) + RegisterCustomCommands(); + + var results = (RedisResult[])db.Execute("COMMAND", new object[] { "GETKEYS", command }.Union(args).ToArray()); ClassicAssert.IsNotNull(results); - ClassicAssert.AreEqual(expectedKeys.Length, results.Length); + ClassicAssert.AreEqual(expectedKeys.Length, results!.Length); - for (var i = 0; i < expectedKeys.Length; i++) - { - ClassicAssert.AreEqual(expectedKeys[i], results[i].ToString()); - } + var actualKeys = results.Select(r => r.ToString()).ToArray(); + CollectionAssert.AreEqual(expectedKeys, actualKeys); } /// /// Test COMMAND GETKEYSANDFLAGS command with various command signatures /// [Test] - [TestCase("SET", "mykey", "RW access update variable_flags", new[] { "mykey", "value" }, Description = "Simple SET command")] - [TestCase("MSET", "key1,key2", "OW update|OW update", new[] { "key1", "value1", "key2", "value2" }, Description = "Multiple SET pairs")] - [TestCase("MGET", "key1,key2,key3", "RO access|RO access|RO access", new[] { "key1", "key2", "key3" }, Description = "Multiple GET keys")] - [TestCase("ZUNIONSTORE", "destination,key1,key2", "OW update|RO access|RO access", new[] { "destination", "2", "key1", "key2" }, Description = "ZUNIONSTORE with multiple source keys")] - [TestCase("EVAL", "key1,key2", "RW access update|RW access update", new[] { "return redis.call('GET', KEYS[1])", "2", "key1", "key2" }, Description = "EVAL with multiple keys")] - [TestCase("EXPIRE", "mykey", "RW update", new[] { "mykey", "100", "NX" }, Description = "EXPIRE with NX option")] - [TestCase("MIGRATE", "key1,key2", "RW access delete incomplete|RW access delete incomplete", new[] { "127.0.0.1", "6379", "", "0", "5000", "KEYS", "key1", "key2" }, Description = "MIGRATE with multiple keys")] - [TestCase("GEOSEARCHSTORE", "dst,src", "OW update|RO access", new[] { "dst", "src", "FROMMEMBER", "member", "COUNT", "10", "ASC" }, Description = "GEOSEARCHSTORE with options")] - public void CommandGetKeysAndFlagsTest(string command, string expectedKeysStr, string expectedFlagsStr, string[] args) + [TestCase("SET", new[] { "mykey", "value" }, false, new[] { "mykey" }, new[] { KeySpecificationFlags.RW | KeySpecificationFlags.Access | KeySpecificationFlags.Update | KeySpecificationFlags.VariableFlags })] + [TestCase("MSET", new[] { "key1", "value1", "key2", "value2" }, false, new[] { "key1", "key2" }, new[] { KeySpecificationFlags.OW | KeySpecificationFlags.Update })] + [TestCase("MGET", new[] { "key1", "key2", "key3" }, false, new[] { "key1", "key2", "key3" }, new[] { KeySpecificationFlags.RO | KeySpecificationFlags.Access })] + [TestCase("ZUNIONSTORE", new[] { "destination", "2", "key1", "key2" }, false, new[] { "destination", "key1", "key2" }, new[] { KeySpecificationFlags.OW | KeySpecificationFlags.Update, KeySpecificationFlags.RO | KeySpecificationFlags.Access, KeySpecificationFlags.RO | KeySpecificationFlags.Access })] + [TestCase("EVAL", new[] { "return redis.call('GET', KEYS[1])", "2", "key1", "key2" }, false, new[] { "key1", "key2" }, new[] { KeySpecificationFlags.RW | KeySpecificationFlags.Access | KeySpecificationFlags.Update })] + [TestCase("EXPIRE", new[] { "mykey", "100", "NX" }, false, new[] { "mykey" }, new[] { KeySpecificationFlags.RW | KeySpecificationFlags.Update })] + [TestCase("MIGRATE", new[] { "127.0.0.1", "6379", "", "0", "5000", "KEYS", "key1", "key2" }, false, new[] { "key1", "key2" }, new[] { KeySpecificationFlags.RW | KeySpecificationFlags.Access | KeySpecificationFlags.Delete | KeySpecificationFlags.Incomplete })] + [TestCase("GEOSEARCHSTORE", new[] { "dst", "src", "FROMMEMBER", "member", "COUNT", "10", "ASC" }, false, new[] { "dst", "src" }, new[] { KeySpecificationFlags.OW | KeySpecificationFlags.Update, KeySpecificationFlags.RO | KeySpecificationFlags.Access })] + [TestCase("DELIFM", new[] { "mykey", "value" }, true, new[] { "mykey" }, new[] { KeySpecificationFlags.RM | KeySpecificationFlags.Delete })] + [TestCase("MGETIFPM", new[] { "prefix", "key1", "key2", "key3" }, true, new[] { "key1", "key2", "key3" }, new[] { KeySpecificationFlags.RO | KeySpecificationFlags.Access })] + [TestCase("MYDICTGET", new[] { "mykey", "key1" }, true, new[] { "mykey" }, new[] { KeySpecificationFlags.RO | KeySpecificationFlags.Access })] + [TestCase("MYDICTSET", new[] { "mykey", "key1", "val1" }, true, new[] { "mykey" }, new[] { KeySpecificationFlags.RW | KeySpecificationFlags.Access | KeySpecificationFlags.Update })] + [TestCase("READWRITETX", new[] { "readkey", "writekey1", "writekey2" }, true, new[] { "readkey", "writekey1", "writekey2" }, new[] { KeySpecificationFlags.RO | KeySpecificationFlags.Access, KeySpecificationFlags.OW | KeySpecificationFlags.Update, KeySpecificationFlags.OW | KeySpecificationFlags.Update })] + [TestCase("SETIFPM", new[] { "mykey", "myvalue", "prefix" }, true, new[] { "mykey" }, new[] { KeySpecificationFlags.RW | KeySpecificationFlags.Access | KeySpecificationFlags.Update })] + [TestCase("SETWPIFPGT", new[] { "mykey", "myvalue", "prefix" }, true, new[] { "mykey" }, new[] { KeySpecificationFlags.RW | KeySpecificationFlags.Access | KeySpecificationFlags.Update })] + public void CommandGetKeysAndFlagsTest(string command, string[] args, bool isCustomCmd, string[] expectedKeys, KeySpecificationFlags[] keySpecFlags) { using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); var db = redis.GetDatabase(0); - var expectedKeys = expectedKeysStr.Split(','); - var expectedFlags = expectedFlagsStr.Split('|') - .Select(f => f.Split(' ', StringSplitOptions.RemoveEmptyEntries)) - .ToArray(); + if (isCustomCmd) + RegisterCustomCommands(); - var cmdArgs = new object[] { "GETKEYSANDFLAGS", command }.Union(args).ToArray(); - var results = (RedisResult[])db.Execute("COMMAND", cmdArgs); + var results = (RedisResult[])db.Execute("COMMAND", new object[] { "GETKEYSANDFLAGS", command }.Union(args).ToArray()); ClassicAssert.IsNotNull(results); - ClassicAssert.AreEqual(expectedKeys.Length, results.Length); + ClassicAssert.AreEqual(expectedKeys.Length, results!.Length); + + var expectedFlags = keySpecFlags.Select(EnumUtils.GetEnumDescriptions).ToArray(); + ClassicAssert.IsTrue(expectedKeys.Length == expectedFlags.Length || expectedFlags.Length == 1); + + // If we are given a single flags argument, we multiply it for all keys + if (expectedFlags.Length == 1 && expectedKeys.Length > 1) + { + expectedFlags = Enumerable.Range(0, expectedKeys.Length).Select(_ => expectedFlags[0]).ToArray(); + } for (var i = 0; i < expectedKeys.Length; i++) { var keyInfo = (RedisResult[])results[i]; - ClassicAssert.AreEqual(2, keyInfo.Length); - ClassicAssert.AreEqual(expectedKeys[i], keyInfo[0].ToString()); + ClassicAssert.IsNotNull(keyInfo); + + ClassicAssert.AreEqual(2, keyInfo!.Length); + var actualKey = keyInfo[0].ToString(); + ClassicAssert.AreEqual(expectedKeys[i], actualKey); - var flags = ((RedisResult[])keyInfo[1]).Select(f => f.ToString()).ToArray(); - CollectionAssert.AreEquivalent(expectedFlags[i], flags); + ClassicAssert.IsNotNull((RedisResult[])keyInfo[1]); + var actualFlags = ((RedisResult[])keyInfo[1])!.Select(r => r.ToString()).ToArray(); + ClassicAssert.IsNotNull(actualFlags); + CollectionAssert.AreEquivalent(expectedFlags[i], actualFlags); } } diff --git a/test/Garnet.test/RespConfigTests.cs b/test/Garnet.test/RespConfigTests.cs index 0b590cb2723..19b7f7a1bad 100644 --- a/test/Garnet.test/RespConfigTests.cs +++ b/test/Garnet.test/RespConfigTests.cs @@ -13,8 +13,8 @@ namespace Garnet.test { - using ObjectStoreAllocator = ObjectAllocator>; - using ObjectStoreFunctions = StoreFunctions; + using StoreAllocator = ObjectAllocator>; + using StoreFunctions = StoreFunctions; /// /// Test dynamically changing server configuration using CONFIG SET command. @@ -26,9 +26,7 @@ public class RespConfigTests GarnetServer server; private string memorySize = "17g"; private string indexSize = "64m"; - private string objectStoreLogMemorySize = "17m"; - private string objectStoreHeapMemorySize = "32m"; - private string objectStoreIndexSize = "8m"; + private string heapMemorySize = "32m"; private bool useReviv; public RespConfigTests(bool useReviv) @@ -43,9 +41,7 @@ public void Setup() server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, memorySize: memorySize, indexSize: indexSize, - objectStoreLogMemorySize: objectStoreLogMemorySize, - objectStoreIndexSize: objectStoreIndexSize, - objectStoreHeapMemorySize: objectStoreHeapMemorySize, + heapMemorySize: heapMemorySize, useReviv: useReviv); server.Start(); } @@ -61,27 +57,26 @@ public void TearDown() /// This test verifies that dynamically changing the memory size configuration using CONFIG SET memory / obj-log-memory /// incurs the expected changes in Garnet server metrics, as well as verifies error handling for incorrect inputs. /// - /// Store type (Main / Object) /// Memory size smaller than the initial size /// Memory size larger than the initial size (within buffer bounds) /// Memory size larger than the buffer size /// Malformed memory size string [Test] - [TestCase(StoreType.Main, "16g", "32g", "64g", "g4")] - [TestCase(StoreType.Main, "9gB", "28GB", "33G", "2gBB")] - [TestCase(StoreType.Object, "16m", "32m", "64m", "3bm")] + [TestCase("16g", "32g", "64g", "g4")] + [TestCase("9gB", "28GB", "33G", "2gBB")] + [TestCase("16m", "32m", "64m", "3bm")] [TestCase(StoreType.Object, "5MB", "30M", "128mb", "44d")] - public void ConfigSetMemorySizeTest(StoreType storeType, string smallerSize, string largerSize, string largerThanBufferSize, string malformedSize) + public void ConfigSetMemorySizeTest(string smallerSize, string largerSize, string largerThanBufferSize, string malformedSize) { using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); var db = redis.GetDatabase(0); - var option = storeType == StoreType.Main ? "memory" : "obj-log-memory"; - var metricType = storeType == StoreType.Main ? InfoMetricsType.STORE : InfoMetricsType.OBJECTSTORE; - var initMemorySize = storeType == StoreType.Main ? memorySize : objectStoreLogMemorySize; + var option = "memory"; + var metricType = InfoMetricsType.STORE; + var initMemorySize = memorySize; var currMemorySize = ServerOptions.ParseSize(initMemorySize, out _); var bufferSize = ServerOptions.NextPowerOf2(currMemorySize); - var pageSize = storeType == StoreType.Main ? 32L * 1024 * 1024 : 4 * 1024; // default page size + var pageSize = 32L * 1024 * 1024; // default page size // Check initial MinEPC before any changes var metrics = server.Metrics.GetInfoMetrics(metricType); @@ -148,23 +143,22 @@ public void ConfigSetMemorySizeTest(StoreType storeType, string smallerSize, str /// This test verifies that dynamically changing the index size configuration using CONFIG SET index / obj-index /// incurs the expected changes in Garnet server metrics, as well as verifies error handling for incorrect inputs. /// - /// Store type (Main / Object) /// Index size smaller than the initial size /// Index size larger than the initial size /// Illegal index size (not a power of 2) /// Malformed index size string [Test] - [TestCase(StoreType.Main, "32m", "128m", "63m", "8d")] - [TestCase(StoreType.Main, "16mB", "256MB", "23m", "g8")] - [TestCase(StoreType.Object, "2m", "32m", "28m", "m9")] - [TestCase(StoreType.Object, "4Mb", "16mB", "129MB", "0.3gb")] - public void ConfigSetIndexSizeTest(StoreType storeType, string smallerSize, string largerSize, string illegalSize, string malformedSize) + [TestCase("32m", "128m", "63m", "8d")] + [TestCase("16mB", "256MB", "23m", "g8")] + [TestCase("2m", "32m", "28m", "m9")] + [TestCase("4Mb", "16mB", "129MB", "0.3gb")] + public void ConfigSetIndexSizeTest(string smallerSize, string largerSize, string illegalSize, string malformedSize) { using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); var db = redis.GetDatabase(0); - var metricType = storeType == StoreType.Main ? InfoMetricsType.STORE : InfoMetricsType.OBJECTSTORE; - var option = storeType == StoreType.Main ? "index" : "obj-index"; - var initIndexSize = storeType == StoreType.Main ? indexSize : objectStoreIndexSize; + var metricType = InfoMetricsType.STORE; + var option = "index"; + var initIndexSize = indexSize; // Check initial index size before any changes var currIndexSize = ServerOptions.ParseSize(initIndexSize, out _); @@ -228,7 +222,7 @@ public void ConfigObjHeapSizeTest(string smallerSize, string largerSize, string var db = redis.GetDatabase(0); var option = "obj-heap-memory"; - var currObjHeapSize = ServerOptions.ParseSize(objectStoreHeapMemorySize, out _); + var currObjHeapSize = ServerOptions.ParseSize(heapMemorySize, out _); // Check initial heap size before any changes var metrics = server.Metrics.GetInfoMetrics(InfoMetricsType.MEMORY); @@ -238,7 +232,7 @@ public void ConfigObjHeapSizeTest(string smallerSize, string largerSize, string ClassicAssert.AreEqual(currObjHeapSize, objHeapTargetSize); // Try to set heap size to the same value as current - var result = db.Execute("CONFIG", "SET", option, objectStoreHeapMemorySize); + var result = db.Execute("CONFIG", "SET", option, heapMemorySize); ClassicAssert.AreEqual("OK", result.ToString()); // Heap size should remain unchanged @@ -288,9 +282,7 @@ public class RespConfigUtilizationTests GarnetServer server; private string memorySize = "3m"; private string indexSize = "1m"; - private string objectStoreLogMemorySize = "2500"; - private string objectStoreHeapMemorySize = "1m"; - private string objectStoreIndexSize = "2048"; + private string heapMemorySize = "1m"; private string pageSize = "1024"; private bool useReviv; @@ -307,10 +299,7 @@ public void Setup() memorySize: memorySize, indexSize: indexSize, pageSize: pageSize, - objectStorePageSize: pageSize, - objectStoreLogMemorySize: objectStoreLogMemorySize, - objectStoreIndexSize: objectStoreIndexSize, - objectStoreHeapMemorySize: objectStoreHeapMemorySize, + heapMemorySize: heapMemorySize, useReviv: useReviv); server.Start(); } @@ -326,28 +315,27 @@ public void TearDown() /// This test verifies that dynamically changing the memory size configuration using CONFIG SET /// incurs the expected shifts in the head and tail addresses of the store. /// - /// Store Type (Main / Object) /// Memory size smaller than the initial size /// Memory size larger than the initial size (within buffer bounds) [Test] - [TestCase(StoreType.Main, "1m", "4m")] - [TestCase(StoreType.Main, "1024k", "4000k")] - [TestCase(StoreType.Object, "1024", "4000")] - [TestCase(StoreType.Object, "1024", "4096")] - public void ConfigSetMemorySizeUtilizationTest(StoreType storeType, string smallerSize, string largerSize) + [TestCase("1m", "4m")] + [TestCase("1024k", "4000k")] + [TestCase("1024", "4000")] + [TestCase("1024", "4096")] + public void ConfigSetMemorySizeUtilizationTest(string smallerSize, string largerSize) { using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true)); var db = redis.GetDatabase(0); - var option = storeType == StoreType.Main ? "memory" : "obj-log-memory"; - var initMemorySize = storeType == StoreType.Main ? memorySize : objectStoreLogMemorySize; + var option = "memory"; + var initMemorySize = memorySize; var currMemorySize = TestUtils.GetEffectiveMemorySize(initMemorySize, pageSize, out var parsedPageSize); var garnetServer = redis.GetServer(TestUtils.EndPoint); - var info = TestUtils.GetStoreAddressInfo(garnetServer, isObjectStore: storeType == StoreType.Object); - ClassicAssert.AreEqual(storeType == StoreType.Main ? 64 : 24, info.TailAddress); + var info = TestUtils.GetStoreAddressInfo(garnetServer); + ClassicAssert.AreEqual(64, info.TailAddress); var i = 0; - var val = new RedisValue(new string('x', storeType == StoreType.Main ? 512 - 32 : 1)); + var val = new RedisValue(new string('x', 512 - 32)); // Insert records until head address moves var prevHead = info.HeadAddress; @@ -355,14 +343,11 @@ public void ConfigSetMemorySizeUtilizationTest(StoreType storeType, string small while (info.HeadAddress == prevHead) { var key = $"key{i++:00000}"; - if (storeType == StoreType.Main) - _ = db.StringSet(key, val); - else - _ = db.ListRightPush(key, [val]); + _ = db.StringSet(key, val); prevHead = info.HeadAddress; prevTail = info.TailAddress; - info = TestUtils.GetStoreAddressInfo(garnetServer, isObjectStore: storeType == StoreType.Object); + info = TestUtils.GetStoreAddressInfo(garnetServer); } // Verify that records were inserted up to the configured memory size limit @@ -374,7 +359,7 @@ public void ConfigSetMemorySizeUtilizationTest(StoreType storeType, string small ClassicAssert.AreEqual("OK", result.ToString()); // Verify that head address moved forward - info = TestUtils.GetStoreAddressInfo(garnetServer, isObjectStore: storeType == StoreType.Object); + info = TestUtils.GetStoreAddressInfo(garnetServer); Assert.That(info.HeadAddress, Is.GreaterThan(prevHead)); currMemorySize = TestUtils.GetEffectiveMemorySize(smallerSize, pageSize, out _); @@ -385,14 +370,11 @@ public void ConfigSetMemorySizeUtilizationTest(StoreType storeType, string small while (info.HeadAddress == prevHead) { var key = $"key{i++:00000}"; - if (storeType == StoreType.Main) - _ = db.StringSet(key, val); - else - _ = db.ListRightPush(key, [val]); + _ = db.StringSet(key, val); prevHead = info.HeadAddress; prevTail = info.TailAddress; - info = TestUtils.GetStoreAddressInfo(garnetServer, isObjectStore: storeType == StoreType.Object); + info = TestUtils.GetStoreAddressInfo(garnetServer); } // Verify that records were inserted up to the configured memory size limit @@ -410,14 +392,11 @@ public void ConfigSetMemorySizeUtilizationTest(StoreType storeType, string small while (info.HeadAddress == prevHead) { var key = $"key{i++:00000}"; - if (storeType == StoreType.Main) - _ = db.StringSet(key, val); - else - _ = db.ListRightPush(key, [val]); + _ = db.StringSet(key, val); prevHead = info.HeadAddress; prevTail = info.TailAddress; - info = TestUtils.GetStoreAddressInfo(garnetServer, isObjectStore: storeType == StoreType.Object); + info = TestUtils.GetStoreAddressInfo(garnetServer); } // Verify that memory is fully utilized @@ -430,15 +409,14 @@ public void ConfigSetMemorySizeUtilizationTest(StoreType storeType, string small /// The test fills the store to a larger capacity than the initial memory size, then verifies that recovering with the /// smaller initial memory size retains the last inserted keys in the expected initial capacity. /// - /// Store Type (Main / Object) /// Memory size larger than the initial size (within buffer bounds) [Test] - [TestCase(StoreType.Main, "4m")] - [TestCase(StoreType.Object, "4096")] - public void ConfigSetMemorySizeRecoveryTest(StoreType storeType, string largerSize) + [TestCase("4m")] + [TestCase("4096")] + public void ConfigSetMemorySizeRecoveryTest(string largerSize) { - var option = storeType == StoreType.Main ? "memory" : "obj-log-memory"; - var initMemorySize = storeType == StoreType.Main ? memorySize : objectStoreLogMemorySize; + var option = "memory"; + var initMemorySize = memorySize; var currMemorySize = TestUtils.GetEffectiveMemorySize(initMemorySize, pageSize, out var parsedPageSize); @@ -449,11 +427,11 @@ public void ConfigSetMemorySizeRecoveryTest(StoreType storeType, string largerSi { var db = redis.GetDatabase(0); var garnetServer = redis.GetServer(TestUtils.EndPoint); - var info = TestUtils.GetStoreAddressInfo(garnetServer, isObjectStore: storeType == StoreType.Object); - ClassicAssert.AreEqual(storeType == StoreType.Main ? 64 : 24, info.TailAddress); + var info = TestUtils.GetStoreAddressInfo(garnetServer); + ClassicAssert.AreEqual(64, info.TailAddress); var i = 0; - var val = new RedisValue(new string('x', storeType == StoreType.Main ? 512 - 32 : 1)); + var val = new RedisValue(new string('x', 512 - 32)); // Insert records until head address moves var prevHead = info.HeadAddress; @@ -461,14 +439,11 @@ public void ConfigSetMemorySizeRecoveryTest(StoreType storeType, string largerSi while (info.HeadAddress == prevHead) { var key = $"key{i++:00000}"; - if (storeType == StoreType.Main) - _ = db.StringSet(key, val); - else - _ = db.ListRightPush(key, [val]); + _ = db.StringSet(key, val); prevHead = info.HeadAddress; prevTail = info.TailAddress; - info = TestUtils.GetStoreAddressInfo(garnetServer, isObjectStore: storeType == StoreType.Object); + info = TestUtils.GetStoreAddressInfo(garnetServer); } var lastIdxFirstRound = i - 1; @@ -500,14 +475,11 @@ public void ConfigSetMemorySizeRecoveryTest(StoreType storeType, string largerSi while (info.HeadAddress == prevHead) { var key = $"key{i++:00000}"; - if (storeType == StoreType.Main) - _ = db.StringSet(key, val); - else - _ = db.ListRightPush(key, [val]); + _ = db.StringSet(key, val); prevHead = info.HeadAddress; prevTail = info.TailAddress; - info = TestUtils.GetStoreAddressInfo(garnetServer, isObjectStore: storeType == StoreType.Object); + info = TestUtils.GetStoreAddressInfo(garnetServer); } lastIdxSecondRound = i - 1; @@ -526,10 +498,7 @@ public void ConfigSetMemorySizeRecoveryTest(StoreType storeType, string largerSi memorySize: memorySize, indexSize: indexSize, pageSize: pageSize, - objectStorePageSize: pageSize, - objectStoreLogMemorySize: objectStoreLogMemorySize, - objectStoreIndexSize: objectStoreIndexSize, - objectStoreHeapMemorySize: objectStoreHeapMemorySize, + heapMemorySize: heapMemorySize, useReviv: useReviv, tryRecover: true); server.Start(); @@ -568,8 +537,7 @@ public class RespConfigIndexUtilizationTests GarnetServer server; private string memorySize = "3m"; private string indexSize = "512"; - private string objectStoreLogMemorySize = "16384"; - private string objectStoreHeapMemorySize = "16384"; + private string heapMemorySize = "16384"; private string pageSize = "1024"; private bool useReviv; @@ -586,10 +554,7 @@ public void Setup() memorySize: memorySize, indexSize: indexSize, pageSize: pageSize, - objectStorePageSize: pageSize, - objectStoreLogMemorySize: objectStoreLogMemorySize, - objectStoreIndexSize: indexSize, - objectStoreHeapMemorySize: objectStoreHeapMemorySize, + heapMemorySize: heapMemorySize, useReviv: useReviv); server.Start(); } @@ -605,22 +570,18 @@ public void TearDown() /// This test verifies that dynamically changing the index size configuration using CONFIG SET /// incurs the expected shifts in the overflow buckets of the store, and that no data is lost in the process. /// - /// Store type (Main / Object) /// Larger index size than configured /// Larger index size than previous [Test] - [TestCase(StoreType.Main, "1024", "4096")] - [TestCase(StoreType.Object, "1024", "4096")] - public void ConfigSetIndexSizeUtilizationTest(StoreType storeType, string largerSize1, string largerSize2) + [TestCase("1024", "4096")] + public void ConfigSetIndexSizeUtilizationTest(string largerSize1, string largerSize2) { using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true)); var db = redis.GetDatabase(0); - var option = storeType == StoreType.Main ? "index" : "obj-index"; + var option = "index"; var parsedIndexSize = ServerOptions.ParseSize(indexSize, out _); - var currIndexSize = storeType == StoreType.Main - ? server.Provider.StoreWrapper.store.IndexSize - : server.Provider.StoreWrapper.objectStore.IndexSize; + var currIndexSize = server.Provider.StoreWrapper.store.IndexSize; // Verify initial index size and overflow bucket allocations are zero ClassicAssert.AreEqual(parsedIndexSize / 64, currIndexSize); @@ -635,10 +596,7 @@ public void ConfigSetIndexSizeUtilizationTest(StoreType storeType, string larger // Insert first batch of data for (var i = 0; i < 250; i++) { - if (storeType == StoreType.Main) - _ = db.StringSet(keys[i], val); - else - _ = db.ListRightPush(keys[i], [val]); + _ = db.StringSet(keys[i], val); } // Verify that overflow bucket allocations are non-zero after initial insertions @@ -657,10 +615,7 @@ public void ConfigSetIndexSizeUtilizationTest(StoreType storeType, string larger // Insert second batch of data for (var i = 250; i < 500; i++) { - if (storeType == StoreType.Main) - _ = db.StringSet(keys[i], val); - else - _ = db.ListRightPush(keys[i], [val]); + _ = db.StringSet(keys[i], val); } prevOverflowBucketAllocations = GetOverflowBucketAllocations(); @@ -680,9 +635,7 @@ public void ConfigSetIndexSizeUtilizationTest(StoreType storeType, string larger } long GetOverflowBucketAllocations() => - storeType == StoreType.Main - ? server.Provider.StoreWrapper.store.OverflowBucketAllocations - : server.Provider.StoreWrapper.objectStore.OverflowBucketAllocations; + server.Provider.StoreWrapper.store.OverflowBucketAllocations; } } @@ -696,8 +649,7 @@ public class RespConfigHeapUtilizationTests GarnetServer server; private string memorySize = "3m"; private string indexSize = "512"; - private string objectStoreLogMemorySize = "8192"; - private string objectStoreHeapMemorySize = "4096"; + private string heapMemorySize = "4096"; private string pageSize = "1024"; private bool useReviv; @@ -714,10 +666,7 @@ public void Setup() memorySize: memorySize, indexSize: indexSize, pageSize: pageSize, - objectStorePageSize: pageSize, - objectStoreLogMemorySize: objectStoreLogMemorySize, - objectStoreIndexSize: indexSize, - objectStoreHeapMemorySize: objectStoreHeapMemorySize, + heapMemorySize: heapMemorySize, useReviv: useReviv); server.Start(); } @@ -743,8 +692,8 @@ public void ConfigSetHeapSizeUtilizationTest(string largerSize) var option = "obj-heap-memory"; // Verify that initial empty page count is zero - var objectStore = server.Provider.StoreWrapper.objectStore; - ClassicAssert.AreEqual(0, objectStore.Log.EmptyPageCount); + var store = server.Provider.StoreWrapper.store; + ClassicAssert.AreEqual(0, store.Log.EmptyPageCount); // Add objects to store to fill up heap var values = new RedisValue[16]; @@ -758,12 +707,12 @@ public void ConfigSetHeapSizeUtilizationTest(string largerSize) } // Wait for log size tracker - var sizeTrackerDelay = TimeSpan.FromSeconds(LogSizeTracker.ResizeTaskDelaySeconds + 2); + var sizeTrackerDelay = TimeSpan.FromSeconds(LogSizeTracker.ResizeTaskDelaySeconds + 2); Thread.Sleep(sizeTrackerDelay); // Verify that empty page count has increased - ClassicAssert.Greater(objectStore.Log.EmptyPageCount, 0); - var prevEpc = objectStore.Log.EmptyPageCount; + ClassicAssert.Greater(store.Log.EmptyPageCount, 0); + var prevEpc = store.Log.EmptyPageCount; // Try to set heap size to a larger value than current var result = db.Execute("CONFIG", "SET", option, largerSize); @@ -773,7 +722,7 @@ public void ConfigSetHeapSizeUtilizationTest(string largerSize) Thread.Sleep(sizeTrackerDelay); // Verify that empty page count has decreased - ClassicAssert.Less(objectStore.Log.EmptyPageCount, prevEpc); + ClassicAssert.Less(store.Log.EmptyPageCount, prevEpc); } } } \ No newline at end of file diff --git a/test/Garnet.test/RespCustomCommandTests.cs b/test/Garnet.test/RespCustomCommandTests.cs index a247f34ec17..ff01793cf88 100644 --- a/test/Garnet.test/RespCustomCommandTests.cs +++ b/test/Garnet.test/RespCustomCommandTests.cs @@ -68,7 +68,7 @@ public class LargeGetTxn : CustomTransactionProcedure public override bool Prepare(TGarnetReadApi api, ref CustomProcedureInput procInput) { int offset = 0; - AddKey(GetNextArg(ref procInput, ref offset), LockType.Shared, false); + AddKey(GetNextArg(ref procInput, ref offset), LockType.Shared, StoreType.Main); return true; } @@ -164,8 +164,8 @@ public class RandomSubstituteOrExpandValForKeyTxn : CustomTransactionProcedure public override bool Prepare(TGarnetReadApi api, ref CustomProcedureInput procInput) { int offset = 0; - AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, false); - AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, false); + AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, StoreType.Main); + AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, StoreType.Main); return true; } @@ -974,14 +974,15 @@ public void RegisterCustomCommandTest() [.. args]); // Test READWRITETX - string key = "readkey"; + string key1 = "readkey1"; + string key2 = "readkey2"; string value = "foovalue0"; - db.StringSet(key, value); + db.StringSet(key1, value); string writekey1 = "writekey1"; string writekey2 = "writekey2"; - var result = db.Execute("READWRITETX", key, writekey1, writekey2); + var result = db.Execute("READWRITETX", key1, writekey1, writekey2); ClassicAssert.AreEqual("SUCCESS", (string)result); // Read keys to verify transaction succeeded @@ -997,32 +998,32 @@ public void RegisterCustomCommandTest() string newValue2 = "foovalue2"; // This conditional set should pass (prefix matches) - result = db.Execute("SETIFPM", key, newValue1, "foo"); + result = db.Execute("SETIFPM", key1, newValue1, "foo"); ClassicAssert.AreEqual("OK", (string)result); - retValue = db.StringGet(key); + retValue = db.StringGet(key1); ClassicAssert.AreEqual(newValue1, retValue); // This conditional set should fail (prefix does not match) - result = db.Execute("SETIFPM", key, newValue2, "bar"); + result = db.Execute("SETIFPM", key1, newValue2, "bar"); ClassicAssert.AreEqual("OK", (string)result); - retValue = db.StringGet(key); + retValue = db.StringGet(key1); ClassicAssert.AreEqual(newValue1, retValue); // Test MYDICTSET string newKey1 = "newkey1"; string newKey2 = "newkey2"; - db.Execute("MYDICTSET", key, newKey1, newValue1); + db.Execute("MYDICTSET", key2, newKey1, newValue1); - var dictVal = db.Execute("MYDICTGET", key, newKey1); + var dictVal = db.Execute("MYDICTGET", key2, newKey1); ClassicAssert.AreEqual(newValue1, (string)dictVal); - db.Execute("MYDICTSET", key, newKey2, newValue2); + db.Execute("MYDICTSET", key2, newKey2, newValue2); // Test MYDICTGET - dictVal = db.Execute("MYDICTGET", key, newKey2); + dictVal = db.Execute("MYDICTGET", key2, newKey2); ClassicAssert.AreEqual(newValue2, (string)dictVal); } diff --git a/test/Garnet.test/RespEtagTests.cs b/test/Garnet.test/RespEtagTests.cs index 1e27aa6d753..8c6e93c627e 100644 --- a/test/Garnet.test/RespEtagTests.cs +++ b/test/Garnet.test/RespEtagTests.cs @@ -1363,7 +1363,7 @@ public void SingleDeleteWithObjectStoreDisabledForEtagSetData() TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir); server.Start(); var key = "delKey"; @@ -1389,7 +1389,7 @@ public void SingleDeleteWithObjectStoreDisable_LTMForEtagSetData() TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true); server.Start(); using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); var db = redis.GetDatabase(0); @@ -1430,7 +1430,7 @@ public void MultiKeyDeleteForEtagSetData([Values] bool withoutObjectStore) { TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir); server.Start(); } @@ -1465,7 +1465,7 @@ public void MultiKeyUnlinkForEtagSetData([Values] bool withoutObjectStore) { TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir); server.Start(); } @@ -1499,7 +1499,7 @@ public void SingleExistsForEtagSetData([Values] bool withoutObjectStore) { TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir); server.Start(); } using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); @@ -1817,21 +1817,19 @@ public void MainObjectKeyForEtagSetData() ClassicAssert.AreEqual(1, long.Parse(db.Execute("SET", key, "v1", "WITHETAG").ToString())); - // Do SetAdd using the same key - ClassicAssert.IsTrue(db.SetAdd(key, "v2")); + // Do SetAdd using the same key, expected error + Assert.Throws(() => db.SetAdd(key, "v2"), + Encoding.ASCII.GetString(CmdStrings.RESP_ERR_WRONG_TYPE)); - // Two keys "test:1" - this is expected as of now - // because Garnet has a separate main and object store + // One key "test:1" with a string value is expected var keys = server.Keys(db.Database, key).ToList(); - ClassicAssert.AreEqual(2, keys.Count); + ClassicAssert.AreEqual(1, keys.Count); ClassicAssert.AreEqual(key, (string)keys[0]); - ClassicAssert.AreEqual(key, (string)keys[1]); + var value = db.StringGet(key); + ClassicAssert.AreEqual("v1", (string)value); // do ListRightPush using the same key, expected error - var ex = Assert.Throws(() => db.ListRightPush(key, "v3")); - var expectedError = Encoding.ASCII.GetString(CmdStrings.RESP_ERR_WRONG_TYPE); - ClassicAssert.IsNotNull(ex); - ClassicAssert.AreEqual(expectedError, ex.Message); + Assert.Throws(() => db.ListRightPush(key, "v3"), Encoding.ASCII.GetString(CmdStrings.RESP_ERR_WRONG_TYPE)); } [Test] diff --git a/test/Garnet.test/RespHashTests.cs b/test/Garnet.test/RespHashTests.cs index 139e84f9b1e..84821f77589 100644 --- a/test/Garnet.test/RespHashTests.cs +++ b/test/Garnet.test/RespHashTests.cs @@ -24,7 +24,7 @@ public class RespHashTests public void Setup() { TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, enableReadCache: true, enableObjectStoreReadCache: true, enableAOF: true, lowMemory: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, enableReadCache: true, enableAOF: true, lowMemory: true); server.Start(); } @@ -1140,7 +1140,7 @@ public async Task CanDoHashExpireLTM() db.HashSet(key, [new HashEntry("Field1", "StringValue"), new HashEntry("Field2", "1")]); } - var info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true, isObjectStore: true); + var info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true); // Ensure data has spilled to disk ClassicAssert.Greater(info.HeadAddress, info.BeginAddress); diff --git a/test/Garnet.test/RespInfoTests.cs b/test/Garnet.test/RespInfoTests.cs index 383f9cff5b1..694546436b9 100644 --- a/test/Garnet.test/RespInfoTests.cs +++ b/test/Garnet.test/RespInfoTests.cs @@ -82,11 +82,8 @@ public async Task InfoHlogScanTest() // hydrate var startingHA = server.Provider.StoreWrapper.store.Log.HeadAddress; - var startingHAObj = server.Provider.StoreWrapper.objectStore.Log.HeadAddress; - await Task.WhenAll( - HydrateStore(db, (db, key, value) => db.StringSetAsync(key, value), () => startingHA == server.Provider.StoreWrapper.store.Log.HeadAddress), - HydrateStore(db, (db, key, value) => db.SetAddAsync(key, value), () => startingHAObj == server.Provider.StoreWrapper.objectStore.Log.HeadAddress) - ); + await HydrateStore(db, (db, key, value) => db.StringSetAsync(key, value), + () => startingHA == server.Provider.StoreWrapper.store.Log.HeadAddress); // Wait for the immediate expirations to kick in await Task.Delay(500); diff --git a/test/Garnet.test/RespModuleTests.cs b/test/Garnet.test/RespModuleTests.cs index 092325547d0..5a7e0b4ae37 100644 --- a/test/Garnet.test/RespModuleTests.cs +++ b/test/Garnet.test/RespModuleTests.cs @@ -384,7 +384,6 @@ public void TearDown() public void TestNoAllowedPathsForModuleLoading() { using var server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, - disableObjects: true, disablePubSub: true, enableModuleCommand: Garnet.server.Auth.Settings.ConnectionProtectionOption.Yes, extensionBinPaths: null, @@ -413,7 +412,6 @@ public void TestNoAllowedPathsForModuleLoading() public void TestModuleCommandNotEnabled() { using var server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, - disableObjects: true, disablePubSub: true, enableModuleCommand: Garnet.server.Auth.Settings.ConnectionProtectionOption.No, extensionBinPaths: [testModuleDir, binPath], diff --git a/test/Garnet.test/RespSlowLogTests.cs b/test/Garnet.test/RespSlowLogTests.cs index 430f1f78b33..59e5c7058ca 100644 --- a/test/Garnet.test/RespSlowLogTests.cs +++ b/test/Garnet.test/RespSlowLogTests.cs @@ -17,7 +17,7 @@ public class RespSlowLogTests public void Setup() { TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disablePubSub: true, latencyMonitor: false, disableObjects: false, slowLogThreshold: slowLogThreshold); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disablePubSub: true, latencyMonitor: false, slowLogThreshold: slowLogThreshold); server.Start(); } diff --git a/test/Garnet.test/RespSortedSetTests.cs b/test/Garnet.test/RespSortedSetTests.cs index e57f138d109..ed02b04e7f7 100644 --- a/test/Garnet.test/RespSortedSetTests.cs +++ b/test/Garnet.test/RespSortedSetTests.cs @@ -20,10 +20,13 @@ namespace Garnet.test { using TestBasicGarnetApi = GarnetApi, - SpanByteAllocator>>, + /* MainStoreFunctions */ StoreFunctions, + ObjectAllocator>>, BasicContext, + ObjectAllocator>>, + BasicContext, ObjectAllocator>>>; [TestFixture] @@ -78,7 +81,7 @@ public class RespSortedSetTests public void Setup() { TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, enableReadCache: true, enableObjectStoreReadCache: true, enableAOF: true, lowMemory: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, enableReadCache: true, enableAOF: true, lowMemory: true); server.Start(); } @@ -100,7 +103,8 @@ public unsafe void SortedSetPopTest() db.SortedSetAdd("key1", "b", 2); var session = new RespServerSession(0, new EmbeddedNetworkSender(), server.Provider.StoreWrapper, null, null, false); - var api = new TestBasicGarnetApi(session.storageSession, session.storageSession.basicContext, session.storageSession.objectStoreBasicContext); + var api = new TestBasicGarnetApi(session.storageSession, session.storageSession.basicContext, + session.storageSession.objectStoreBasicContext, session.storageSession.unifiedStoreBasicContext); var key = Encoding.ASCII.GetBytes("key1"); fixed (byte* keyPtr = key) { @@ -132,7 +136,8 @@ public unsafe void SortedSetPopWithExpire() Thread.Sleep(200); var session = new RespServerSession(0, new EmbeddedNetworkSender(), server.Provider.StoreWrapper, null, null, false); - var api = new TestBasicGarnetApi(session.storageSession, session.storageSession.basicContext, session.storageSession.objectStoreBasicContext); + var api = new TestBasicGarnetApi(session.storageSession, session.storageSession.basicContext, + session.storageSession.objectStoreBasicContext, session.storageSession.unifiedStoreBasicContext); var key = Encoding.ASCII.GetBytes("key1"); fixed (byte* keyPtr = key) { @@ -2180,7 +2185,7 @@ public async Task CanDoSortedSetExpireLTM() ]); } - var info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true, isObjectStore: true); + var info = TestUtils.GetStoreAddressInfo(server, includeReadCache: true); // Ensure data has spilled to disk ClassicAssert.Greater(info.HeadAddress, info.BeginAddress); diff --git a/test/Garnet.test/RespTests.cs b/test/Garnet.test/RespTests.cs index dbefbfa3380..1b85d6674b0 100644 --- a/test/Garnet.test/RespTests.cs +++ b/test/Garnet.test/RespTests.cs @@ -1441,7 +1441,7 @@ public void SingleDeleteWithObjectStoreDisabled() TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir); server.Start(); var key = "delKey"; @@ -1466,7 +1466,7 @@ public void SingleDeleteWithObjectStoreDisable_LTM() TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true); server.Start(); using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); var db = redis.GetDatabase(0); @@ -1500,36 +1500,6 @@ public void SingleDeleteWithObjectStoreDisable_LTM() } } - [Test] - public void GarnetObjectStoreDisabledError() - { - TearDown(); - TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: true); - server.Start(); - - using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); - var db = redis.GetDatabase(0); - - var iter = 100; - var mykey = "mykey"; - for (var i = 0; i < iter; i++) - { - var exception = Assert.Throws(() => _ = db.ListLength(mykey)); - ClassicAssert.AreEqual("ERR Garnet Exception: Object store is disabled", exception.Message); - } - - // Ensure connection is still healthy - for (var i = 0; i < iter; i++) - { - var myvalue = "myvalue" + i; - var result = db.StringSet(mykey, myvalue); - ClassicAssert.IsTrue(result); - var returned = (string)db.StringGet(mykey); - ClassicAssert.AreEqual(myvalue, returned); - } - } - [Test] public void MultiKeyDelete([Values] bool withoutObjectStore) { @@ -1537,7 +1507,7 @@ public void MultiKeyDelete([Values] bool withoutObjectStore) { TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir); server.Start(); } @@ -1605,7 +1575,7 @@ public void MultiKeyUnlink([Values] bool withoutObjectStore) { TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir); server.Start(); } @@ -1671,7 +1641,7 @@ public void SingleExists([Values] bool withoutObjectStore) { TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir); server.Start(); } using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); @@ -1936,7 +1906,7 @@ public void SingleRenameKeyEdgeCase([Values] bool withoutObjectStore) { TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir); server.Start(); } using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig()); @@ -2553,7 +2523,9 @@ public void KeyExpireStringTest(string command) ClassicAssert.AreEqual(key, (string)value); if (command.Equals("EXPIRE")) - db.KeyExpire(key, TimeSpan.FromSeconds(1)); + { + var res = db.KeyExpire(key, TimeSpan.FromSeconds(1)); + } else db.Execute(command, [key, 1000]); @@ -3386,21 +3358,36 @@ public void MainObjectKey() // Do StringSet ClassicAssert.IsTrue(db.StringSet(key, "v1")); - // Do SetAdd using the same key - ClassicAssert.IsTrue(db.SetAdd(key, "v2")); + // Do SetAdd using the same key, expected error + Assert.Throws(() => db.SetAdd(key, "v2"), + Encoding.ASCII.GetString(CmdStrings.RESP_ERR_WRONG_TYPE)); - // Two keys "test:1" - this is expected as of now - // because Garnet has a separate main and object store + // One key "test:1" with a string value is expected var keys = server.Keys(db.Database, key).ToList(); - ClassicAssert.AreEqual(2, keys.Count); + ClassicAssert.AreEqual(1, keys.Count); ClassicAssert.AreEqual(key, (string)keys[0]); - ClassicAssert.AreEqual(key, (string)keys[1]); + var value = db.StringGet(key); + ClassicAssert.AreEqual("v1", (string)value); // do ListRightPush using the same key, expected error - var ex = Assert.Throws(() => db.ListRightPush(key, "v3")); - var expectedError = Encoding.ASCII.GetString(CmdStrings.RESP_ERR_WRONG_TYPE); - ClassicAssert.IsNotNull(ex); - ClassicAssert.AreEqual(expectedError, ex.Message); + Assert.Throws(() => db.ListRightPush(key, "v3"), Encoding.ASCII.GetString(CmdStrings.RESP_ERR_WRONG_TYPE)); + + // Delete the key + ClassicAssert.IsTrue(db.KeyDelete(key)); + + // Do SetAdd using the same key + ClassicAssert.IsTrue(db.SetAdd(key, "v2")); + + // Do StringIncrement using the same key, expected error + //Assert.Throws(() => db.StringIncrement(key), Encoding.ASCII.GetString(CmdStrings.RESP_ERR_WRONG_TYPE)); + + // One key "test:1" with a set value is expected + keys = server.Keys(db.Database, key).ToList(); + ClassicAssert.AreEqual(1, keys.Count); + ClassicAssert.AreEqual(key, (string)keys[0]); + var members = db.SetMembers(key); + ClassicAssert.AreEqual(1, members.Length); + ClassicAssert.AreEqual("v2", (string)members[0]); } [Test] @@ -3942,7 +3929,7 @@ public void AsyncTest1() // Set up low-memory database TearDown(); TestUtils.DeleteDirectory(TestUtils.MethodTestDir, wait: true); - server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true, disableObjects: true); + server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true); server.Start(); string firstKey = null, firstValue = null, lastKey = null, lastValue = null; diff --git a/test/Garnet.test/SortedSetRemoveTxn.cs b/test/Garnet.test/SortedSetRemoveTxn.cs index 1a2d341dd64..4cb442f94d1 100644 --- a/test/Garnet.test/SortedSetRemoveTxn.cs +++ b/test/Garnet.test/SortedSetRemoveTxn.cs @@ -21,7 +21,7 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce var offset = 0; var subscriptionContainerKey = GetNextArg(ref procInput.parseState, ref offset); - AddKey(subscriptionContainerKey, LockType.Exclusive, true); + AddKey(subscriptionContainerKey, LockType.Exclusive, StoreType.Object); return true; } diff --git a/test/Garnet.test/TestProcedureBitmap.cs b/test/Garnet.test/TestProcedureBitmap.cs index e8f77cdca03..379e80241f3 100644 --- a/test/Garnet.test/TestProcedureBitmap.cs +++ b/test/Garnet.test/TestProcedureBitmap.cs @@ -35,9 +35,9 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce if (bitmapB.Length == 0) return false; - AddKey(bitmapA, LockType.Exclusive, false); - AddKey(destinationKey, LockType.Exclusive, false); - AddKey(bitmapB, LockType.Exclusive, false); + AddKey(bitmapA, LockType.Exclusive, StoreType.Main); + AddKey(destinationKey, LockType.Exclusive, StoreType.Main); + AddKey(bitmapB, LockType.Exclusive, StoreType.Main); return true; } diff --git a/test/Garnet.test/TestProcedureHLL.cs b/test/Garnet.test/TestProcedureHLL.cs index a47f4b74ff4..8d01e0a1e59 100644 --- a/test/Garnet.test/TestProcedureHLL.cs +++ b/test/Garnet.test/TestProcedureHLL.cs @@ -26,7 +26,7 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce if (hll.Length == 0) return false; - AddKey(hll, LockType.Exclusive, false); + AddKey(hll, LockType.Exclusive, StoreType.Main); return true; } diff --git a/test/Garnet.test/TestProcedureHash.cs b/test/Garnet.test/TestProcedureHash.cs index 28aa2f4ac18..ae8cdddf2c6 100644 --- a/test/Garnet.test/TestProcedureHash.cs +++ b/test/Garnet.test/TestProcedureHash.cs @@ -27,7 +27,7 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce if (setA.Length == 0) return false; - AddKey(setA, LockType.Exclusive, true); + AddKey(setA, LockType.Exclusive, StoreType.Object); return true; } diff --git a/test/Garnet.test/TestProcedureLists.cs b/test/Garnet.test/TestProcedureLists.cs index f6348e0edc8..9cf678d10aa 100644 --- a/test/Garnet.test/TestProcedureLists.cs +++ b/test/Garnet.test/TestProcedureLists.cs @@ -29,9 +29,9 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce if (lstKey.Length == 0 || lstKeyB.Length == 0 || lstKeyC.Length == 0) return false; - AddKey(lstKey, LockType.Exclusive, true); - AddKey(lstKeyB, LockType.Exclusive, true); - AddKey(lstKeyC, LockType.Exclusive, true); + AddKey(lstKey, LockType.Exclusive, StoreType.Object); + AddKey(lstKeyB, LockType.Exclusive, StoreType.Object); + AddKey(lstKeyC, LockType.Exclusive, StoreType.Object); return true; } diff --git a/test/Garnet.test/TestProcedureSet.cs b/test/Garnet.test/TestProcedureSet.cs index 06d42c57e7a..a31a4e983c0 100644 --- a/test/Garnet.test/TestProcedureSet.cs +++ b/test/Garnet.test/TestProcedureSet.cs @@ -26,7 +26,7 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce if (setA.Length == 0) return false; - AddKey(setA, LockType.Exclusive, true); + AddKey(setA, LockType.Exclusive, StoreType.Object); return true; } diff --git a/test/Garnet.test/TestProcedureSortedSets.cs b/test/Garnet.test/TestProcedureSortedSets.cs index a3e35334c9f..ff3b04f2e48 100644 --- a/test/Garnet.test/TestProcedureSortedSets.cs +++ b/test/Garnet.test/TestProcedureSortedSets.cs @@ -27,7 +27,7 @@ public override bool Prepare(TGarnetReadApi api, ref CustomProce if (ssA.Length == 0) return false; - AddKey(ssA, LockType.Exclusive, true); + AddKey(ssA, LockType.Exclusive, StoreType.Object); return true; } diff --git a/test/Garnet.test/TestUtils.cs b/test/Garnet.test/TestUtils.cs index bfff3da0c0f..2e4d51f614a 100644 --- a/test/Garnet.test/TestUtils.cs +++ b/test/Garnet.test/TestUtils.cs @@ -226,11 +226,9 @@ public static GarnetServer CreateGarnetServer( bool tryRecover = false, bool lowMemory = false, string memorySize = default, - string objectStoreLogMemorySize = default, string pageSize = default, bool enableAOF = false, bool enableTLS = false, - bool disableObjects = false, int metricsSamplingFreq = -1, bool latencyMonitor = false, int commitFrequencyMs = 0, @@ -239,11 +237,8 @@ public static GarnetServer CreateGarnetServer( string defaultPassword = null, bool useAcl = false, // NOTE: Temporary until ACL is enforced as default string aclFile = null, - string objectStorePageSize = default, - string objectStoreHeapMemorySize = default, - string objectStoreIndexSize = "16k", - string objectStoreIndexMaxSize = default, - string objectStoreReadCacheHeapMemorySize = default, + string heapMemorySize = default, + string readCacheHeapMemorySize = default, string indexSize = "1m", string indexMaxSize = default, string[] extensionBinPaths = null, @@ -255,7 +250,6 @@ public static GarnetServer CreateGarnetServer( ConnectionProtectionOption enableModuleCommand = ConnectionProtectionOption.No, bool enableLua = false, bool enableReadCache = false, - bool enableObjectStoreReadCache = false, ILogger logger = null, IEnumerable loadModulePaths = null, string pubSubPageSize = null, @@ -323,7 +317,6 @@ public static GarnetServer CreateGarnetServer( DisablePubSub = disablePubSub, Recover = tryRecover, IndexSize = indexSize, - ObjectStoreIndexSize = objectStoreIndexSize, EnableAOF = enableAOF, EnableLua = enableLua, CommitFrequencyMs = commitFrequencyMs, @@ -336,7 +329,6 @@ public static GarnetServer CreateGarnetServer( issuerCertificatePath: null, null, 0, false, null, logger: logger) : null, - DisableObjects = disableObjects, QuietMode = true, MetricsSamplingFrequency = metricsSamplingFreq, LatencyMonitor = latencyMonitor, @@ -354,7 +346,6 @@ public static GarnetServer CreateGarnetServer( EnableDebugCommand = enableDebugCommand, EnableModuleCommand = enableModuleCommand, EnableReadCache = enableReadCache, - EnableObjectStoreReadCache = enableObjectStoreReadCache, ReplicationOffsetMaxLag = asyncReplay ? -1 : 0, LuaOptions = enableLua ? new LuaOptions(luaMemoryMode, luaMemoryLimit, luaTimeout ?? Timeout.InfiniteTimeSpan, luaLoggingMode, luaAllowedFunctions ?? [], logger) : null, UnixSocketPath = unixSocketPath, @@ -366,42 +357,28 @@ public static GarnetServer CreateGarnetServer( if (!string.IsNullOrEmpty(memorySize)) opts.MemorySize = memorySize; - if (!string.IsNullOrEmpty(objectStoreLogMemorySize)) - opts.ObjectStoreLogMemorySize = objectStoreLogMemorySize; - if (!string.IsNullOrEmpty(pageSize)) opts.PageSize = pageSize; if (!string.IsNullOrEmpty(pubSubPageSize)) opts.PubSubPageSize = pubSubPageSize; - if (!string.IsNullOrEmpty(objectStorePageSize)) - opts.ObjectStorePageSize = objectStorePageSize; - - if (!string.IsNullOrEmpty(objectStoreHeapMemorySize)) - opts.ObjectStoreHeapMemorySize = objectStoreHeapMemorySize; + if (!string.IsNullOrEmpty(heapMemorySize)) + opts.HeapMemorySize = heapMemorySize; - if (!string.IsNullOrEmpty(objectStoreReadCacheHeapMemorySize)) - opts.ObjectStoreReadCacheHeapMemorySize = objectStoreReadCacheHeapMemorySize; + if (!string.IsNullOrEmpty(readCacheHeapMemorySize)) + opts.ReadCacheHeapMemorySize = readCacheHeapMemorySize; if (indexMaxSize != default) opts.IndexMaxSize = indexMaxSize; - if (objectStoreIndexMaxSize != default) opts.ObjectStoreIndexMaxSize = objectStoreIndexMaxSize; - if (lowMemory) { - opts.MemorySize = opts.ObjectStoreLogMemorySize = memorySize == default ? "1024" : memorySize; - opts.PageSize = opts.ObjectStorePageSize = pageSize == default ? "512" : pageSize; + opts.MemorySize = memorySize == default ? "1024" : memorySize; + opts.PageSize = pageSize == default ? "512" : pageSize; if (enableReadCache) { opts.ReadCacheMemorySize = opts.MemorySize; opts.ReadCachePageSize = opts.PageSize; } - - if (enableObjectStoreReadCache) - { - opts.ObjectStoreReadCacheLogMemorySize = opts.MemorySize; - opts.ObjectStoreReadCachePageSize = opts.PageSize; - } } ILoggerFactory loggerFactory = null; @@ -432,7 +409,6 @@ public static GarnetServer CreateGarnetServer( opts.RevivInChainOnly = false; opts.RevivBinRecordCounts = []; opts.RevivBinRecordSizes = []; - opts.RevivObjBinRecordCount = 256; } if (useInChainRevivOnly) @@ -468,7 +444,6 @@ public static (GarnetServer[] Nodes, GarnetServerOptions[] Options) CreateGarnet EndPointCollection endpoints, bool enableCluster = true, bool disablePubSub = false, - bool disableObjects = false, bool tryRecover = false, bool enableAOF = false, int timeout = -1, @@ -531,7 +506,6 @@ public static (GarnetServer[] Nodes, GarnetServerOptions[] Options) CreateGarnet endpoint, enableCluster: enableCluster, disablePubSub, - disableObjects, tryRecover, enableAOF, timeout, @@ -603,7 +577,6 @@ public static GarnetServerOptions GetGarnetServerOptions( EndPoint endpoint, bool enableCluster = true, bool disablePubSub = false, - bool disableObjects = false, bool tryRecover = false, bool enableAOF = false, int timeout = -1, @@ -702,18 +675,15 @@ public static GarnetServerOptions GetGarnetServerOptions( { ThreadPoolMinThreads = 100, SegmentSize = segmentSize, - ObjectStoreSegmentSize = segmentSize, EnableStorageTier = useAzureStorage || (!disableStorageTier && logDir != null), LogDir = disableStorageTier ? null : logDir, CheckpointDir = checkpointDir, EndPoints = [endpoint], DisablePubSub = disablePubSub, - DisableObjects = disableObjects, EnableDebugCommand = ConnectionProtectionOption.Yes, EnableModuleCommand = ConnectionProtectionOption.Yes, Recover = tryRecover, IndexSize = "1m", - ObjectStoreIndexSize = "16k", EnableCluster = enableCluster, CleanClusterConfig = cleanClusterConfig, ClusterTimeout = timeout, @@ -779,8 +749,8 @@ public static GarnetServerOptions GetGarnetServerOptions( if (lowMemory) { - opts.MemorySize = opts.ObjectStoreLogMemorySize = memorySize == default ? "1024" : memorySize; - opts.PageSize = opts.ObjectStorePageSize = pageSize == default ? "512" : pageSize; + opts.MemorySize = memorySize == default ? "1024" : memorySize; + opts.PageSize = pageSize == default ? "512" : pageSize; } return opts; @@ -1104,10 +1074,10 @@ public static void CreateTestLibrary(string[] namespaces, string[] referenceFile } } - public static StoreAddressInfo GetStoreAddressInfo(IServer server, bool includeReadCache = false, bool isObjectStore = false) + public static StoreAddressInfo GetStoreAddressInfo(IServer server, bool includeReadCache = false) { StoreAddressInfo result = default; - var info = isObjectStore ? server.Info("OBJECTSTORE") : server.Info("STORE"); + var info = server.Info("STORE"); foreach (var section in info) { foreach (var entry in section) diff --git a/test/Garnet.test/WriteWithExpiryTxn.cs b/test/Garnet.test/WriteWithExpiryTxn.cs index 2f2993444cc..4faacf2cc93 100644 --- a/test/Garnet.test/WriteWithExpiryTxn.cs +++ b/test/Garnet.test/WriteWithExpiryTxn.cs @@ -19,7 +19,7 @@ sealed class WriteWithExpiryTxn : CustomTransactionProcedure public override bool Prepare(TGarnetReadApi api, ref CustomProcedureInput procInput) { int offset = 0; - AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, false); + AddKey(GetNextArg(ref procInput, ref offset), LockType.Exclusive, StoreType.Main); return true; } diff --git a/website/docs/dev/onboarding.md b/website/docs/dev/onboarding.md index e787356a646..dfce20d40c0 100644 --- a/website/docs/dev/onboarding.md +++ b/website/docs/dev/onboarding.md @@ -33,12 +33,12 @@ For an introduction to Garnet and its capabilities, you can start with [Welcome ### Start hacking -1. Clone the repository - -```bash -git clone https://github.com/microsoft/garnet.git -``` - +1. Clone the repository + +```bash +git clone https://github.com/microsoft/garnet.git +``` + After cloning the repository you can either run the unit tests or run the server and use one of the RESP client suggested in Windows or Linux. 2. Run the tests suite @@ -47,13 +47,13 @@ After cloning the repository you can either run the unit tests or run the server dotnet test -c Release -l "console;verbosity=detailed" ``` -3. Run the server - -Using a size memory of 4 GB and index size of 64 MB: - -```bash -cd /main/GarnetServer/ -dotnet run -c Debug -f net8.0 -- --logger-level Trace -m 4g -i 64m +3. Run the server + +Using a size memory of 4 GB and index size of 64 MB: + +```bash +cd /main/GarnetServer/ +dotnet run -c Debug -f net8.0 -- --logger-level Trace -m 4g -i 64m ``` 4. Use the Memurai client in Windows to send commands to Garnet. A guide about how to install Memurai on Windows can be found [here](https://docs.memurai.com/en/installation.html). @@ -62,16 +62,16 @@ dotnet run -c Debug -f net8.0 -- --logger-level Trace -m 4g -i 64m 6. A third option is to install Redis-Insight on Windows. Follow the official guide [here](https://redis.com/redis-enterprise/redis-insight/#insight-form). -## Troubleshooting - -1. If you need to use TLS in Linux, follow the guide at: - - `/Garnet/test/testcerts/README.md` - -2. If you need to run the local device library, make sure to have these dependencies: - - ```bash - sudo apt install -y g++ libaio-dev uuid-dev libtbb-dev +## Troubleshooting + +1. If you need to use TLS in Linux, follow the guide at: + + `/Garnet/test/testcerts/README.md` + +2. If you need to run the local device library, make sure to have these dependencies: + + ```bash + sudo apt install -y g++ libaio-dev uuid-dev libtbb-dev ``` ## Garnet API development @@ -104,6 +104,8 @@ RESP representation: **Tsavorite** and **Garnet** rely heavily on these two types for allocating data in memory and then transfer it on the network layer. Understanding and familiarity with both of them will be very helpful for a better understanding of the code in general. * [Documentation about Span](https://learn.microsoft.com/en-us/dotnet/api/system.span-1?view=net-7.0) + + * [Unsafe code best practices](https://learn.microsoft.com/en-us/dotnet/standard/unsafe-code/best-practices) * [Use of pointers and unsafe code](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code) @@ -150,12 +152,12 @@ Any new feature, change to existing functionality or bug fixing needs to be done /// /// Iterates the set of keys in the main store. /// -/// The pattern to apply for filtering -/// When true the filter is omitted -/// The value of the cursor in the command request -/// Value of the cursor returned -/// The list of keys from the stores -/// The size of the batch of keys +/// The pattern to apply for filtering +/// When true the filter is omitted +/// The value of the cursor in the command request +/// Value of the cursor returned +/// The list of keys from the stores +/// The size of the batch of keys /// Type of key to filter out /// public bool DbScan(ArgSlice patternB, bool allKeys, long cursor, out long storeCursor, out List Keys, long count = 10, Span type = default); @@ -188,3 +190,4 @@ Note that Tsavorite has its own solution file and test suite in the folder `