diff --git a/libs/cluster/Server/ClusterManagerSlotState.cs b/libs/cluster/Server/ClusterManagerSlotState.cs index 4b536557dd..a475fd9a04 100644 --- a/libs/cluster/Server/ClusterManagerSlotState.cs +++ b/libs/cluster/Server/ClusterManagerSlotState.cs @@ -463,7 +463,7 @@ public static unsafe void DeleteKeysInSlotsFromMainStore(BasicGarnetApi BasicGar while (iter.GetNext(out _)) { ref SpanByte key = ref iter.GetKey(); - var s = NumUtils.HashSlot(key.ToPointer(), key.Length); + var s = HashSlotUtils.HashSlot(key.ToPointer(), key.Length); if (slots.Contains(s)) _ = BasicGarnetApi.DELETE(ref key, StoreType.Main); } @@ -481,7 +481,7 @@ public static unsafe void DeleteKeysInSlotsFromObjectStore(BasicGarnetApi BasicG { ref var key = ref iterObject.GetKey(); ref var value = ref iterObject.GetValue(); - var s = NumUtils.HashSlot(key); + var s = HashSlotUtils.HashSlot(key); if (slots.Contains(s)) _ = BasicGarnetApi.DELETE(key, StoreType.Object); } diff --git a/libs/cluster/Server/Migration/MigrationKeyIterationFunctions.cs b/libs/cluster/Server/Migration/MigrationKeyIterationFunctions.cs index e3ec222392..fa2d94ccb6 100644 --- a/libs/cluster/Server/Migration/MigrationKeyIterationFunctions.cs +++ b/libs/cluster/Server/Migration/MigrationKeyIterationFunctions.cs @@ -27,7 +27,7 @@ internal MainStoreMigrateSlots(MigrateSession session, HashSet slots) public bool SingleReader(ref SpanByte key, ref SpanByte value, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) { cursorRecordResult = CursorRecordResult.Accept; // default; not used here - var s = NumUtils.HashSlot(key.ToPointer(), key.Length); + var s = HashSlotUtils.HashSlot(key.ToPointer(), key.Length); if (slots.Contains(s) && !ClusterSession.Expired(ref value) && !session.WriteOrSendMainStoreKeyValuePair(ref key, ref value)) return false; @@ -54,7 +54,7 @@ internal ObjectStoreMigrateSlots(MigrateSession session, HashSet slots) public bool SingleReader(ref byte[] key, ref IGarnetObject value, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) { cursorRecordResult = CursorRecordResult.Accept; // default; not used here - var slot = NumUtils.HashSlot(key); + var slot = HashSlotUtils.HashSlot(key); if (slots.Contains(slot) && !ClusterSession.Expired(ref value)) { @@ -85,7 +85,7 @@ internal MainStoreDeleteKeysInSlot(ClientSession public bool SingleReader(ref SpanByte key, ref SpanByte value, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) { cursorRecordResult = CursorRecordResult.Accept; // default; not used here - if (NumUtils.HashSlot(key.ToPointer(), key.LengthWithoutMetadata) == slot && !Expired(ref value)) + if (HashSlotUtils.HashSlot(key.ToPointer(), key.LengthWithoutMetadata) == slot && !Expired(ref value)) keyCount++; return true; } @@ -46,7 +46,7 @@ public bool SingleReader(ref byte[] key, ref IGarnetObject value, RecordMetadata cursorRecordResult = CursorRecordResult.Accept; // default; not used here , out CursorRecordResult cursorRecordResult fixed (byte* keyPtr = key) { - if (NumUtils.HashSlot(keyPtr, key.Length) == slot && !Expired(ref value)) + if (HashSlotUtils.HashSlot(keyPtr, key.Length) == slot && !Expired(ref value)) keyCount++; } return true; @@ -73,7 +73,7 @@ internal MainStoreGetKeysInSlot(List keys, int slot, int maxKeyCount) public bool SingleReader(ref SpanByte key, ref SpanByte value, RecordMetadata recordMetadata, long numberOfRecords, out CursorRecordResult cursorRecordResult) { cursorRecordResult = CursorRecordResult.Accept; // default; not used here, out CursorRecordResult cursorRecordResult - if (NumUtils.HashSlot(key.ToPointer(), key.LengthWithoutMetadata) == slot && !Expired(ref value)) + if (HashSlotUtils.HashSlot(key.ToPointer(), key.LengthWithoutMetadata) == slot && !Expired(ref value)) keys.Add(key.ToByteArray()); return keys.Count < maxKeyCount; } @@ -100,7 +100,7 @@ public bool SingleReader(ref byte[] key, ref IGarnetObject value, RecordMetadata cursorRecordResult = CursorRecordResult.Accept; // default; not used here fixed (byte* keyPtr = key) { - if (NumUtils.HashSlot(keyPtr, key.Length) == slot && !Expired(ref value)) + if (HashSlotUtils.HashSlot(keyPtr, key.Length) == slot && !Expired(ref value)) keys.Add(key); } return true; diff --git a/libs/cluster/Session/ClusterSlotVerify.cs b/libs/cluster/Session/ClusterSlotVerify.cs index 5659f7f2d4..34a20af441 100644 --- a/libs/cluster/Session/ClusterSlotVerify.cs +++ b/libs/cluster/Session/ClusterSlotVerify.cs @@ -133,7 +133,7 @@ static ClusterSlotVerificationResult ArrayCrosslotVerify(int keyCount, ref byte* if (!RespReadUtils.ReadPtrWithLengthHeader(ref valPtr, ref vsize, ref ptr, endPtr)) return new(SlotVerifiedState.OK, 0); - var slot = NumUtils.HashSlot(keyPtr, ksize); + var slot = HashSlotUtils.HashSlot(keyPtr, ksize); for (var c = 1; c < keyCount; c++) { @@ -148,7 +148,7 @@ static ClusterSlotVerificationResult ArrayCrosslotVerify(int keyCount, ref byte* if (!RespReadUtils.ReadPtrWithLengthHeader(ref valPtr, ref vsize, ref ptr, endPtr)) return new(SlotVerifiedState.OK, 0); - var _slot = NumUtils.HashSlot(keyPtr, ksize); + var _slot = HashSlotUtils.HashSlot(keyPtr, ksize); crossSlot |= (_slot != slot); } diff --git a/libs/cluster/Session/MigrateCommand.cs b/libs/cluster/Session/MigrateCommand.cs index ca02f4b28b..0d3e1b4d36 100644 --- a/libs/cluster/Session/MigrateCommand.cs +++ b/libs/cluster/Session/MigrateCommand.cs @@ -151,7 +151,7 @@ private bool TryMIGRATE(int count, byte* ptr) if (pstate != MigrateCmdParseState.SUCCESS) continue; // Check if all keys are local R/W because we migrate keys and need to be able to delete them - var slot = NumUtils.HashSlot(keyPtr, ksize); + var slot = HashSlotUtils.HashSlot(keyPtr, ksize); if (!current.IsLocal(slot, readCommand: false)) { pstate = MigrateCmdParseState.SLOTNOTLOCAL; diff --git a/libs/cluster/Session/RespClusterMigrateCommands.cs b/libs/cluster/Session/RespClusterMigrateCommands.cs index 3cd370c5f3..b330cb5408 100644 --- a/libs/cluster/Session/RespClusterMigrateCommands.cs +++ b/libs/cluster/Session/RespClusterMigrateCommands.cs @@ -86,7 +86,7 @@ private bool NetworkClusterMigrate(ReadOnlySpan bufSpan, int count, out bo continue; } - var slot = NumUtils.HashSlot(key.ToPointer(), key.LengthWithoutMetadata); + var slot = HashSlotUtils.HashSlot(key.ToPointer(), key.LengthWithoutMetadata); if (!currentConfig.IsImportingSlot(slot))//Slot is not in importing state { migrateState = 1; @@ -119,7 +119,7 @@ private bool NetworkClusterMigrate(ReadOnlySpan bufSpan, int count, out bo if (migrateState > 0) continue; - var slot = NumUtils.HashSlot(key); + var slot = HashSlotUtils.HashSlot(key); if (!currentConfig.IsImportingSlot(slot))//Slot is not in importing state { migrateState = 1; diff --git a/libs/cluster/Session/RespClusterSlotManagementCommands.cs b/libs/cluster/Session/RespClusterSlotManagementCommands.cs index 06bcc3a4cf..56bad351fe 100644 --- a/libs/cluster/Session/RespClusterSlotManagementCommands.cs +++ b/libs/cluster/Session/RespClusterSlotManagementCommands.cs @@ -471,7 +471,7 @@ private bool NetworkClusterKeySlot(int count, out bool invalidParameters) return false; readHead = (int)(ptr - recvBufferPtr); - int slot = NumUtils.HashSlot(keyPtr, ksize); + int slot = HashSlotUtils.HashSlot(keyPtr, ksize); while (!RespWriteUtils.WriteInteger(slot, ref dcurr, dend)) SendAndReset(); diff --git a/libs/common/HashSlotUtils.cs b/libs/common/HashSlotUtils.cs new file mode 100644 index 0000000000..1eef85aad2 --- /dev/null +++ b/libs/common/HashSlotUtils.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Garnet.common +{ + public static unsafe class HashSlotUtils + { + /// + /// This table is based on the CRC-16-CCITT polynomial (0x1021) + /// +#pragma warning disable IDE0300 // Simplify collection initialization. Ignored to avoid dotnet-format bug, see https://github.com/dotnet/sdk/issues/39898 +#if NET7_0_OR_GREATER + private static ReadOnlySpan Crc16Table => new ushort[256] +#else + private static readonly ushort[] Crc16Table = new ushort[256] +#endif + { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 + }; +#pragma warning restore IDE0300 // Simplify collection initialization + + /// + /// Generate a ushort hash value using the CRC16 algorithm + /// + /// Pointer to head of data byte sequence + /// Length of byte sequence + /// + internal static unsafe ushort Hash(byte* data, int len) + { + ushort result = 0; + +#if NET7_0_OR_GREATER + ref var crc16Base = ref MemoryMarshal.GetReference(Crc16Table); +#else + ref var crc16Base = ref MemoryMarshal.GetArrayDataReference(Crc16Table); +#endif + var end = data + len; + while (data < end) + { + var index = (nuint)(uint)((result >> 8) ^ *data++) & 0xff; + result = (ushort)(Unsafe.Add(ref crc16Base, index) ^ (result << 8)); + } + return result; + } + + /// + /// Compute hash slot of given data + /// + /// + /// + public static unsafe ushort HashSlot(byte[] key) + { + fixed (byte* keyPtr = key) + return HashSlot(keyPtr, key.Length); + } + + /// + /// Compute hash slot of given data + /// + /// + /// + /// + public static unsafe ushort HashSlot(byte* keyPtr, int ksize) + { + var startPtr = keyPtr; + var end = keyPtr + ksize; + + // Find first occurence of '{' + while (startPtr < end && *startPtr != '{') { startPtr++; }; + + // Return early if did not find '{' + if (startPtr == end) return (ushort)(Hash(keyPtr, ksize) & 16383); + + var endPtr = startPtr + 1; + + // Find first occurence of '}' + while (endPtr < end && *endPtr != '}') { endPtr++; } + + // Return early if did not find '}' after '{' + if (endPtr == end || endPtr == startPtr + 1) return (ushort)(Hash(keyPtr, ksize) & 16383); + + // Return hash for byte sequence between brackets + return (ushort)(Hash(startPtr + 1, (int)(endPtr - startPtr - 1)) & 16383); + } + } +} \ No newline at end of file diff --git a/libs/common/NumUtils.cs b/libs/common/NumUtils.cs index 92c3b729df..e69f763c87 100644 --- a/libs/common/NumUtils.cs +++ b/libs/common/NumUtils.cs @@ -4,8 +4,6 @@ using System; using System.Buffers.Text; using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Garnet.common { @@ -360,175 +358,6 @@ public static int NumDigitsInLong(long v, ref bool fNeg) return 19; } - internal static unsafe int IndexOfByte(byte* src, byte value, int index, int count) - { - byte* pByte = src + index; - - // Align up the pointer to sizeof(int). - while (((int)pByte & 3) != 0) - { - if (count == 0) - return -1; - else if (*pByte == value) - return (int)(pByte - src); - - count--; - pByte++; - } - - // Fill comparer with value byte for comparisons - // - // comparer = 0/0/value/value - uint comparer = (((uint)value << 8) + (uint)value); - // comparer = value/value/value/value - comparer = (comparer << 16) + comparer; - - // Run through buffer until we hit a 4-byte section which contains - // the byte we're looking for or until we exhaust the buffer. - while (count > 3) - { - // Test the buffer for presence of value. comparer contains the byte - // replicated 4 times. - uint t1 = *(uint*)pByte; - t1 = t1 ^ comparer; - uint t2 = 0x7efefeff + t1; - t1 = t1 ^ 0xffffffff; - t1 = t1 ^ t2; - t1 = t1 & 0x81010100; - - // if t1 is zero then these 4-bytes don't contain a match - if (t1 != 0) - { - // We've found a match for value, figure out which position it's in. - int foundIndex = (int)(pByte - src); - if (pByte[0] == value) - return foundIndex; - else if (pByte[1] == value) - return foundIndex + 1; - else if (pByte[2] == value) - return foundIndex + 2; - else if (pByte[3] == value) - return foundIndex + 3; - } - - count -= 4; - pByte += 4; - - } - - // Catch any bytes that might be left at the tail of the buffer - while (count > 0) - { - if (*pByte == value) - return (int)(pByte - src); - - count--; - pByte++; - } - - // If we don't have a match return -1; - return -1; - } - - /// - /// This table is based on the CRC-16-CCITT polynomial (0x1021) - /// -#pragma warning disable IDE0300 // Simplify collection initialization. Ignored to avoid dotnet-format bug, see https://github.com/dotnet/sdk/issues/39898 -#if NET7_0_OR_GREATER - private static ReadOnlySpan Crc16Table => new ushort[256] -#else - private static readonly ushort[] Crc16Table = new ushort[256] -#endif - { - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, - 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, - 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, - 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, - 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, - 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, - 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, - 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, - 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, - 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, - 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, - 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, - 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, - 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, - 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, - 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, - 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, - 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, - 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, - 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, - 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, - 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, - 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, - 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, - 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, - 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, - 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 - }; -#pragma warning restore IDE0300 // Simplify collection initialization - - public static unsafe ushort CRC16(byte* data, int len) - { - ushort result = 0; - -#if NET7_0_OR_GREATER - ref var crc16Base = ref MemoryMarshal.GetReference(Crc16Table); -#else - ref var crc16Base = ref MemoryMarshal.GetArrayDataReference(Crc16Table); -#endif - byte* end = data + len; - while (data < end) - { - nuint index = (nuint)(uint)((result >> 8) ^ *data++) & 0xff; - result = (ushort)(Unsafe.Add(ref crc16Base, index) ^ (result << 8)); - } - return result; - } - - /// - /// Compute hash slot of given data - /// - /// - /// - public static unsafe ushort HashSlot(byte[] key) - { - fixed (byte* keyPtr = key) - return HashSlot(keyPtr, key.Length); - } - - /// - /// Compute hash slot of given data - /// - /// - /// - /// - public static unsafe ushort HashSlot(byte* keyPtr, int ksize) - { - var startTag = keyPtr; - var end = keyPtr + ksize; - while (startTag < end && *startTag++ != '{') ; - if (startTag < end - 1) - { - var endTag = startTag; - while (endTag < end && *endTag++ != '}') ; - if (endTag <= end && endTag > startTag + 1) - { - keyPtr = startTag; - ksize = (int)(endTag - startTag - 1); - Debug.Assert(ksize > 0); - } - } - return (ushort)(CRC16(keyPtr, ksize) & 16383); - } - /// /// Try to parse from pointer to integer /// diff --git a/libs/server/ArgSlice/ArgSliceUtils.cs b/libs/server/ArgSlice/ArgSliceUtils.cs index a920d0511e..f78755fdbe 100644 --- a/libs/server/ArgSlice/ArgSliceUtils.cs +++ b/libs/server/ArgSlice/ArgSliceUtils.cs @@ -14,6 +14,6 @@ public static class ArgSliceUtils /// Compute hash slot of given ArgSlice /// public static unsafe ushort HashSlot(ArgSlice argSlice) - => NumUtils.HashSlot(argSlice.ptr, argSlice.Length); + => HashSlotUtils.HashSlot(argSlice.ptr, argSlice.Length); } } \ No newline at end of file diff --git a/playground/ClusterStress/OnlineReqGen.cs b/playground/ClusterStress/OnlineReqGen.cs index 19cb58dc89..3023bb468b 100644 --- a/playground/ClusterStress/OnlineReqGen.cs +++ b/playground/ClusterStress/OnlineReqGen.cs @@ -54,7 +54,7 @@ private void GenerateCRCPrefixesForAllSlots() while (slots.Count > 0) { int keyPrefix = keyRandomGen.Next(0, int.MaxValue); - int slot = Garnet.common.NumUtils.HashSlot(Encoding.ASCII.GetBytes(keyPrefix.ToString())); + int slot = Garnet.common.HashSlotUtils.HashSlot(Encoding.ASCII.GetBytes(keyPrefix.ToString())); if (slots.Contains(slot)) { slotPrefixes[slot] = keyPrefix; @@ -72,10 +72,10 @@ private byte[] GetClusterKeyBytes(int key) public byte[] GenerateKeyBytes(out int slot) { int key = randomGen ? (zipf ? zipfg.Next() : keyRandomGen.Next(DbSize)) : (keyIndex++ % DbSize); - slot = Garnet.common.NumUtils.HashSlot(Encoding.ASCII.GetBytes(key.ToString())); + slot = Garnet.common.HashSlotUtils.HashSlot(Encoding.ASCII.GetBytes(key.ToString())); byte[] keyBytes = GetClusterKeyBytes(key); #if DEBUG - int _slot = Garnet.common.NumUtils.HashSlot(keyBytes); + int _slot = Garnet.common.HashSlotUtils.HashSlot(keyBytes); System.Diagnostics.Debug.Assert(_slot == slot, $"GenerateKeyBytes slot number incosistence {_slot}:{slot}"); #endif return keyBytes; @@ -84,10 +84,10 @@ public byte[] GenerateKeyBytes(out int slot) public string GenerateKey(out int slot) { int key = randomGen ? (zipf ? zipfg.Next() : keyRandomGen.Next(DbSize)) : (keyIndex++ % DbSize); - slot = Garnet.common.NumUtils.HashSlot(Encoding.ASCII.GetBytes(key.ToString())); + slot = Garnet.common.HashSlotUtils.HashSlot(Encoding.ASCII.GetBytes(key.ToString())); byte[] keyBytes = GetClusterKeyBytes(key); #if DEBUG - int _slot = Garnet.common.NumUtils.HashSlot(keyBytes); + int _slot = Garnet.common.HashSlotUtils.HashSlot(keyBytes); System.Diagnostics.Debug.Assert(_slot == slot, $"GenerateKeyBytes slot number incosistence {_slot}:{slot}"); #endif return Encoding.ASCII.GetString(keyBytes); diff --git a/playground/ClusterStress/ReqGenSharded.cs b/playground/ClusterStress/ReqGenSharded.cs index d775285cad..15ed40a5a8 100644 --- a/playground/ClusterStress/ReqGenSharded.cs +++ b/playground/ClusterStress/ReqGenSharded.cs @@ -14,7 +14,7 @@ private void GenerateRandomKeys() for (int i = 0; i < DbSize; i++) { int key = Start + keyRandomGen.Next(DbSize); - int slot = Garnet.common.NumUtils.HashSlot(System.Text.Encoding.ASCII.GetBytes(key.ToString())); + int slot = Garnet.common.HashSlotUtils.HashSlot(System.Text.Encoding.ASCII.GetBytes(key.ToString())); databaseKeys[slot].Add(key); } } @@ -37,7 +37,7 @@ private void GenerateKeysCoverAllSlots() { retry: int key = Start + keyRandomGen.Next(); - int slot = Garnet.common.NumUtils.HashSlot(System.Text.Encoding.ASCII.GetBytes(key.ToString())); + int slot = Garnet.common.HashSlotUtils.HashSlot(System.Text.Encoding.ASCII.GetBytes(key.ToString())); if (databaseKeys[slot].Count < keysPerSlot) databaseKeys[slot].Add(key); diff --git a/test/Garnet.test.cluster/ClusterManagementTests.cs b/test/Garnet.test.cluster/ClusterManagementTests.cs index e2a2bf5185..8753f83eb4 100644 --- a/test/Garnet.test.cluster/ClusterManagementTests.cs +++ b/test/Garnet.test.cluster/ClusterManagementTests.cs @@ -195,6 +195,58 @@ public void ClusterResetTest() context.clusterTestUtils.WaitUntilNodeIsKnownByAllNodes(0, context.logger); } + [Test, Order(5)] + public void ClusterKeySlotTest() + { + var node_count = 1; + context.CreateInstances(node_count); + context.CreateConnection(); + + (string, int)[] testCases = [("6e6bzswz8}", 7038), + ("8}jb94e7tf", 4828), + ("{}2xc5pbb7", 11672), + ("vr{a07}pdt", 12154), + ("cx{ldv}wdl", 14261), + ("erv805by}u", 15389), + ("{ey1pqbij}", 8341), + ("2tbjjyn}n8", 5152), + ("t}jehlyo06", 1232), + ("{u08t}xjal", 2490), + ("5g{mkb95a}", 3345), + ("x{v}x70nka", 7761), + ("g67ikt}q8q", 7694), + ("ovi8}mn7t7", 14473), + ("p5ljmg{}8s", 11196), + ("3wov{fd}8m", 3502), + ("bxmcjzi3{}", 10246), + ("{b1rrm7rn}", 14105), + ("e0{4ylm}78", 5069), + ("rkptge5}sx", 3468), + ("o6{uyxsy}j", 3278), + ("ykd6q{ma8}", 5754), + ("w{j5pz3iy}", 6520), + ("mhsr{dm}x0", 15077), + ("0}dtokfryr", 5134), + ("h7}0cj9mwm", 8187), + ("w{jhqd}frk", 5369), + ("5yzd{6}hzw", 5781), + ("w6b4vgtzr}", 6045), + ("4{b17h85}l", 5923), + ("Hm{W\x13\x1c", 7517), + ("zyy8yt1chw", 3081), + ("7858tqv03y", 773), + ("fdhhuk8yqv", 5763), + ("8bfgeino4s", 6257)]; + + foreach (var testCase in testCases) + { + var key = testCase.Item1; + var expectedSlot = testCase.Item2; + var slot = context.clusterTestUtils.ClusterKeySlot(0, key: key, logger: context.logger); + Assert.AreEqual(expectedSlot, slot, $"{key}"); + } + } + //[Test, Order(5)] //[Category("CLUSTER")] public void ClusterRestartNodeDropGossip() diff --git a/test/Garnet.test.cluster/ClusterMigrateTests.cs b/test/Garnet.test.cluster/ClusterMigrateTests.cs index 1c8bf8ede2..6ad32a75b9 100644 --- a/test/Garnet.test.cluster/ClusterMigrateTests.cs +++ b/test/Garnet.test.cluster/ClusterMigrateTests.cs @@ -8,6 +8,7 @@ using System.Net; using System.Text; using System.Threading; +using Garnet.common; using Microsoft.Extensions.Logging; using NUnit.Framework; using StackExchange.Redis; @@ -330,15 +331,17 @@ public void ClusterAddDelSlots() #endregion #region DelSlots - resp = context.clusterTestUtils.AddDelSlots(0, new List { 7638 }, true); - Assert.AreEqual(resp, "OK"); - byte[] key = Encoding.ASCII.GetBytes("{abc}0"); byte[] val = Encoding.ASCII.GetBytes("1234"); + var slot = HashSlotUtils.HashSlot(key); + resp = context.clusterTestUtils.AddDelSlots(0, [slot], true); + Assert.AreEqual(resp, "OK"); + + var respState = context.clusterTestUtils.SetKey(0, key, val, out var _, out var _, out var _, logger: context.logger); Assert.AreEqual(respState, ResponseState.OK); - resp = context.clusterTestUtils.AddDelSlots(0, new List { 7638 }, false); + resp = context.clusterTestUtils.AddDelSlots(0, [slot], false); Assert.AreEqual(resp, "OK"); respState = context.clusterTestUtils.SetKey(0, key, val, out var _, out var _, out var _, logger: context.logger); @@ -347,7 +350,7 @@ public void ClusterAddDelSlots() resp = context.clusterTestUtils.GetKey(0, key, out var _, out var _, out var _, out var _, logger: context.logger); Assert.AreEqual(resp, "CLUSTERDOWN"); - resp = context.clusterTestUtils.AddDelSlots(0, new List { 7638 }, true); + resp = context.clusterTestUtils.AddDelSlots(0, [slot], true); Assert.AreEqual(resp, "OK"); resp = context.clusterTestUtils.GetKey(0, key, out var _, out var _, out var _, out var _, logger: context.logger); @@ -374,7 +377,9 @@ public void ClusterSlotChangeStatus() var val = Encoding.ASCII.GetBytes("1234"); var respState = context.clusterTestUtils.SetKey(sourcePortIndex, key, val, out _, out _, out _, logger: context.logger); Assert.AreEqual(respState, ResponseState.OK); - var slot = 7638; + var slot = (int)HashSlotUtils.HashSlot(key); + var expectedSlot = 7638; + Assert.AreEqual(expectedSlot, slot); var sourceNodeId = context.clusterTestUtils.GetNodeIdFromNode(sourcePortIndex, context.logger); var targetNodeId = context.clusterTestUtils.GetNodeIdFromNode(targetPortIndex, context.logger); @@ -442,7 +447,7 @@ public void ClusterSlotChangeStatus() resp = context.clusterTestUtils.GetKey(otherNodeIndex, key, out slot, out var address, out var port, out var responseState, logger: context.logger); Assert.AreEqual(ResponseState.MOVED, responseState); Assert.AreEqual(resp, "MOVED"); - Assert.AreEqual(slot, 7638); + Assert.AreEqual(expectedSlot, slot); Assert.AreEqual(address, context.clusterTestUtils.GetEndPoint(sourcePortIndex).Address.ToString()); Assert.AreEqual(port, context.clusterTestUtils.GetEndPoint(sourcePortIndex).Port); @@ -455,7 +460,7 @@ public void ClusterSlotChangeStatus() resp = context.clusterTestUtils.GetKey(sourcePortIndex, Encoding.ASCII.GetBytes("{abc}1"), out slot, out address, out port, out responseState, logger: context.logger); Assert.AreEqual(ResponseState.ASK, responseState); Assert.AreEqual(resp, "ASK"); - Assert.AreEqual(slot, 7638); + Assert.AreEqual(expectedSlot, slot); Assert.AreEqual(address, context.clusterTestUtils.GetEndPoint(targetPortIndex).Address.ToString()); Assert.AreEqual(port, context.clusterTestUtils.GetEndPoint(targetPortIndex).Port); @@ -463,7 +468,7 @@ public void ClusterSlotChangeStatus() resp = context.clusterTestUtils.GetKey(targetPortIndex, Encoding.ASCII.GetBytes("{abc}1"), out slot, out address, out port, out responseState, logger: context.logger); Assert.AreEqual(ResponseState.MOVED, responseState); Assert.AreEqual(resp, "MOVED"); - Assert.AreEqual(slot, 7638); + Assert.AreEqual(expectedSlot, slot); Assert.AreEqual(address, context.clusterTestUtils.GetEndPoint(sourcePortIndex).Address.ToString()); Assert.AreEqual(port, context.clusterTestUtils.GetEndPoint(sourcePortIndex).Port); @@ -474,7 +479,7 @@ public void ClusterSlotChangeStatus() //5. request write on source node to new key redirect. respState = context.clusterTestUtils.SetKey(sourcePortIndex, Encoding.ASCII.GetBytes("{abc}1"), Encoding.ASCII.GetBytes("5678"), out slot, out address, out port, logger: context.logger); Assert.AreEqual(respState, ResponseState.ASK); - Assert.AreEqual(slot, 7638); + Assert.AreEqual(expectedSlot, slot); Assert.AreEqual(address, context.clusterTestUtils.GetEndPoint(targetPortIndex).Address.ToString()); Assert.AreEqual(port, context.clusterTestUtils.GetEndPoint(targetPortIndex).Port); @@ -486,18 +491,18 @@ public void ClusterSlotChangeStatus() #endregion #region RESET_SLOT_STATE - resp = context.clusterTestUtils.SetSlot(targetPortIndex, 7638, "STABLE", "", logger: context.logger); + resp = context.clusterTestUtils.SetSlot(targetPortIndex, expectedSlot, "STABLE", "", logger: context.logger); Assert.AreEqual(resp, "OK"); resp = context.clusterTestUtils.GetKey(targetPortIndex, Encoding.ASCII.GetBytes("{abc}1"), out slot, out address, out port, out responseState, logger: context.logger); Assert.AreEqual(ResponseState.MOVED, responseState); Assert.AreEqual(resp, "MOVED"); - Assert.AreEqual(slot, 7638); + Assert.AreEqual(expectedSlot, slot); Assert.AreEqual(address, context.clusterTestUtils.GetEndPoint(sourcePortIndex).Address.ToString()); Assert.AreEqual(port, context.clusterTestUtils.GetEndPoint(sourcePortIndex).Port); - resp = context.clusterTestUtils.SetSlot(sourcePortIndex, 7638, "STABLE", "", logger: context.logger); + resp = context.clusterTestUtils.SetSlot(sourcePortIndex, expectedSlot, "STABLE", "", logger: context.logger); Assert.AreEqual(resp, "OK"); - resp = context.clusterTestUtils.GetKey(sourcePortIndex, Encoding.ASCII.GetBytes("{abc}1"), out slot, out address, out port, out responseState, logger: context.logger); + resp = context.clusterTestUtils.GetKey(sourcePortIndex, Encoding.ASCII.GetBytes("{abc}1"), out _, out _, out _, out responseState, logger: context.logger); Assert.AreEqual(ResponseState.OK, responseState); #endregion @@ -514,7 +519,6 @@ public void ClusterRedirectMessage() context.CreateConnection(useTLS: UseTLS); _ = context.clusterTestUtils.SimpleSetupCluster(logger: context.logger); var key = Encoding.ASCII.GetBytes("{abc}0"); - var slot = ClusterTestUtils.HashSlot(key); List keys = []; @@ -529,32 +533,35 @@ public void ClusterRedirectMessage() vals.Add(newKey); } - var resp = context.clusterTestUtils.SetMultiKey(0, keys, vals, out var _, out var _, out var _); + var sourceNodeIndex = 0; + var otherNodeIndex = 1; + + var resp = context.clusterTestUtils.SetMultiKey(sourceNodeIndex, keys, vals, out var _, out var _, out var _); Assert.AreEqual(resp, "OK"); - _ = context.clusterTestUtils.GetMultiKey(0, keys, out var valuesGet, out _, out _, out _); + _ = context.clusterTestUtils.GetMultiKey(sourceNodeIndex, keys, out var valuesGet, out _, out _, out _); Assert.AreEqual(valuesGet, vals); keys[0][1] = (byte)('w'); - resp = context.clusterTestUtils.GetMultiKey(0, keys, out _, out _, out _, out _); + resp = context.clusterTestUtils.GetMultiKey(sourceNodeIndex, keys, out _, out _, out _, out _); Assert.AreEqual(resp, "CROSSSLOT"); - resp = context.clusterTestUtils.SetMultiKey(0, keys, vals, out _, out _, out _); + resp = context.clusterTestUtils.SetMultiKey(sourceNodeIndex, keys, vals, out _, out _, out _); Assert.AreEqual(resp, "CROSSSLOT"); keys[0][1] = (byte)('a'); Assert.AreEqual(ClusterTestUtils.HashSlot(keys[0]), ClusterTestUtils.HashSlot(keys[1])); - resp = context.clusterTestUtils.GetMultiKey(1, keys, out _, out var _slot, out var _address, out var _port); + resp = context.clusterTestUtils.GetMultiKey(otherNodeIndex, keys, out _, out var _slot, out var _address, out var _port); Assert.AreEqual(resp, "MOVED"); Assert.AreEqual(_slot, slot); - Assert.AreEqual(_address, context.clusterTestUtils.GetEndPoint(0).Address.ToString()); - Assert.AreEqual(_port, context.clusterTestUtils.GetEndPoint(0).Port); + Assert.AreEqual(_address, context.clusterTestUtils.GetEndPoint(sourceNodeIndex).Address.ToString()); + Assert.AreEqual(_port, context.clusterTestUtils.GetEndPoint(sourceNodeIndex).Port); - resp = context.clusterTestUtils.SetMultiKey(1, keys, vals, out _slot, out _address, out _port); + resp = context.clusterTestUtils.SetMultiKey(otherNodeIndex, keys, vals, out _slot, out _address, out _port); Assert.AreEqual(resp, "MOVED"); Assert.AreEqual(_slot, slot); - Assert.AreEqual(_address, context.clusterTestUtils.GetEndPoint(0).Address.ToString()); - Assert.AreEqual(_port, context.clusterTestUtils.GetEndPoint(0).Port); + Assert.AreEqual(_address, context.clusterTestUtils.GetEndPoint(sourceNodeIndex).Address.ToString()); + Assert.AreEqual(_port, context.clusterTestUtils.GetEndPoint(sourceNodeIndex).Port); context.logger.LogDebug("1. ClusterRedirectMessageTest done"); } @@ -852,7 +859,7 @@ public void ClusterSimpleMigrateSlotsWithObjects() var key = Encoding.ASCII.GetBytes("{abc}0"); var slot = ClusterTestUtils.HashSlot(key); var memberCount = 10; - Assert.AreEqual(slot, 7638); + Assert.AreEqual(7638, slot); context.logger.LogDebug($"1. Loading object keys data started"); List> memberPair; @@ -906,7 +913,7 @@ public void ClusterSimpleMigrateKeys() var keyCount = 10; var key = Encoding.ASCII.GetBytes("{abc}a"); - List keys = new(); + List keys = []; var _workingSlot = ClusterTestUtils.HashSlot(key); Assert.AreEqual(7638, _workingSlot); diff --git a/test/Garnet.test.cluster/ClusterTestUtils.cs b/test/Garnet.test.cluster/ClusterTestUtils.cs index e4813db3e2..8fee8284ae 100644 --- a/test/Garnet.test.cluster/ClusterTestUtils.cs +++ b/test/Garnet.test.cluster/ClusterTestUtils.cs @@ -741,7 +741,7 @@ public static ushort HashSlot(byte[] key) fixed (byte* ptr = key) { byte* keyPtr = ptr; - return NumUtils.HashSlot(keyPtr, key.Length); + return HashSlotUtils.HashSlot(keyPtr, key.Length); } } @@ -1897,6 +1897,30 @@ public string ClusterReset(IPEndPoint endPoint, bool soft = true, int expiry = 6 } } + public int ClusterKeySlot(int nodeIndex, string key, ILogger logger = null) + => ClusterKeySlot((IPEndPoint)endpoints[nodeIndex], key, logger); + + public int ClusterKeySlot(IPEndPoint endPoint, string key, ILogger logger = null) + { + try + { + var server = redis.GetServer(endPoint); + var args = new List() { + "keyslot", + Encoding.ASCII.GetBytes(key) + }; + + var result = (string)server.Execute("cluster", args); + return int.Parse(result); + } + catch (Exception ex) + { + logger?.LogError(ex, "An error has occured; ClusterKeySlot"); + Assert.Fail(); + return -1; + } + } + public ClusterConfiguration ClusterNodes(int nodeIndex, ILogger logger = null) => ClusterNodes((IPEndPoint)endpoints[nodeIndex], logger);