From 3aa431d2031f2ebac87130ab50af24f2159b7ed2 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Wed, 17 Feb 2021 14:05:49 -0800 Subject: [PATCH 01/37] First implementation of new SI API --- cs/benchmark/FasterSpanByteYcsbBenchmark.cs | 2 +- cs/benchmark/FasterYcsbBenchmark.cs | 21 ++- cs/benchmark/Program.cs | 18 ++- cs/benchmark/SecondaryIndexes.cs | 33 ++++ cs/src/core/Allocator/GenericAllocator.cs | 4 +- .../ClientSession/AdvancedClientSession.cs | 65 +++++++- cs/src/core/ClientSession/ClientSession.cs | 64 +++++++- cs/src/core/Index/Common/Contexts.cs | 7 + cs/src/core/Index/FASTER/FASTER.cs | 43 ++++- cs/src/core/Index/FASTER/FASTERImpl.cs | 30 ++-- cs/src/core/Index/FASTER/FASTERLegacy.cs | 7 +- cs/src/core/Index/FASTER/RecordAccessor.cs | 26 +++ cs/src/core/Index/Interfaces/FunctionsBase.cs | 6 +- .../Index/Interfaces/IAdvancedFunctions.cs | 18 ++- .../core/Index/Interfaces/IFasterSession.cs | 5 +- cs/src/core/SecondaryIndex/ISecondaryIndex.cs | 96 +++++++++++ .../SecondaryIndex/SecondaryIndexBroker.cs | 151 ++++++++++++++++++ .../SecondaryIndex/SecondaryIndexException.cs | 34 ++++ cs/test/ReadAddressTests.cs | 4 +- 19 files changed, 589 insertions(+), 45 deletions(-) create mode 100644 cs/benchmark/SecondaryIndexes.cs create mode 100644 cs/src/core/SecondaryIndex/ISecondaryIndex.cs create mode 100644 cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs create mode 100644 cs/src/core/SecondaryIndex/SecondaryIndexException.cs diff --git a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs index 504337df6..6be76f392 100644 --- a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs +++ b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs @@ -300,7 +300,7 @@ public unsafe void Run() long startTailAddress = store.Log.TailAddress; Console.WriteLine("Start tail address = " + startTailAddress); - if (!storeWasRecovered && this.backupMode.HasFlag(BackupMode.Backukp) && kPeriodicCheckpointMilliseconds <= 0) + if (!storeWasRecovered && this.backupMode.HasFlag(BackupMode.Backup) && kPeriodicCheckpointMilliseconds <= 0) { Console.WriteLine("Checkpointing FasterKV for fast restart"); store.TakeFullCheckpoint(out _); diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index a9416de7b..925cc9487 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -82,7 +82,7 @@ public enum Op : ulong volatile bool done = false; - public FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distribution_, int readPercent_, int backupOptions_) + public FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distribution_, int readPercent_, int backupOptions_, int indexType_) { // Pin loading thread if it is not used for checkpointing if (kPeriodicCheckpointMilliseconds <= 0) @@ -110,13 +110,26 @@ public FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distributio var path = "D:\\data\\FasterYcsbBenchmark\\"; device = Devices.CreateLogDevice(path + "hlog", preallocateFile: true); + var secondaryIndexType = (SecondaryIndexType)indexType_; if (kSmallMemoryLog) store = new FasterKV - (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); + (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, + new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }, + supportsMutableIndexes: secondaryIndexType != SecondaryIndexType.None); else store = new FasterKV - (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); + (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true }, + new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }, + supportsMutableIndexes: secondaryIndexType != SecondaryIndexType.None); + + if (secondaryIndexType != SecondaryIndexType.None) + { + if (secondaryIndexType.HasFlag(SecondaryIndexType.Key)) + store.SecondaryIndexBroker.AddIndex(new NullKeyIndex()); + if (secondaryIndexType.HasFlag(SecondaryIndexType.Value)) + store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); + } } private void RunYcsb(int thread_idx) @@ -295,7 +308,7 @@ public unsafe void Run() long startTailAddress = store.Log.TailAddress; Console.WriteLine("Start tail address = " + startTailAddress); - if (!storeWasRecovered && this.backupMode.HasFlag(BackupMode.Backukp) && kPeriodicCheckpointMilliseconds <= 0) + if (!storeWasRecovered && this.backupMode.HasFlag(BackupMode.Backup) && kPeriodicCheckpointMilliseconds <= 0) { Console.WriteLine("Checkpointing FasterKV for fast restart"); store.TakeFullCheckpoint(out _); diff --git a/cs/benchmark/Program.cs b/cs/benchmark/Program.cs index dd75e2561..5fb00c044 100644 --- a/cs/benchmark/Program.cs +++ b/cs/benchmark/Program.cs @@ -28,6 +28,14 @@ class Options "\n 3 = Both (Recover FasterKV if the Checkpoint is available, else populate FasterKV from data and Checkpoint it so it can be Restored in a subsequent run)")] public int Backup { get; set; } + [Option('i', "index", Required = false, Default = 0, + HelpText = "Secondary index type(s); these implement a no-op index to test the overhead on FasterKV operations:" + + "\n 0 = None (default)" + + "\n 1 = Key-based index" + + "\n 2 = Value-based index" + + "\n 3 = Both index types")] + public int SecondaryIndexType { get; set; } + [Option('r', "read_percent", Required = false, Default = 50, HelpText = "Percentage of reads (-1 for 100% read-modify-write")] public int ReadPercent { get; set; } @@ -44,7 +52,13 @@ enum BenchmarkType : int [Flags] enum BackupMode : int { - None, Restore, Backukp, Both + None, Restore, Backup, Both + }; + + [Flags] + enum SecondaryIndexType : int + { + None, Key, Value, Both }; public class Program @@ -62,7 +76,7 @@ public static void Main(string[] args) if (b == BenchmarkType.Ycsb) { - var test = new FASTER_YcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent, options.Backup); + var test = new FASTER_YcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent, options.Backup, options.SecondaryIndexType); test.Run(); } else if (b == BenchmarkType.SpanByte) diff --git a/cs/benchmark/SecondaryIndexes.cs b/cs/benchmark/SecondaryIndexes.cs new file mode 100644 index 000000000..78f9fc634 --- /dev/null +++ b/cs/benchmark/SecondaryIndexes.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using FASTER.core; + +namespace FASTER.benchmark +{ + class NullKeyIndex : ISecondaryKeyIndex + { + public string Name => "KeyIndex"; + + public bool IsMutable => true; + + public void Delete(ref Key key) { } + + public void Insert(ref Key key) { } + + public void Upsert(ref Key key, bool isMutable) { } + } + + class NullValueIndex : ISecondaryValueIndex + { + public string Name => "ValueIndex"; + + public bool IsMutable => true; + + public void Delete(ref Value value, long recordId) { } + + public void Insert(ref Value value, long recordId) { } + + public void Upsert(ref Value value, long recordId, bool isMutable) { } + } +} diff --git a/cs/src/core/Allocator/GenericAllocator.cs b/cs/src/core/Allocator/GenericAllocator.cs index 8373a70e0..90346f663 100644 --- a/cs/src/core/Allocator/GenericAllocator.cs +++ b/cs/src/core/Allocator/GenericAllocator.cs @@ -922,7 +922,7 @@ protected override bool RetrievedFullRecord(byte* record, ref AsyncIOContext public override bool KeyHasObjects() { - return SerializerSettings.keySerializer != null; + return SerializerSettings?.keySerializer != null; } /// @@ -931,7 +931,7 @@ public override bool KeyHasObjects() /// public override bool ValueHasObjects() { - return SerializerSettings.valueSerializer != null; + return SerializerSettings?.valueSerializer != null; } #endregion diff --git a/cs/src/core/ClientSession/AdvancedClientSession.cs b/cs/src/core/ClientSession/AdvancedClientSession.cs index 9af9c34f4..ea4c7a512 100644 --- a/cs/src/core/ClientSession/AdvancedClientSession.cs +++ b/cs/src/core/ClientSession/AdvancedClientSession.cs @@ -737,15 +737,54 @@ public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) { - return _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, address); + if (!_clientSession.fht.SupportsMutableIndexes) + return _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, address); + + ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + try + { + if (!recordInfo.Tombstone && _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, address)) + { + // KeyIndexes do not need notification of in-place updates because the key does not change. + if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) + _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); + return true; + } + } + finally + { + recordInfo.Unlock(); + } + return false; + } + + public bool ConcurrentDeleter(ref Key key, ref Value value, long address) + { + if (!_clientSession.fht.SupportsMutableIndexes) + return _clientSession.functions.ConcurrentDeleter(ref key, ref value, address); + + ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + try + { + if (_clientSession.fht.SecondaryIndexBroker.MutableKeyIndexCount > 0) + _clientSession.fht.SecondaryIndexBroker.Delete(ref key); + if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) + _clientSession.fht.SecondaryIndexBroker.Delete(ref value, address); + return _clientSession.functions.ConcurrentDeleter(ref key, ref value, address); + } + finally + { + recordInfo.Unlock(); + } + return true; } public bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) => _clientSession.functions.NeedCopyUpdate(ref key, ref input, ref oldValue); - public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long oldAddress, long newAddress) + public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long address) { - _clientSession.functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue, oldAddress, newAddress); + _clientSession.functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue, address); } public void DeleteCompletionCallback(ref Key key, Context ctx) @@ -770,7 +809,25 @@ public void InitialUpdater(ref Key key, ref Input input, ref Value value, long a public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address) { - return _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, address); + if (!_clientSession.fht.SupportsMutableIndexes) + return _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, address); + + ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + try + { + if (!recordInfo.Tombstone && _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, address)) + { + // KeyIndexes do not need notification of in-place updates because the key does not change. + if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) + _clientSession.fht.SecondaryIndexBroker.Upsert(ref value, address); + return true; + } + } + finally + { + recordInfo.Unlock(); + } + return false; } public void ReadCompletionCallback(ref Key key, ref Input input, ref Output output, Context ctx, Status status, RecordInfo recordInfo) diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index 77845f9ae..f4b0be1cb 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -747,13 +747,53 @@ public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) { - return _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst); + if (!_clientSession.fht.SupportsMutableIndexes) + return _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst); + + ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + try + { + if (!recordInfo.Tombstone && _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) + { + // KeyIndexes do not need notification of in-place updates because the key does not change. + if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) + _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); + return true; + } + } + finally + { + recordInfo.Unlock(); + } + return false; + } + + public bool ConcurrentDeleter(ref Key key, ref Value value, long address) + { + // Non-Advanced IFunctions has no ConcurrentDeleter + if (!_clientSession.fht.SupportsMutableIndexes) + return false; + + ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + try + { + if (_clientSession.fht.SecondaryIndexBroker.MutableKeyIndexCount > 0) + _clientSession.fht.SecondaryIndexBroker.Delete(ref key); + if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) + _clientSession.fht.SecondaryIndexBroker.Delete(ref value, address); + _clientSession.fht.SetRecordDeleted(ref recordInfo, ref value); + } + finally + { + recordInfo.Unlock(); + } + return true; } public bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) => _clientSession.functions.NeedCopyUpdate(ref key, ref input, ref oldValue); - public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long oldAddress, long newAddress) + public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long address) { _clientSession.functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue); } @@ -780,7 +820,25 @@ public void InitialUpdater(ref Key key, ref Input input, ref Value value, long a public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address) { - return _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value); + if (!_clientSession.fht.SupportsMutableIndexes) + return _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value); + + ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + try + { + if (!recordInfo.Tombstone && _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value)) + { + // KeyIndexes do not need notification of in-place updates because the key does not change. + if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) + _clientSession.fht.SecondaryIndexBroker.Upsert(ref value, address); + return true; + } + } + finally + { + recordInfo.Unlock(); + } + return false; } public void ReadCompletionCallback(ref Key key, ref Input input, ref Output output, Context ctx, Status status, RecordInfo recordInfo) diff --git a/cs/src/core/Index/Common/Contexts.cs b/cs/src/core/Index/Common/Contexts.cs index 38fc75b16..2dcb2ee10 100644 --- a/cs/src/core/Index/Common/Contexts.cs +++ b/cs/src/core/Index/Common/Contexts.cs @@ -89,6 +89,7 @@ internal struct PendingContext internal const byte kSkipReadCache = 0x01; internal const byte kNoKey = 0x02; internal const byte kSkipCopyReadsToTail = 0x04; + internal const byte kIsNewRecord = 0x08; internal static byte GetOperationFlags(ReadFlags readFlags, bool noKey = false) { @@ -119,6 +120,12 @@ internal bool SkipCopyReadsToTail set => operationFlags = value ? (byte)(operationFlags | kSkipCopyReadsToTail) : (byte)(operationFlags & ~kSkipCopyReadsToTail); } + internal bool IsNewRecord + { + get => (operationFlags & kIsNewRecord) != 0; + set => operationFlags = value ? (byte)(operationFlags | kIsNewRecord) : (byte)(operationFlags & ~kIsNewRecord); + } + public void Dispose() { key?.Dispose(); diff --git a/cs/src/core/Index/FASTER/FASTER.cs b/cs/src/core/Index/FASTER/FASTER.cs index 5d44056e6..e6bcc9019 100644 --- a/cs/src/core/Index/FASTER/FASTER.cs +++ b/cs/src/core/Index/FASTER/FASTER.cs @@ -37,7 +37,6 @@ public partial class FasterKV : FasterBase, private readonly CopyReadsToTail CopyReadsToTail; private readonly bool FoldOverSnapshot; internal readonly int sectorSize; - private readonly bool WriteDefaultOnDelete; internal bool RelaxedCPR; /// @@ -85,6 +84,17 @@ public partial class FasterKV : FasterBase, internal ConcurrentDictionary _recoveredSessions; + /// + /// This FASTER instance support indexes that receive updates immediately on relevant FASTER operations, + /// when they are done in the mutable region. + /// + public readonly bool SupportsMutableIndexes; + + /// + /// Manages secondary indexes for this FASTER instance. + /// + public SecondaryIndexBroker SecondaryIndexBroker { get; } = new SecondaryIndexBroker(); + /// /// Create FASTER instance /// @@ -94,10 +104,11 @@ public partial class FasterKV : FasterBase, /// Serializer settings /// FASTER equality comparer for key /// + /// If true, this FASTER instance supports mutable indexes public FasterKV(long size, LogSettings logSettings, CheckpointSettings checkpointSettings = null, SerializerSettings serializerSettings = null, IFasterEqualityComparer comparer = null, - VariableLengthStructSettings variableLengthStructSettings = null) + VariableLengthStructSettings variableLengthStructSettings = null, bool supportsMutableIndexes = false) { if (comparer != null) this.comparer = comparer; @@ -162,8 +173,6 @@ public FasterKV(long size, LogSettings logSettings, if ((!Utility.IsBlittable() && variableLengthStructSettings?.keyLength == null) || (!Utility.IsBlittable() && variableLengthStructSettings?.valueLength == null)) { - WriteDefaultOnDelete = true; - hlog = new GenericAllocator(logSettings, serializerSettings, this.comparer, null, epoch); Log = new LogAccessor(this, hlog); if (UseReadCache) @@ -222,6 +231,8 @@ public FasterKV(long size, LogSettings logSettings, } } + this.SupportsMutableIndexes = supportsMutableIndexes; + hlog.Initialize(); sectorSize = (int)logSettings.LogDevice.SectorSize; @@ -591,6 +602,18 @@ internal Status ContextUpsert(ref Key key if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) { status = (Status)internalStatus; + if (this.SupportsMutableIndexes && pcontext.IsNewRecord) + { + ref RecordInfo recordInfo = ref this.RecordAccessor.SpinLockRecordInfo(pcontext.logicalAddress); + if (!recordInfo.Invalid && !recordInfo.Tombstone) + { + if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) + this.SecondaryIndexBroker.Insert(ref key); + if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) + this.SecondaryIndexBroker.Insert(ref value, pcontext.logicalAddress); + } + recordInfo.Unlock(); + } } else { @@ -618,6 +641,18 @@ internal Status ContextRMW(ref Key key, r if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) { status = (Status)internalStatus; + if (this.SupportsMutableIndexes && pcontext.IsNewRecord) + { + ref RecordInfo recordInfo = ref this.RecordAccessor.SpinLockRecordInfo(pcontext.logicalAddress); + if (!recordInfo.Invalid && !recordInfo.Tombstone) + { + if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) + this.SecondaryIndexBroker.Insert(ref key); + if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) + this.SecondaryIndexBroker.Insert(ref this.hlog.GetValue(this.hlog.GetPhysicalAddress(pcontext.logicalAddress)), pcontext.logicalAddress); + } + recordInfo.Unlock(); + } } else { diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index 448ac2540..0ddec20a7 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -463,6 +463,8 @@ internal OperationStatus InternalUpsert( if (foundEntry.word == entry.word) { + pendingContext.logicalAddress = newLogicalAddress; + pendingContext.IsNewRecord = true; status = OperationStatus.SUCCESS; goto LatchRelease; } @@ -790,7 +792,7 @@ internal OperationStatus InternalRMW( { fasterSession.CopyUpdater(ref key, ref input, ref hlog.GetValue(physicalAddress), - ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize), logicalAddress, newLogicalAddress); + ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize), newLogicalAddress); status = OperationStatus.SUCCESS; } } @@ -815,6 +817,8 @@ ref hlog.GetValue(physicalAddress), if (foundEntry.word == entry.word) { + pendingContext.logicalAddress = newLogicalAddress; + pendingContext.IsNewRecord = true; goto LatchRelease; } else @@ -1025,15 +1029,8 @@ internal OperationStatus InternalDelete( // Mutable Region: Update the record in-place if (logicalAddress >= hlog.ReadOnlyAddress) { - // Apply tombstone bit to the record - hlog.GetInfo(physicalAddress).Tombstone = true; - - if (WriteDefaultOnDelete) - { - // Write default value. Ignore return value; the record is already marked - Value v = default; - fasterSession.ConcurrentWriter(ref hlog.GetKey(physicalAddress), ref v, ref hlog.GetValue(physicalAddress), logicalAddress); - } + if (!fasterSession.ConcurrentDeleter(ref hlog.GetKey(physicalAddress), ref hlog.GetValue(physicalAddress), logicalAddress)) + SetRecordDeleted(ref hlog.GetInfo(physicalAddress), ref hlog.GetValue(physicalAddress)); // Try to update hash chain and completely elide record only if previous address points to invalid address if (entry.Address == logicalAddress && hlog.GetInfo(physicalAddress).PreviousAddress < hlog.BeginAddress) @@ -1130,9 +1127,16 @@ internal OperationStatus InternalDelete( return status; } -#endregion + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetRecordDeleted(ref RecordInfo recordInfo, ref Value value) + { + recordInfo.Tombstone = true; + if (hlog.ValueHasObjects()) + value = default; + } + #endregion -#region ContainsKeyInMemory + #region ContainsKeyInMemory [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Status InternalContainsKeyInMemory( @@ -1483,7 +1487,7 @@ ref pendingContext.input.Get(), fasterSession.CopyUpdater(ref key, ref pendingContext.input.Get(), ref hlog.GetContextRecordValue(ref request), - ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize), request.logicalAddress, newLogicalAddress); + ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize), newLogicalAddress); status = OperationStatus.SUCCESS; } diff --git a/cs/src/core/Index/FASTER/FASTERLegacy.cs b/cs/src/core/Index/FASTER/FASTERLegacy.cs index 97d1b624c..ec9486f3c 100644 --- a/cs/src/core/Index/FASTER/FASTERLegacy.cs +++ b/cs/src/core/Index/FASTER/FASTERLegacy.cs @@ -337,10 +337,15 @@ public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long add return _fasterKV._functions.ConcurrentWriter(ref key, ref src, ref dst); } + public bool ConcurrentDeleter(ref Key key, ref Value value, long address) + { + return false; + } + public bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) => _fasterKV._functions.NeedCopyUpdate(ref key, ref input, ref oldValue); - public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long oldAddress, long newAddress) + public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long address) { _fasterKV._functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue); } diff --git a/cs/src/core/Index/FASTER/RecordAccessor.cs b/cs/src/core/Index/FASTER/RecordAccessor.cs index ccf9560da..1de8aeb9c 100644 --- a/cs/src/core/Index/FASTER/RecordAccessor.cs +++ b/cs/src/core/Index/FASTER/RecordAccessor.cs @@ -77,6 +77,19 @@ public bool IsReadCacheAddress(long logicalAddress) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsTombstone(long logicalAddress) => GetRecordInfo(logicalAddress).Tombstone; + /// + /// Set the record at the given logical address to deleted + /// + /// The address to examine + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetRecordDeleted(long logicalAddress) + { + ref RecordInfo recordInfo = ref GetRecordInfo(logicalAddress); + recordInfo.Tombstone = true; + if (this.fkv.hlog.ValueHasObjects()) + this.fkv.hlog.GetValue(this.fkv.hlog.GetPhysicalAddress(logicalAddress)) = default; + } + /// /// Returns the version number of the record at the given logical address /// @@ -95,6 +108,19 @@ public void SpinLock(long logicalAddress) GetRecordInfo(logicalAddress).SpinLock(); } + /// + /// Locks the RecordInfo at address, and returns that RecordInfo reference (which can be used for unlocking). + /// + /// The address to examine + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref RecordInfo SpinLockRecordInfo(long logicalAddress) + { + Debug.Assert(logicalAddress >= this.fkv.Log.ReadOnlyAddress); + ref RecordInfo recordInfo = ref GetRecordInfo(logicalAddress); + recordInfo.SpinLock(); + return ref recordInfo; + } + /// /// Unlocks the RecordInfo at address /// diff --git a/cs/src/core/Index/Interfaces/FunctionsBase.cs b/cs/src/core/Index/Interfaces/FunctionsBase.cs index d280f897f..96754aade 100644 --- a/cs/src/core/Index/Interfaces/FunctionsBase.cs +++ b/cs/src/core/Index/Interfaces/FunctionsBase.cs @@ -88,9 +88,11 @@ public virtual void SingleReader(ref Key key, ref Input input, ref Value value, public virtual void InitialUpdater(ref Key key, ref Input input, ref Value value, long address) { } public virtual bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) => true; - public virtual void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long oldAddress, long newAddress) { } + public virtual void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long address) { } public virtual bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address) { return true; } + public virtual bool ConcurrentDeleter(ref Key key, ref Value value, long address) { return false; } + public virtual void ReadCompletionCallback(ref Key key, ref Input input, ref Output output, Context ctx, Status status, RecordInfo recordInfo) { } public virtual void RMWCompletionCallback(ref Key key, ref Input input, Context ctx, Status status) { } public virtual void UpsertCompletionCallback(ref Key key, ref Value value, Context ctx) { } @@ -117,7 +119,7 @@ public class AdvancedSimpleFunctions : AdvancedFunctionsBas public override void SingleWriter(ref Key key, ref Value src, ref Value dst, long address) => dst = src; public override void InitialUpdater(ref Key key, ref Value input, ref Value value, long address) => value = input; - public override void CopyUpdater(ref Key key, ref Value input, ref Value oldValue, ref Value newValue, long oldAddress, long newAddress) => newValue = merger(input, oldValue); + public override void CopyUpdater(ref Key key, ref Value input, ref Value oldValue, ref Value newValue, long address) => newValue = merger(input, oldValue); public override bool InPlaceUpdater(ref Key key, ref Value input, ref Value value, long address) { value = merger(input, value); return true; } public override void ReadCompletionCallback(ref Key key, ref Value input, ref Value output, Context ctx, Status status, RecordInfo recordInfo) { } diff --git a/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs b/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs index 5d90968c8..dfcbf24e9 100644 --- a/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs +++ b/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs @@ -71,7 +71,7 @@ public interface IAdvancedFunctions /// The user input to be used for computing the updated value /// The existing value that would be copied. bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) -#if NETSTANDARD21 +#if NETSTANDARD2_1 => true #endif ; @@ -83,11 +83,8 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The user input to be used for computing from /// The previous value to be copied/updated /// The destination to be updated; because this is an copy to a new location, there is no previous value there. - /// The logical address of the record being copied from; can be used as a RecordId by indexing or passed to . - /// Note that this address may be in the immutable region, or may not be in memory because this method is called for a read that has gone pending. - /// Use to test before dereferencing. - /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to - void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long oldAddress, long newAddress); + /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to + void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long address); /// /// In-place update for RMW @@ -138,5 +135,14 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The location where is to be copied; because this method is called only for in-place updates, there is a previous value there. /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address); + + /// + /// Concurrent deleter; called on an Delete that finds the record in the mutable range. + /// + /// The key for the record to be deleted + /// The value for the record being deleted; because this method is called only for in-place updates, there is a previous value there. Usually this is ignored or assigned 'default'. + /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to + /// True if handled by the Functions implementation, else false + public bool ConcurrentDeleter(ref Key key, ref Value value, long address); } } \ No newline at end of file diff --git a/cs/src/core/Index/Interfaces/IFasterSession.cs b/cs/src/core/Index/Interfaces/IFasterSession.cs index 52290e295..29fbfb65a 100644 --- a/cs/src/core/Index/Interfaces/IFasterSession.cs +++ b/cs/src/core/Index/Interfaces/IFasterSession.cs @@ -1,4 +1,7 @@ -namespace FASTER.core +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace FASTER.core { /// /// Provides thread management and callback to checkpoint completion (called state machine). diff --git a/cs/src/core/SecondaryIndex/ISecondaryIndex.cs b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs new file mode 100644 index 000000000..099f9d8b8 --- /dev/null +++ b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace FASTER.core +{ + /// + /// Base, non-generic interface for a SecondaryIndex in FASTER. + /// + public interface ISecondaryIndex + { + /// + /// The identifier of the index. + /// + string Name { get; } + + /// + /// If true, the index is updated immediately on each FasterKV operation; otherwise it is updated only when record pages go ReadOnly. + /// + /// Requires + bool IsMutable { get; } + } + + /// + /// Interface for a FASTER SecondaryIndex that is derived from the FasterKV Key generic parameter. + /// + public interface ISecondaryKeyIndex : ISecondaryIndex + { + /// + /// Inserts a key into the secondary index. Called only for mutable indexes, on the initial insert of a Key. + /// + /// The key to be inserted; always mutable + /// + /// If the index is mutable and the is already there, this call should be ignored, because it is the result + /// of a race in which the record in the primary FasterKV was updated after the initial insert but before this method + /// was called. + /// + void Insert(ref TKVKey key); + + /// + /// Upserts a key into the secondary index. This may be called either immediately during a FasterKV operation, or when the page containing a record goes ReadOnly. + /// + /// The key to be inserted + /// Whether the recordId was in the mutable region of FASTER. If true, the record may subsequently be Upserted or Deleted. + /// + /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. + /// In this case, is false, and the index may move the to an immutable storage area. + /// + void Upsert(ref TKVKey key, bool isMutable); + + /// + /// Removes a key from the secondary index. Called only for mutable indexes. + /// + /// The key to be removed + void Delete(ref TKVKey key); + } + + /// + /// Interface for a FASTER SecondaryIndex that is derived from the FasterKV Value generic parameter. + /// + public interface ISecondaryValueIndex : ISecondaryIndex + { + /// + /// Inserts a recordId into the secondary index, with the associated value from which the index derives its key(s). + /// Called only for mutable indexes, on the initial insert of a Key. + /// + /// The value to be inserted; always mutable + /// The identifier of the record containing the + /// + /// If the index is mutable and the is already there for this , + /// this call should be ignored, because it is the result of a race in which the record in the primary FasterKV was + /// updated after the initial insert but before this method was called, so the on this call + /// would overwrite it with an obsolete value. + /// + void Insert(ref TKVValue value, long recordId); + + /// + /// Upserts a recordId into the secondary index, with the associated value from which the index derives its key(s). + /// This may be called either immediately during a FasterKV operation, or when the page containing a record goes ReadOnly. + /// + /// The value to be upserted + /// The identifier of the record containing the + /// Whether the recordId was in the mutable region of FASTER; if so, it may subsequently be Upserted or Deleted. + /// + /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. + ///In this case, is false, and the index may move the to an immutable storage area. + /// + void Upsert(ref TKVValue value, long recordId, bool isMutable); + + /// + /// Removes a recordId from the secondary index's key(s) derived from the . Called only for mutable indexes. + /// + /// The value from whose derived key(s) the is to be removed + /// The recordId to be removed + void Delete(ref TKVValue value, long recordId); + } +} \ No newline at end of file diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs new file mode 100644 index 000000000..258fbd220 --- /dev/null +++ b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace FASTER.core +{ + /// + /// Manages the list of secondary indexes in the FasterKV. + /// + public class SecondaryIndexBroker + { + private readonly Dictionary indexes = new Dictionary(); + + // Use arrays for faster traversal. + private ISecondaryKeyIndex[] mutableKeyIndexes = Array.Empty>(); + internal int MutableKeyIndexCount => mutableKeyIndexes.Length; + + private ISecondaryValueIndex[] mutableValueIndexes = Array.Empty>(); + internal int MutableValueIndexCount => mutableValueIndexes.Length; + + /// + /// Adds a secondary index to the list. + /// + /// + public void AddIndex(ISecondaryIndex index) + { + static bool addSpecific(TIndex idx, ref TIndex[] vec) + where TIndex : ISecondaryIndex + { + if (idx is { }) + { + if (idx.IsMutable) + { + Array.Resize(ref vec, vec.Length + 1); +#pragma warning disable IDE0056 // Use index operator (^ is not supported on .NET Framework or NETCORE pre-3.0) + vec[vec.Length - 1] = idx; +#pragma warning restore IDE0056 // Use index operator + } + return true; + } + return false; + } + + if (!addSpecific(index as ISecondaryKeyIndex, ref mutableKeyIndexes) + && !addSpecific(index as ISecondaryValueIndex, ref mutableValueIndexes)) + throw new SecondaryIndexException("Object is not a KeyIndex or ValueIndex"); + indexes[index.Name] = index; + } + + /// + /// The number of indexes registered. + /// + public int Count => indexes.Count; + + /// + /// Enumerates the list of indexes. + /// + /// + public IEnumerable GetIndexes() => indexes.Values; + + /// + /// Returns the index with the specified name. + /// + /// + public ISecondaryIndex GetIndex(string name) => indexes[name]; + + // On failure of an operation, a SecondaryIndexException is thrown by the Index + + #region Mutable KeyIndexes + /// + /// Inserts a mutable key into all mutable secondary key indexes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Insert(ref TKVKey key) + { + foreach (var keyIndex in mutableKeyIndexes) + keyIndex.Insert(ref key); + } + + /// + /// Upserts a mutable key into all mutable secondary key indexes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Upsert(ref TKVKey key) + { + foreach (var keyIndex in mutableKeyIndexes) + keyIndex.Upsert(ref key, true); + } + + /// + /// Deletes a key from all mutable secondary key indexes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Delete(ref TKVKey key) + { + foreach (var keyIndex in mutableKeyIndexes) + keyIndex.Delete(ref key); + } + #endregion Mutable KeyIndexes + + #region Mutable ValueIndexes + /// + /// Inserts a recordId keyed by a mutable value into all mutable secondary value indexes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Insert(ref TKVValue value, long recordId) + { + foreach (var valueIndex in mutableValueIndexes) + valueIndex.Insert(ref value, recordId); + } + + /// + /// Upserts a recordId keyed by a mutable value into all mutable secondary value indexes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Upsert(ref TKVValue value, long recordId) + { + foreach (var valueIndex in mutableValueIndexes) + valueIndex.Upsert(ref value, recordId, isMutable:false); + } + + /// + /// Deletes a recordId keyed by a mutable value from all mutable secondary value indexes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Delete(ref TKVValue value, long recordId) + { + foreach (var valueIndex in mutableValueIndexes) + valueIndex.Delete(ref value, recordId); + } + #endregion Mutable ValueIndexes + + /// + /// Upserts a readonly key into all secondary key indexes and readonly values into secondary value indexes. + /// + public void UpsertReadOnly(ref TKVKey key, ref TKVValue value, long recordId) + { + foreach (var index in indexes) + { + if (index is ISecondaryKeyIndex keyIndex) + keyIndex.Upsert(ref key, isMutable: false); + else if (index is ISecondaryValueIndex valueIndex) + valueIndex.Upsert(ref value, recordId, isMutable: false); + } + } + + } +} diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexException.cs b/cs/src/core/SecondaryIndex/SecondaryIndexException.cs new file mode 100644 index 000000000..015650b64 --- /dev/null +++ b/cs/src/core/SecondaryIndex/SecondaryIndexException.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Runtime.Serialization; + +namespace FASTER.core +{ + /// + /// FASTER exception base type + /// + public class SecondaryIndexException : FasterException + { + /// + public SecondaryIndexException() + { + } + + /// + public SecondaryIndexException(string message) : base(message) + { + } + + /// + public SecondaryIndexException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + public SecondaryIndexException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/cs/test/ReadAddressTests.cs b/cs/test/ReadAddressTests.cs index f609b70f4..763a30f8f 100644 --- a/cs/test/ReadAddressTests.cs +++ b/cs/test/ReadAddressTests.cs @@ -91,10 +91,10 @@ public override void InitialUpdater(ref Key key, ref Value input, ref Value valu base.InitialUpdater(ref key, ref input, ref value, address); } - public override void CopyUpdater(ref Key key, ref Value input, ref Value oldValue, ref Value newValue, long oldAddress, long newAddress) + public override void CopyUpdater(ref Key key, ref Value input, ref Value oldValue, ref Value newValue, long newAddress) { this.lastWriteAddress = newAddress; - base.CopyUpdater(ref key, ref input, ref oldValue, ref newValue, oldAddress, newAddress); + base.CopyUpdater(ref key, ref input, ref oldValue, ref newValue, newAddress); } // Track the recordInfo for its PreviousAddress. From 97bcf90f121fee7a18069b5925d892eec7ded775 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Fri, 19 Feb 2021 13:26:40 -0800 Subject: [PATCH 02/37] Add I(Advanced)Functions.Lock/Unlock and minimize SI-related locking --- cs/benchmark/FasterYcsbBenchmark.cs | 2 +- cs/src/core/Allocator/GenericAllocator.cs | 9 +-- .../ClientSession/AdvancedClientSession.cs | 62 ++++++++++++------- cs/src/core/ClientSession/ClientSession.cs | 60 +++++++++++------- cs/src/core/Index/FASTER/FASTERLegacy.cs | 4 ++ .../Index/FASTER/LogCompactionFunctions.cs | 4 ++ cs/src/core/Index/FASTER/RecordAccessor.cs | 19 +++--- cs/src/core/Index/Interfaces/FunctionsBase.cs | 10 +++ .../Index/Interfaces/IAdvancedFunctions.cs | 34 ++++++++++ cs/src/core/Index/Interfaces/IFunctions.cs | 34 ++++++++++ cs/test/AsyncTests.cs | 5 ++ cs/test/ObjectRecoveryTest2.cs | 6 ++ cs/test/ObjectRecoveryTestTypes.cs | 6 ++ cs/test/ObjectTestTypes.cs | 24 +++++++ cs/test/RecoverContinueTests.cs | 6 ++ cs/test/RecoveryTestTypes.cs | 6 ++ cs/test/SimpleRecoveryTest.cs | 6 ++ cs/test/TestTypes.cs | 18 ++++++ cs/test/VLTestTypes.cs | 12 ++++ 19 files changed, 270 insertions(+), 57 deletions(-) diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index 925cc9487..74156c16f 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -34,7 +34,7 @@ public enum Op : ulong const int kPeriodicCheckpointMilliseconds = 0; #else const bool kDumpDistribution = false; - const bool kUseSmallData = false; + const bool kUseSmallData = true; // false; const bool kUseSyntheticData = false; const bool kSmallMemoryLog = false; const bool kAffinitizedSession = true; diff --git a/cs/src/core/Allocator/GenericAllocator.cs b/cs/src/core/Allocator/GenericAllocator.cs index 90346f663..ff68f9d5f 100644 --- a/cs/src/core/Allocator/GenericAllocator.cs +++ b/cs/src/core/Allocator/GenericAllocator.cs @@ -48,20 +48,17 @@ public GenericAllocator(LogSettings settings, SerializerSettings ser } SerializerSettings = serializerSettings; + SerializerSettings = serializerSettings ?? new SerializerSettings(); if ((!keyBlittable) && (settings.LogDevice as NullDevice == null) && ((SerializerSettings == null) || (SerializerSettings.keySerializer == null))) { Debug.WriteLine("Key is not blittable, but no serializer specified via SerializerSettings. Using (slow) DataContractSerializer as default."); - if (SerializerSettings == null) - SerializerSettings = new SerializerSettings(); SerializerSettings.keySerializer = ObjectSerializer.Get(); } if ((!valueBlittable) && (settings.LogDevice as NullDevice == null) && ((SerializerSettings == null) || (SerializerSettings.valueSerializer == null))) { Debug.WriteLine("Value is not blittable, but no serializer specified via SerializerSettings. Using (slow) DataContractSerializer as default."); - if (SerializerSettings == null) - SerializerSettings = new SerializerSettings(); SerializerSettings.valueSerializer = ObjectSerializer.Get(); } @@ -922,7 +919,7 @@ protected override bool RetrievedFullRecord(byte* record, ref AsyncIOContext public override bool KeyHasObjects() { - return SerializerSettings?.keySerializer != null; + return SerializerSettings.keySerializer != null; } /// @@ -931,7 +928,7 @@ public override bool KeyHasObjects() /// public override bool ValueHasObjects() { - return SerializerSettings?.valueSerializer != null; + return SerializerSettings.valueSerializer != null; } #endregion diff --git a/cs/src/core/ClientSession/AdvancedClientSession.cs b/cs/src/core/ClientSession/AdvancedClientSession.cs index ea4c7a512..2fefc0bc3 100644 --- a/cs/src/core/ClientSession/AdvancedClientSession.cs +++ b/cs/src/core/ClientSession/AdvancedClientSession.cs @@ -735,35 +735,38 @@ public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst, address); } - public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) - { - if (!_clientSession.fht.SupportsMutableIndexes) - return _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, address); + public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) + => !_clientSession.fht.SupportsMutableIndexes || _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount == 0 + ? _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, address) + : ConcurrentWriterSI(ref key, ref src, ref dst, address); - ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + private bool ConcurrentWriterSI(ref Key key, ref Value src, ref Value dst, long address) + { + ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref dst); try { if (!recordInfo.Tombstone && _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, address)) { // KeyIndexes do not need notification of in-place updates because the key does not change. - if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) - _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); + _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); return true; } } finally { - recordInfo.Unlock(); + this.Unlock(ref recordInfo, ref key, ref dst); } return false; } - public bool ConcurrentDeleter(ref Key key, ref Value value, long address) - { - if (!_clientSession.fht.SupportsMutableIndexes) - return _clientSession.functions.ConcurrentDeleter(ref key, ref value, address); + public bool ConcurrentDeleter(ref Key key, ref Value value, long address) + => !_clientSession.fht.SupportsMutableIndexes + ? _clientSession.functions.ConcurrentDeleter(ref key, ref value, address) + : ConcurrentDeleterSI(ref key, ref value, address); - ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + private bool ConcurrentDeleterSI(ref Key key, ref Value value, long address) + { + ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref value); try { if (_clientSession.fht.SecondaryIndexBroker.MutableKeyIndexCount > 0) @@ -774,7 +777,7 @@ public bool ConcurrentDeleter(ref Key key, ref Value value, long address) } finally { - recordInfo.Unlock(); + this.Unlock(ref recordInfo, ref key, ref value); } return true; } @@ -807,25 +810,26 @@ public void InitialUpdater(ref Key key, ref Input input, ref Value value, long a _clientSession.functions.InitialUpdater(ref key, ref input, ref value, address); } - public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address) - { - if (!_clientSession.fht.SupportsMutableIndexes) - return _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, address); + public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address) + => !_clientSession.fht.SupportsMutableIndexes || _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount == 0 + ? _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, address) + : InPlaceUpdaterSI(ref key, ref input, ref value, address); - ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + private bool InPlaceUpdaterSI(ref Key key, ref Input input, ref Value value, long address) + { + ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref value); try { if (!recordInfo.Tombstone && _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, address)) { // KeyIndexes do not need notification of in-place updates because the key does not change. - if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) - _clientSession.fht.SecondaryIndexBroker.Upsert(ref value, address); + _clientSession.fht.SecondaryIndexBroker.Upsert(ref value, address); return true; } } finally { - recordInfo.Unlock(); + this.Unlock(ref recordInfo, ref key, ref value); } return false; } @@ -872,6 +876,20 @@ public IHeapContainer GetHeapContainer(ref Input input) return new VarLenHeapContainer(ref input, _clientSession.inputVariableLengthStruct, _clientSession.fht.hlog.bufferPool); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref RecordInfo Lock(long address, ref Key key, ref Value value) + { + ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.GetRecordInfo(address); + _clientSession.functions.Lock(ref recordInfo, ref key, ref value); + return ref recordInfo; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value); } } } diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index f4b0be1cb..ef4592c51 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -745,36 +745,39 @@ public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst); } - public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) - { - if (!_clientSession.fht.SupportsMutableIndexes) - return _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst); + public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) + => !_clientSession.fht.SupportsMutableIndexes || _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount == 0 + ? _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst) + : ConcurrentWriterSI(ref key, ref src, ref dst, address); - ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + private bool ConcurrentWriterSI(ref Key key, ref Value src, ref Value dst, long address) + { + ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref dst); try { if (!recordInfo.Tombstone && _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) { // KeyIndexes do not need notification of in-place updates because the key does not change. - if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) - _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); + _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); return true; } } finally { - recordInfo.Unlock(); + this.Unlock(ref recordInfo, ref key, ref dst); } return false; } public bool ConcurrentDeleter(ref Key key, ref Value value, long address) - { // Non-Advanced IFunctions has no ConcurrentDeleter - if (!_clientSession.fht.SupportsMutableIndexes) - return false; + => !_clientSession.fht.SupportsMutableIndexes || _clientSession.fht.SecondaryIndexBroker.Count == 0 + ? false + : ConcurrentDeleterSI(ref key, ref value, address); - ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + private bool ConcurrentDeleterSI(ref Key key, ref Value value, long address) + { + ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref value); try { if (_clientSession.fht.SecondaryIndexBroker.MutableKeyIndexCount > 0) @@ -785,7 +788,7 @@ public bool ConcurrentDeleter(ref Key key, ref Value value, long address) } finally { - recordInfo.Unlock(); + this.Unlock(ref recordInfo, ref key, ref value); } return true; } @@ -818,25 +821,26 @@ public void InitialUpdater(ref Key key, ref Input input, ref Value value, long a _clientSession.functions.InitialUpdater(ref key, ref input, ref value); } - public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address) - { - if (!_clientSession.fht.SupportsMutableIndexes) - return _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value); + public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address) + => !_clientSession.fht.SupportsMutableIndexes || _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount == 0 + ? _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value) + : InPlaceUpdaterSI(ref key, ref input, ref value, address); - ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.SpinLockRecordInfo(address); + private bool InPlaceUpdaterSI(ref Key key, ref Input input, ref Value value, long address) + { + ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref value); try { if (!recordInfo.Tombstone && _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value)) { // KeyIndexes do not need notification of in-place updates because the key does not change. - if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) - _clientSession.fht.SecondaryIndexBroker.Upsert(ref value, address); + _clientSession.fht.SecondaryIndexBroker.Upsert(ref value, address); return true; } } finally { - recordInfo.Unlock(); + this.Unlock(ref recordInfo, ref key, ref value); } return false; } @@ -883,6 +887,20 @@ public IHeapContainer GetHeapContainer(ref Input input) return new VarLenHeapContainer(ref input, _clientSession.inputVariableLengthStruct, _clientSession.fht.hlog.bufferPool); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref RecordInfo Lock(long address, ref Key key, ref Value value) + { + ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.GetRecordInfo(address); + _clientSession.functions.Lock(ref recordInfo, ref key, ref value); + return ref recordInfo; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value); } } } diff --git a/cs/src/core/Index/FASTER/FASTERLegacy.cs b/cs/src/core/Index/FASTER/FASTERLegacy.cs index ec9486f3c..e02bea00f 100644 --- a/cs/src/core/Index/FASTER/FASTERLegacy.cs +++ b/cs/src/core/Index/FASTER/FASTERLegacy.cs @@ -412,6 +412,10 @@ public IHeapContainer GetHeapContainer(ref Input input) { return new StandardHeapContainer(ref input); } + + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + + public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } } } diff --git a/cs/src/core/Index/FASTER/LogCompactionFunctions.cs b/cs/src/core/Index/FASTER/LogCompactionFunctions.cs index 9848ab903..73323e04a 100644 --- a/cs/src/core/Index/FASTER/LogCompactionFunctions.cs +++ b/cs/src/core/Index/FASTER/LogCompactionFunctions.cs @@ -31,6 +31,8 @@ public void SingleReader(ref Key key, ref Empty input, ref Value value, ref Empt public void SingleWriter(ref Key key, ref Value src, ref Value dst) { _functions.Copy(ref src, ref dst, _allocator.ValueLength); } public void UpsertCompletionCallback(ref Key key, ref Value value, Empty ctx) { } public void DeleteCompletionCallback(ref Key key, Empty ctx) { } + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } } internal sealed class LogCompactFunctions : IFunctions @@ -56,6 +58,8 @@ public void SingleReader(ref Key key, ref Empty input, ref Value value, ref Empt public void SingleWriter(ref Key key, ref Value src, ref Value dst) { _functions.Copy(ref src, ref dst, null); } public void UpsertCompletionCallback(ref Key key, ref Value value, Empty ctx) { } public void DeleteCompletionCallback(ref Key key, Empty ctx) { } + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } } internal unsafe struct DefaultVariableCompactionFunctions : ICompactionFunctions diff --git a/cs/src/core/Index/FASTER/RecordAccessor.cs b/cs/src/core/Index/FASTER/RecordAccessor.cs index 1de8aeb9c..8b5c1375f 100644 --- a/cs/src/core/Index/FASTER/RecordAccessor.cs +++ b/cs/src/core/Index/FASTER/RecordAccessor.cs @@ -32,13 +32,6 @@ private void VerifyIsInMemoryAddress(long logicalAddress) throw new FasterException("Address is not in the in-memory portion of the log"); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe ref RecordInfo GetRecordInfo(long logicalAddress) - { - VerifyIsInMemoryAddress(logicalAddress); - return ref this.fkv.hlog.GetInfo(this.fkv.hlog.GetPhysicalAddress(logicalAddress)); - } - /// /// Indicates whether the address is within the FasterKV HybridLog /// @@ -49,6 +42,18 @@ public bool IsReadCacheAddress(long logicalAddress) #region public interface + /// + /// Gets the record header for the address + /// + /// The address to get the record header for + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref RecordInfo GetRecordInfo(long logicalAddress) + { + VerifyIsInMemoryAddress(logicalAddress); + return ref this.fkv.hlog.GetInfo(this.fkv.hlog.GetPhysicalAddress(logicalAddress)); + } + /// /// Indicates whether the address is within the FasterKV HybridLog logical address space (does not verify alignment) /// diff --git a/cs/src/core/Index/Interfaces/FunctionsBase.cs b/cs/src/core/Index/Interfaces/FunctionsBase.cs index 96754aade..53bc0f5b7 100644 --- a/cs/src/core/Index/Interfaces/FunctionsBase.cs +++ b/cs/src/core/Index/Interfaces/FunctionsBase.cs @@ -33,6 +33,11 @@ public virtual void RMWCompletionCallback(ref Key key, ref Input input, Context public virtual void UpsertCompletionCallback(ref Key key, ref Value value, Context ctx) { } public virtual void DeleteCompletionCallback(ref Key key, Context ctx) { } public virtual void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) { } + +#if !NETSTANDARD2_1 + public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + public virtual void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } +#endif } /// @@ -98,6 +103,11 @@ public virtual void RMWCompletionCallback(ref Key key, ref Input input, Context public virtual void UpsertCompletionCallback(ref Key key, ref Value value, Context ctx) { } public virtual void DeleteCompletionCallback(ref Key key, Context ctx) { } public virtual void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) { } + +#if !NETSTANDARD2_1 + public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + public virtual void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } +#endif } /// diff --git a/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs b/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs index dfcbf24e9..3aac569cc 100644 --- a/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs +++ b/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs @@ -144,5 +144,39 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to /// True if handled by the Functions implementation, else false public bool ConcurrentDeleter(ref Key key, ref Value value, long address); + + /// + /// User-provided lock call, defaulting to no-op. A default exclusive implementation is available via . + /// See also to use two bits of an existing int value. + /// + /// The header for the current record + /// The key for the current record + /// The value for the current record + /// + /// This is called only for records guaranteed to be in the mutable range. + /// + void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) +#if NETSTANDARD2_1 + {} +#else + ; +#endif + + /// + /// User-provided unlock call, defaulting to no-op. A default exclusive implementation is available via . + /// See also to use two bits of an existing int value. + /// + /// The header for the current record + /// The key for the current record + /// The value for the current record + /// + /// This is called only for records guaranteed to be in the mutable range. + /// + void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) +#if NETSTANDARD2_1 + {} +#else + ; +#endif } } \ No newline at end of file diff --git a/cs/src/core/Index/Interfaces/IFunctions.cs b/cs/src/core/Index/Interfaces/IFunctions.cs index ed6e98048..a99240228 100644 --- a/cs/src/core/Index/Interfaces/IFunctions.cs +++ b/cs/src/core/Index/Interfaces/IFunctions.cs @@ -125,6 +125,40 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The value to be copied to /// The location where is to be copied; because this method is called only for in-place updates, there is a previous value there. bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst); + + /// + /// User-provided lock call, defaulting to no-op. A default exclusive implementation is available via . + /// See also to use two bits of an existing int value. + /// + /// The header for the current record + /// The key for the current record + /// The value for the current record + /// + /// This is called only for records guaranteed to be in the mutable range. + /// + void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) +#if NETSTANDARD2_1 + {} +#else + ; +#endif + + /// + /// User-provided unlock call, defaulting to no-op. A default exclusive implementation is available via . + /// See also to use two bits of an existing int value. + /// + /// The header for the current record + /// The key for the current record + /// The value for the current record + /// + /// This is called only for records guaranteed to be in the mutable range. + /// + void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) +#if NETSTANDARD2_1 + {} +#else + ; +#endif } /// diff --git a/cs/test/AsyncTests.cs b/cs/test/AsyncTests.cs index acc6e2bdc..20037ca8a 100644 --- a/cs/test/AsyncTests.cs +++ b/cs/test/AsyncTests.cs @@ -176,6 +176,11 @@ public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, { newValue.numClicks += oldValue.numClicks + input.numClicks.numClicks; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } + public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } +#endif } } \ No newline at end of file diff --git a/cs/test/ObjectRecoveryTest2.cs b/cs/test/ObjectRecoveryTest2.cs index ebcada5e9..2746b0031 100644 --- a/cs/test/ObjectRecoveryTest2.cs +++ b/cs/test/ObjectRecoveryTest2.cs @@ -258,5 +258,11 @@ public void UpsertCompletionCallback(ref MyKey key, ref MyValue value, MyContext public void RMWCompletionCallback(ref MyKey key, ref MyInput input, MyContext ctx, Status status) { } public void DeleteCompletionCallback(ref MyKey key, MyContext ctx) { } public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) { } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } + + public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } +#endif } } diff --git a/cs/test/ObjectRecoveryTestTypes.cs b/cs/test/ObjectRecoveryTestTypes.cs index ece0e8249..7db68d818 100644 --- a/cs/test/ObjectRecoveryTestTypes.cs +++ b/cs/test/ObjectRecoveryTestTypes.cs @@ -139,5 +139,11 @@ public void CopyUpdater(ref AdId key, ref Input input, ref NumClicks oldValue, r { newValue = new NumClicks { numClicks = oldValue.numClicks + input.numClicks.numClicks }; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } + + public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } +#endif } } diff --git a/cs/test/ObjectTestTypes.cs b/cs/test/ObjectTestTypes.cs index 42e2658d4..51bf21451 100644 --- a/cs/test/ObjectTestTypes.cs +++ b/cs/test/ObjectTestTypes.cs @@ -143,6 +143,12 @@ public void SingleWriter(ref MyKey key, ref MyValue src, ref MyValue dst) { dst = src; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } + + public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } +#endif } public class MyFunctionsDelete : IFunctions @@ -224,6 +230,12 @@ public void SingleWriter(ref MyKey key, ref MyValue src, ref MyValue dst) { dst = src; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } + + public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } +#endif } public class MixedFunctions : IFunctions @@ -286,6 +298,12 @@ public void SingleWriter(ref int key, ref MyValue src, ref MyValue dst) { dst = src; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref int key, ref MyValue value) { } + + public void Unlock(ref RecordInfo recordInfo, ref int key, ref MyValue value) { } +#endif } public class MyLargeValue @@ -391,5 +409,11 @@ public void SingleWriter(ref MyKey key, ref MyLargeValue src, ref MyLargeValue d { dst = src; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyLargeValue value) { } + + public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyLargeValue value) { } +#endif } } diff --git a/cs/test/RecoverContinueTests.cs b/cs/test/RecoverContinueTests.cs index a2bb9b465..4f4cf8981 100644 --- a/cs/test/RecoverContinueTests.cs +++ b/cs/test/RecoverContinueTests.cs @@ -223,5 +223,11 @@ public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, { newValue.numClicks += oldValue.numClicks + input.numClicks.numClicks; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } + + public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } +#endif } } diff --git a/cs/test/RecoveryTestTypes.cs b/cs/test/RecoveryTestTypes.cs index 7bc2e8e8b..eaf1b3e70 100644 --- a/cs/test/RecoveryTestTypes.cs +++ b/cs/test/RecoveryTestTypes.cs @@ -107,5 +107,11 @@ public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, { newValue.numClicks += oldValue.numClicks + input.numClicks.numClicks; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } + + public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } +#endif } } diff --git a/cs/test/SimpleRecoveryTest.cs b/cs/test/SimpleRecoveryTest.cs index 2545048c5..a5c782f83 100644 --- a/cs/test/SimpleRecoveryTest.cs +++ b/cs/test/SimpleRecoveryTest.cs @@ -324,6 +324,12 @@ public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, { newValue.numClicks += oldValue.numClicks + input.numClicks.numClicks; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } + + public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } +#endif } } \ No newline at end of file diff --git a/cs/test/TestTypes.cs b/cs/test/TestTypes.cs index 5535afd78..b4ce884b5 100644 --- a/cs/test/TestTypes.cs +++ b/cs/test/TestTypes.cs @@ -122,6 +122,12 @@ public void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruc newValue.vfield1 = oldValue.vfield1 + input.ifield1; newValue.vfield2 = oldValue.vfield2 + input.ifield2; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } + + public void Unlock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } +#endif } public class FunctionsCompaction : IFunctions @@ -201,6 +207,12 @@ public void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruc newValue.vfield1 = oldValue.vfield1 + input.ifield1; newValue.vfield2 = oldValue.vfield2 + input.ifield2; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } + + public void Unlock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } +#endif } public class FunctionsCopyOnWrite : IFunctions @@ -278,5 +290,11 @@ public void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruc newValue.vfield1 = oldValue.vfield1 + input.ifield1; newValue.vfield2 = oldValue.vfield2 + input.ifield2; } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } + + public void Unlock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } +#endif } } diff --git a/cs/test/VLTestTypes.cs b/cs/test/VLTestTypes.cs index 5c301f20f..46da5325f 100644 --- a/cs/test/VLTestTypes.cs +++ b/cs/test/VLTestTypes.cs @@ -181,6 +181,12 @@ public bool InPlaceUpdater(ref Key key, ref Input input, ref VLValue value) public void CopyUpdater(ref Key key, ref Input input, ref VLValue oldValue, ref VLValue newValue) { } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref Key key, ref VLValue value) { } + + public void Unlock(ref RecordInfo recordInfo, ref Key key, ref VLValue value) { } +#endif } public class VLFunctions2 : IFunctions @@ -252,5 +258,11 @@ public bool InPlaceUpdater(ref VLValue key, ref Input input, ref VLValue value) public void CopyUpdater(ref VLValue key, ref Input input, ref VLValue oldValue, ref VLValue newValue) { } + +#if !NETSTANDARD2_1 + public void Lock(ref RecordInfo recordInfo, ref VLValue key, ref VLValue value) { } + + public void Unlock(ref RecordInfo recordInfo, ref VLValue key, ref VLValue value) { } +#endif } } From 1b10d6493026b4adf4ccbdc57f79c9a71f2a5001 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Sat, 20 Feb 2021 21:39:56 -0800 Subject: [PATCH 03/37] some experimental stuff to test --- cs/benchmark/Functions.cs | 4 ++ cs/src/core/ClientSession/ClientSession.cs | 78 ++++++++++++++++++++-- cs/src/core/Index/FASTER/FASTERImpl.cs | 26 ++++++-- 3 files changed, 95 insertions(+), 13 deletions(-) diff --git a/cs/benchmark/Functions.cs b/cs/benchmark/Functions.cs index 7e72383c0..f75317518 100644 --- a/cs/benchmark/Functions.cs +++ b/cs/benchmark/Functions.cs @@ -80,5 +80,9 @@ public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Va { newValue.value = input.value + oldValue.value; } + + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + + public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } } } diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index ef4592c51..8a90f1d79 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -745,14 +745,74 @@ public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst); } - public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) - => !_clientSession.fht.SupportsMutableIndexes || _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount == 0 - ? _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst) - : ConcurrentWriterSI(ref key, ref src, ref dst, address); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) + { + if (_clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) + { + // KeyIndexes do not need notification of in-place updates because the key does not change. + if (_clientSession.fht.SupportsMutableIndexes && _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) + _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); + return true; + } + return false; + } + +#if false // outer -- experimental + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) + { + return address > 0 // surrogate for: _clientSession.recordLocker is null + ? ConcurrentWriterNoLock(ref key, ref src, ref dst, address) + : ConcurrentWriterLock(ref key, ref src, ref dst, address); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, long address) + { + if (_clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) + { + // KeyIndexes do not need notification of in-place updates because the key does not change. + if (_clientSession.fht.SupportsMutableIndexes && _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) + _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); + return true; + } + return false; + } - private bool ConcurrentWriterSI(ref Key key, ref Value src, ref Value dst, long address) + private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, long address) { - ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref dst); + RecordInfo recordInfo = default; + this.Lock(ref recordInfo, ref key, ref dst); + try + { + // KeyIndexes do not need notification of in-place updates because the key does not change. + if (_clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) + { + if (_clientSession.fht.SupportsMutableIndexes && _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) + _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); + return true; + } + } + finally + { + this.Unlock(ref recordInfo, ref key, ref dst); + } + return false; + } + + private bool ConcurrentWriterSI(ref RecordInfo recordInfo, ref Key key, ref Value src, ref Value dst, long address) + { +#if true + if (!recordInfo.Tombstone && _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) + { + _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); + return true; + } + return false; +#else + //ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref dst); + RecordInfo recordInfo = default; try { if (!recordInfo.Tombstone && _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) @@ -764,10 +824,14 @@ private bool ConcurrentWriterSI(ref Key key, ref Value src, ref Value dst, long } finally { - this.Unlock(ref recordInfo, ref key, ref dst); + //this.Unlock(ref recordInfo, ref key, ref dst); } + //this.Unlock(ref recordInfo, ref key, ref dst); + return false; +#endif } +#endif // outer - experimental public bool ConcurrentDeleter(ref Key key, ref Value value, long address) // Non-Advanced IFunctions has no ConcurrentDeleter diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index 0ddec20a7..ffde679b4 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -334,15 +334,29 @@ internal OperationStatus InternalUpsert( out physicalAddress); } } -#endregion + #endregion // Optimization for most common case - if (sessionCtx.phase == Phase.REST && logicalAddress >= hlog.ReadOnlyAddress && !hlog.GetInfo(physicalAddress).Tombstone) + ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); + if (sessionCtx.phase == Phase.REST && logicalAddress >= hlog.ReadOnlyAddress && !recordInfo.Tombstone) { +#if false // original if (fasterSession.ConcurrentWriter(ref key, ref value, ref hlog.GetValue(physicalAddress), logicalAddress)) { return OperationStatus.SUCCESS; } +#endif + ref Value recordValue = ref hlog.GetValue(physicalAddress); + fasterSession.Lock(ref recordInfo, ref key, ref recordValue); + try + { + if (fasterSession.ConcurrentWriter(ref key, ref value, ref recordValue, logicalAddress)) + return OperationStatus.SUCCESS; + } + finally + { + fasterSession.Unlock(ref recordInfo, ref key, ref recordValue); + } goto CreateNewRecord; } @@ -1134,9 +1148,9 @@ internal void SetRecordDeleted(ref RecordInfo recordInfo, ref Value value) if (hlog.ValueHasObjects()) value = default; } - #endregion +#endregion - #region ContainsKeyInMemory +#region ContainsKeyInMemory [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Status InternalContainsKeyInMemory( @@ -1758,9 +1772,9 @@ private bool TraceBackForKeyMatch( foundPhysicalAddress = Constants.kInvalidAddress; return false; } - #endregion +#endregion - #region Split Index +#region Split Index private void SplitBuckets(long hash) { long masked_bucket_index = hash & state[1 - resizeInfo.version].size_mask; From d9db3979a08f1be69c1d9ec6ef345bd250048bdf Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Sun, 21 Feb 2021 10:49:45 -0800 Subject: [PATCH 04/37] Experiment with SupportsLocks --- cs/benchmark/Functions.cs | 2 ++ .../ClientSession/AdvancedClientSession.cs | 2 ++ cs/src/core/ClientSession/ClientSession.cs | 16 +++++++---- cs/src/core/Index/FASTER/FASTER.cs | 4 +++ cs/src/core/Index/FASTER/FASTERImpl.cs | 5 ++-- cs/src/core/Index/FASTER/FASTERLegacy.cs | 2 ++ .../Index/FASTER/LogCompactionFunctions.cs | 2 ++ cs/src/core/Index/Interfaces/FunctionsBase.cs | 4 +-- .../Index/Interfaces/IAdvancedFunctions.cs | 10 +++++++ cs/src/core/Index/Interfaces/IFunctions.cs | 28 +++++++++++++------ cs/test/AsyncTests.cs | 1 + cs/test/ObjectRecoveryTest2.cs | 2 +- cs/test/ObjectRecoveryTestTypes.cs | 2 +- cs/test/ObjectTestTypes.cs | 8 +++--- cs/test/RecoverContinueTests.cs | 2 +- cs/test/RecoveryTestTypes.cs | 2 +- cs/test/SimpleRecoveryTest.cs | 2 +- cs/test/TestTypes.cs | 6 ++-- cs/test/VLTestTypes.cs | 4 +-- 19 files changed, 71 insertions(+), 33 deletions(-) diff --git a/cs/benchmark/Functions.cs b/cs/benchmark/Functions.cs index f75317518..9e2d60349 100644 --- a/cs/benchmark/Functions.cs +++ b/cs/benchmark/Functions.cs @@ -81,6 +81,8 @@ public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Va newValue.value = input.value + oldValue.value; } + public bool SupportsLocks => false; + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } diff --git a/cs/src/core/ClientSession/AdvancedClientSession.cs b/cs/src/core/ClientSession/AdvancedClientSession.cs index 2fefc0bc3..4493d4531 100644 --- a/cs/src/core/ClientSession/AdvancedClientSession.cs +++ b/cs/src/core/ClientSession/AdvancedClientSession.cs @@ -885,6 +885,8 @@ private ref RecordInfo Lock(long address, ref Key key, ref Value value) return ref recordInfo; } + public bool SupportsLocks => _clientSession.functions.SupportsLocks; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value); diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index 8a90f1d79..242bea9c9 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -745,6 +745,7 @@ public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst); } +#if false // internal no-lock [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) { @@ -757,14 +758,14 @@ public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long add } return false; } +#endif -#if false // outer -- experimental [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) { - return address > 0 // surrogate for: _clientSession.recordLocker is null - ? ConcurrentWriterNoLock(ref key, ref src, ref dst, address) - : ConcurrentWriterLock(ref key, ref src, ref dst, address); + return _clientSession.functions.SupportsLocks + ? ConcurrentWriterLock(ref key, ref src, ref dst, address) + : ConcurrentWriterNoLock(ref key, ref src, ref dst, address); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -782,12 +783,12 @@ private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, l private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, long address) { - RecordInfo recordInfo = default; + RecordInfo recordInfo = default; // TODO: Get a real recordInfo here this.Lock(ref recordInfo, ref key, ref dst); try { // KeyIndexes do not need notification of in-place updates because the key does not change. - if (_clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) + if (!recordInfo.Tombstone && _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) { if (_clientSession.fht.SupportsMutableIndexes && _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); @@ -801,6 +802,7 @@ private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, lon return false; } +#if false // outer -- experimental private bool ConcurrentWriterSI(ref RecordInfo recordInfo, ref Key key, ref Value src, ref Value dst, long address) { #if true @@ -960,6 +962,8 @@ private ref RecordInfo Lock(long address, ref Key key, ref Value value) return ref recordInfo; } + public bool SupportsLocks => _clientSession.functions.SupportsLocks; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value); diff --git a/cs/src/core/Index/FASTER/FASTER.cs b/cs/src/core/Index/FASTER/FASTER.cs index e6bcc9019..49da900b7 100644 --- a/cs/src/core/Index/FASTER/FASTER.cs +++ b/cs/src/core/Index/FASTER/FASTER.cs @@ -602,6 +602,7 @@ internal Status ContextUpsert(ref Key key if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) { status = (Status)internalStatus; +#if false // TODO indexing if (this.SupportsMutableIndexes && pcontext.IsNewRecord) { ref RecordInfo recordInfo = ref this.RecordAccessor.SpinLockRecordInfo(pcontext.logicalAddress); @@ -614,6 +615,7 @@ internal Status ContextUpsert(ref Key key } recordInfo.Unlock(); } +#endif } else { @@ -641,6 +643,7 @@ internal Status ContextRMW(ref Key key, r if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) { status = (Status)internalStatus; +#if false // TODO indexing if (this.SupportsMutableIndexes && pcontext.IsNewRecord) { ref RecordInfo recordInfo = ref this.RecordAccessor.SpinLockRecordInfo(pcontext.logicalAddress); @@ -653,6 +656,7 @@ internal Status ContextRMW(ref Key key, r } recordInfo.Unlock(); } +#endif } else { diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index ffde679b4..98b683a21 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -340,12 +340,12 @@ internal OperationStatus InternalUpsert( ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); if (sessionCtx.phase == Phase.REST && logicalAddress >= hlog.ReadOnlyAddress && !recordInfo.Tombstone) { -#if false // original +#if true // original if (fasterSession.ConcurrentWriter(ref key, ref value, ref hlog.GetValue(physicalAddress), logicalAddress)) { return OperationStatus.SUCCESS; } -#endif +#else ref Value recordValue = ref hlog.GetValue(physicalAddress); fasterSession.Lock(ref recordInfo, ref key, ref recordValue); try @@ -357,6 +357,7 @@ internal OperationStatus InternalUpsert( { fasterSession.Unlock(ref recordInfo, ref key, ref recordValue); } +#endif goto CreateNewRecord; } diff --git a/cs/src/core/Index/FASTER/FASTERLegacy.cs b/cs/src/core/Index/FASTER/FASTERLegacy.cs index e02bea00f..e785fe337 100644 --- a/cs/src/core/Index/FASTER/FASTERLegacy.cs +++ b/cs/src/core/Index/FASTER/FASTERLegacy.cs @@ -413,6 +413,8 @@ public IHeapContainer GetHeapContainer(ref Input input) return new StandardHeapContainer(ref input); } + public bool SupportsLocks => false; + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } diff --git a/cs/src/core/Index/FASTER/LogCompactionFunctions.cs b/cs/src/core/Index/FASTER/LogCompactionFunctions.cs index 73323e04a..ef371dd3f 100644 --- a/cs/src/core/Index/FASTER/LogCompactionFunctions.cs +++ b/cs/src/core/Index/FASTER/LogCompactionFunctions.cs @@ -31,6 +31,7 @@ public void SingleReader(ref Key key, ref Empty input, ref Value value, ref Empt public void SingleWriter(ref Key key, ref Value src, ref Value dst) { _functions.Copy(ref src, ref dst, _allocator.ValueLength); } public void UpsertCompletionCallback(ref Key key, ref Value value, Empty ctx) { } public void DeleteCompletionCallback(ref Key key, Empty ctx) { } + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } } @@ -58,6 +59,7 @@ public void SingleReader(ref Key key, ref Empty input, ref Value value, ref Empt public void SingleWriter(ref Key key, ref Value src, ref Value dst) { _functions.Copy(ref src, ref dst, null); } public void UpsertCompletionCallback(ref Key key, ref Value value, Empty ctx) { } public void DeleteCompletionCallback(ref Key key, Empty ctx) { } + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } } diff --git a/cs/src/core/Index/Interfaces/FunctionsBase.cs b/cs/src/core/Index/Interfaces/FunctionsBase.cs index 53bc0f5b7..fa86b302f 100644 --- a/cs/src/core/Index/Interfaces/FunctionsBase.cs +++ b/cs/src/core/Index/Interfaces/FunctionsBase.cs @@ -35,6 +35,7 @@ public virtual void DeleteCompletionCallback(ref Key key, Context ctx) { } public virtual void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) { } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } public virtual void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } #endif @@ -104,10 +105,9 @@ public virtual void UpsertCompletionCallback(ref Key key, ref Value value, Conte public virtual void DeleteCompletionCallback(ref Key key, Context ctx) { } public virtual void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) { } -#if !NETSTANDARD2_1 + public bool SupportsLocks => false; public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } public virtual void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } -#endif } /// diff --git a/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs b/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs index 3aac569cc..713a60bc2 100644 --- a/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs +++ b/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs @@ -145,6 +145,16 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// True if handled by the Functions implementation, else false public bool ConcurrentDeleter(ref Key key, ref Value value, long address); + /// + /// Whether this Functions implementation actually locks in and + /// + bool SupportsLocks +#if NETSTANDARD2_1 + => false; +#else + { get; } +#endif + /// /// User-provided lock call, defaulting to no-op. A default exclusive implementation is available via . /// See also to use two bits of an existing int value. diff --git a/cs/src/core/Index/Interfaces/IFunctions.cs b/cs/src/core/Index/Interfaces/IFunctions.cs index a99240228..a483df970 100644 --- a/cs/src/core/Index/Interfaces/IFunctions.cs +++ b/cs/src/core/Index/Interfaces/IFunctions.cs @@ -127,16 +127,26 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst); /// - /// User-provided lock call, defaulting to no-op. A default exclusive implementation is available via . - /// See also to use two bits of an existing int value. + /// Whether this Functions implementation actually locks in and /// - /// The header for the current record - /// The key for the current record - /// The value for the current record - /// - /// This is called only for records guaranteed to be in the mutable range. - /// - void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) + bool SupportsLocks +#if NETSTANDARD2_1 + => false; +#else + { get; } +#endif + + /// + /// User-provided lock call, defaulting to no-op. A default exclusive implementation is available via . + /// See also to use two bits of an existing int value. + /// + /// The header for the current record + /// The key for the current record + /// The value for the current record + /// + /// This is called only for records guaranteed to be in the mutable range. + /// + void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) #if NETSTANDARD2_1 {} #else diff --git a/cs/test/AsyncTests.cs b/cs/test/AsyncTests.cs index 20037ca8a..b34b149bf 100644 --- a/cs/test/AsyncTests.cs +++ b/cs/test/AsyncTests.cs @@ -178,6 +178,7 @@ public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } #endif diff --git a/cs/test/ObjectRecoveryTest2.cs b/cs/test/ObjectRecoveryTest2.cs index 2746b0031..3350bdd9e 100644 --- a/cs/test/ObjectRecoveryTest2.cs +++ b/cs/test/ObjectRecoveryTest2.cs @@ -260,8 +260,8 @@ public void DeleteCompletionCallback(ref MyKey key, MyContext ctx) { } public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) { } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } #endif } diff --git a/cs/test/ObjectRecoveryTestTypes.cs b/cs/test/ObjectRecoveryTestTypes.cs index 7db68d818..a7ee0f58e 100644 --- a/cs/test/ObjectRecoveryTestTypes.cs +++ b/cs/test/ObjectRecoveryTestTypes.cs @@ -141,8 +141,8 @@ public void CopyUpdater(ref AdId key, ref Input input, ref NumClicks oldValue, r } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } - public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } #endif } diff --git a/cs/test/ObjectTestTypes.cs b/cs/test/ObjectTestTypes.cs index 51bf21451..238aba929 100644 --- a/cs/test/ObjectTestTypes.cs +++ b/cs/test/ObjectTestTypes.cs @@ -145,8 +145,8 @@ public void SingleWriter(ref MyKey key, ref MyValue src, ref MyValue dst) } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } #endif } @@ -232,8 +232,8 @@ public void SingleWriter(ref MyKey key, ref MyValue src, ref MyValue dst) } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } #endif } @@ -300,8 +300,8 @@ public void SingleWriter(ref int key, ref MyValue src, ref MyValue dst) } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref int key, ref MyValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref int key, ref MyValue value) { } #endif } @@ -411,8 +411,8 @@ public void SingleWriter(ref MyKey key, ref MyLargeValue src, ref MyLargeValue d } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyLargeValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyLargeValue value) { } #endif } diff --git a/cs/test/RecoverContinueTests.cs b/cs/test/RecoverContinueTests.cs index 4f4cf8981..8a2ca2e5a 100644 --- a/cs/test/RecoverContinueTests.cs +++ b/cs/test/RecoverContinueTests.cs @@ -225,8 +225,8 @@ public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } - public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } #endif } diff --git a/cs/test/RecoveryTestTypes.cs b/cs/test/RecoveryTestTypes.cs index eaf1b3e70..a1effd42a 100644 --- a/cs/test/RecoveryTestTypes.cs +++ b/cs/test/RecoveryTestTypes.cs @@ -109,8 +109,8 @@ public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } - public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } #endif } diff --git a/cs/test/SimpleRecoveryTest.cs b/cs/test/SimpleRecoveryTest.cs index a5c782f83..2a3508d75 100644 --- a/cs/test/SimpleRecoveryTest.cs +++ b/cs/test/SimpleRecoveryTest.cs @@ -326,8 +326,8 @@ public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } - public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } #endif } diff --git a/cs/test/TestTypes.cs b/cs/test/TestTypes.cs index b4ce884b5..7c9aa5d10 100644 --- a/cs/test/TestTypes.cs +++ b/cs/test/TestTypes.cs @@ -124,8 +124,8 @@ public void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruc } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } - public void Unlock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } #endif } @@ -209,8 +209,8 @@ public void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruc } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } - public void Unlock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } #endif } @@ -292,8 +292,8 @@ public void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruc } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } - public void Unlock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } #endif } diff --git a/cs/test/VLTestTypes.cs b/cs/test/VLTestTypes.cs index 46da5325f..619649bba 100644 --- a/cs/test/VLTestTypes.cs +++ b/cs/test/VLTestTypes.cs @@ -183,8 +183,8 @@ public void CopyUpdater(ref Key key, ref Input input, ref VLValue oldValue, ref } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref Key key, ref VLValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref Key key, ref VLValue value) { } #endif } @@ -260,8 +260,8 @@ public void CopyUpdater(ref VLValue key, ref Input input, ref VLValue oldValue, } #if !NETSTANDARD2_1 + public bool SupportsLocks => false; public void Lock(ref RecordInfo recordInfo, ref VLValue key, ref VLValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref VLValue key, ref VLValue value) { } #endif } From c54018c19f131234cd1640a3d2289a51725e7f70 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Tue, 23 Feb 2021 18:15:09 -0800 Subject: [PATCH 05/37] Change to new locking scheme; update test Functions to derive from FunctionsBase --- cs/benchmark/FasterYcsbBenchmark.cs | 6 +- cs/benchmark/Functions.cs | 4 +- cs/benchmark/SecondaryIndexes.cs | 2 +- cs/samples/ReadAddress/Types.cs | 6 +- .../ClientSession/AdvancedClientSession.cs | 153 ++++++++------ cs/src/core/ClientSession/ClientSession.cs | 192 +++++++----------- cs/src/core/ClientSession/FASTERAsync.cs | 18 +- cs/src/core/Index/Common/Contexts.cs | 10 +- cs/src/core/Index/Common/RecordInfo.cs | 38 +--- cs/src/core/Index/FASTER/FASTER.cs | 93 +++++---- cs/src/core/Index/FASTER/FASTERImpl.cs | 103 ++++++---- cs/src/core/Index/FASTER/FASTERLegacy.cs | 18 +- .../Index/FASTER/LogCompactionFunctions.cs | 8 +- cs/src/core/Index/FASTER/RecordAccessor.cs | 138 ------------- cs/src/core/Index/Interfaces/FunctionsBase.cs | 54 +++-- .../Index/Interfaces/IAdvancedFunctions.cs | 65 +++--- cs/src/core/Index/Interfaces/IFunctions.cs | 52 +++-- cs/src/core/SecondaryIndex/ISecondaryIndex.cs | 5 +- .../SecondaryIndex/SecondaryIndexBroker.cs | 4 +- cs/src/core/VarLen/MemoryFunctions.cs | 4 +- cs/src/core/VarLen/SpanByteFunctions.cs | 10 +- cs/test/AsyncTests.cs | 69 ++----- cs/test/LockTests.cs | 57 +++--- cs/test/ObjectRecoveryTest2.cs | 30 +-- cs/test/ObjectRecoveryTestTypes.cs | 61 +----- cs/test/ObjectTestTypes.cs | 177 ++++------------ cs/test/ReadAddressTests.cs | 52 +++-- cs/test/RecoverContinueTests.cs | 72 ++----- cs/test/RecoveryTestTypes.cs | 52 +---- cs/test/SimpleRecoveryTest.cs | 66 ++---- cs/test/TestTypes.cs | 157 +++----------- cs/test/VLTestTypes.cs | 96 ++------- 32 files changed, 613 insertions(+), 1259 deletions(-) delete mode 100644 cs/src/core/Index/FASTER/RecordAccessor.cs diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index 74156c16f..c28ba0470 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -79,6 +79,7 @@ public enum Op : ulong readonly string distribution; readonly int readPercent; readonly Functions functions = new Functions(); + SecondaryIndexType secondaryIndexType = SecondaryIndexType.None; volatile bool done = false; @@ -110,7 +111,6 @@ public FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distributio var path = "D:\\data\\FasterYcsbBenchmark\\"; device = Devices.CreateLogDevice(path + "hlog", preallocateFile: true); - var secondaryIndexType = (SecondaryIndexType)indexType_; if (kSmallMemoryLog) store = new FasterKV @@ -159,7 +159,7 @@ private void RunYcsb(int thread_idx) int count = 0; #endif - var session = store.For(functions).NewSession(null, kAffinitizedSession); + var session = store.For(functions).NewSession(null, threadAffinitized: secondaryIndexType == SecondaryIndexType.None); while (!done) { @@ -396,7 +396,7 @@ private void SetupYcsb(int thread_idx) else Native32.AffinitizeThreadShardedNuma((uint)thread_idx, 2); // assuming two NUMA sockets - var session = store.For(functions).NewSession(null, kAffinitizedSession); + var session = store.For(functions).NewSession(null, threadAffinitized: secondaryIndexType == SecondaryIndexType.None); #if DASHBOARD var tstart = Stopwatch.GetTimestamp(); diff --git a/cs/benchmark/Functions.cs b/cs/benchmark/Functions.cs index 9e2d60349..b5a6714db 100644 --- a/cs/benchmark/Functions.cs +++ b/cs/benchmark/Functions.cs @@ -83,8 +83,8 @@ public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Va public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } - public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; } } diff --git a/cs/benchmark/SecondaryIndexes.cs b/cs/benchmark/SecondaryIndexes.cs index 78f9fc634..574ee7b02 100644 --- a/cs/benchmark/SecondaryIndexes.cs +++ b/cs/benchmark/SecondaryIndexes.cs @@ -24,7 +24,7 @@ class NullValueIndex : ISecondaryValueIndex public bool IsMutable => true; - public void Delete(ref Value value, long recordId) { } + public void Delete(long recordId) { } public void Insert(ref Value value, long recordId) { } diff --git a/cs/samples/ReadAddress/Types.cs b/cs/samples/ReadAddress/Types.cs index 086e15d69..59ccc644f 100644 --- a/cs/samples/ReadAddress/Types.cs +++ b/cs/samples/ReadAddress/Types.cs @@ -43,14 +43,14 @@ public class Context public class Functions : AdvancedSimpleFunctions { // Return false to force a chain of values. - public override bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) => false; + public override bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) => false; - public override bool InPlaceUpdater(ref Key key, ref Value input, ref Value value, long address) => false; + public override bool InPlaceUpdater(ref Key key, ref Value input, ref Value value, ref RecordInfo recordInfo, long address) => false; // Track the recordInfo for its PreviousAddress. public override void ReadCompletionCallback(ref Key key, ref Value input, ref Value output, Context ctx, Status status, RecordInfo recordInfo) { - if (!(ctx is null)) + if (ctx is { }) { ctx.recordInfo = recordInfo; ctx.status = status; diff --git a/cs/src/core/ClientSession/AdvancedClientSession.cs b/cs/src/core/ClientSession/AdvancedClientSession.cs index 4493d4531..f8aa8c592 100644 --- a/cs/src/core/ClientSession/AdvancedClientSession.cs +++ b/cs/src/core/ClientSession/AdvancedClientSession.cs @@ -37,7 +37,7 @@ public sealed class AdvancedClientSession variableLengthStruct; internal readonly IVariableLengthStruct inputVariableLengthStruct; - internal readonly AsyncFasterSession FasterSession; + internal readonly InternalFasterSession FasterSession; internal const string NotAsyncSessionErr = ClientSession>.NotAsyncSessionErr; @@ -53,7 +53,10 @@ internal AdvancedClientSession( this.functions = functions; SupportAsync = supportAsync; LatestCommitPoint = new CommitPoint { UntilSerialNo = -1, ExcludedSerialNos = null }; - FasterSession = new AsyncFasterSession(this); + FasterSession = new InternalFasterSession(this); + + if (fht.SupportsMutableIndexes && !supportAsync) + throw new FasterException("Cannot specify thread-affinitized sessions with mutable secondary indexes"); this.variableLengthStruct = sessionVariableLengthStructSettings?.valueLength; if (this.variableLengthStruct == default) @@ -715,11 +718,11 @@ void IClientSession.AtomicSwitch(int version) } // This is a struct to allow JIT to inline calls (and bypass default interface call mechanism) - internal struct AsyncFasterSession : IFasterSession + internal struct InternalFasterSession : IFasterSession { private readonly AdvancedClientSession _clientSession; - public AsyncFasterSession(AdvancedClientSession clientSession) + public InternalFasterSession(AdvancedClientSession clientSession) { _clientSession = clientSession; } @@ -730,64 +733,92 @@ public void CheckpointCompletionCallback(string guid, CommitPoint commitPoint) _clientSession.LatestCommitPoint = commitPoint; } - public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, long address) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, ref RecordInfo recordInfo, long address) { - _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst, address); + if (!this.SupportsLocks) + _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst, ref recordInfo, address); + else + ConcurrentReaderLock(ref key, ref input, ref value, ref dst, ref recordInfo, address); } - public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) - => !_clientSession.fht.SupportsMutableIndexes || _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount == 0 - ? _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, address) - : ConcurrentWriterSI(ref key, ref src, ref dst, address); - - private bool ConcurrentWriterSI(ref Key key, ref Value src, ref Value dst, long address) + public void ConcurrentReaderLock(ref Key key, ref Input input, ref Value value, ref Output dst, ref RecordInfo recordInfo, long address) { - ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref dst); - try + for (bool retry = true; retry; /* updated in loop */) { - if (!recordInfo.Tombstone && _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, address)) + long context = 0; + this.Lock(ref recordInfo, ref key, ref value, OperationType.READ, ref context); + try { - // KeyIndexes do not need notification of in-place updates because the key does not change. - _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); - return true; + _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst, ref recordInfo, address); } + finally + { + retry = !this.Unlock(ref recordInfo, ref key, ref value, OperationType.READ, context); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) + => !this.SupportsLocks + ? ConcurrentWriterNoLock(ref key, ref src, ref dst, ref recordInfo, address) + : ConcurrentWriterLock(ref key, ref src, ref dst, ref recordInfo, address); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) + => _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, ref recordInfo, address) + && _clientSession.fht.UpdateSIForIPU(ref dst, address); + + private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) + { + long context = 0; + this.Lock(ref recordInfo, ref key, ref dst, OperationType.UPSERT, ref context); + try + { + // KeyIndexes do not need notification of in-place updates because the key does not change. + return !recordInfo.Tombstone && ConcurrentWriterNoLock(ref key, ref src, ref dst, ref recordInfo, address); } finally { - this.Unlock(ref recordInfo, ref key, ref dst); + this.Unlock(ref recordInfo, ref key, ref dst, OperationType.UPSERT, context); } - return false; } - public bool ConcurrentDeleter(ref Key key, ref Value value, long address) - => !_clientSession.fht.SupportsMutableIndexes - ? _clientSession.functions.ConcurrentDeleter(ref key, ref value, address) - : ConcurrentDeleterSI(ref key, ref value, address); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ConcurrentDeleter(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) + => !this.SupportsLocks + ? ConcurrentDeleterNoLock(ref key, ref value, ref recordInfo, address) + : ConcurrentDeleterLock(ref key, ref value, ref recordInfo, address); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool ConcurrentDeleterNoLock(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) + { + if (!_clientSession.functions.ConcurrentDeleter(ref key, ref value, ref recordInfo, address)) + _clientSession.fht.SetRecordDeleted(ref value, ref recordInfo); + return _clientSession.fht.UpdateSIForDelete(ref key, address, isNewRecord:false); + } - private bool ConcurrentDeleterSI(ref Key key, ref Value value, long address) + private bool ConcurrentDeleterLock(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) { - ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref value); + long context = 0; + this.Lock(ref recordInfo, ref key, ref value, OperationType.DELETE, ref context); try { - if (_clientSession.fht.SecondaryIndexBroker.MutableKeyIndexCount > 0) - _clientSession.fht.SecondaryIndexBroker.Delete(ref key); - if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) - _clientSession.fht.SecondaryIndexBroker.Delete(ref value, address); - return _clientSession.functions.ConcurrentDeleter(ref key, ref value, address); + return ConcurrentDeleterNoLock(ref key, ref value, ref recordInfo, address); } finally { - this.Unlock(ref recordInfo, ref key, ref value); + this.Unlock(ref recordInfo, ref key, ref value, OperationType.DELETE, context); } - return true; } public bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) => _clientSession.functions.NeedCopyUpdate(ref key, ref input, ref oldValue); - public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long address) + public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue) { - _clientSession.functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue, address); + _clientSession.functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue); } public void DeleteCompletionCallback(ref Key key, Context ctx) @@ -805,33 +836,35 @@ public int GetLength(ref Value t, ref Input input) return _clientSession.variableLengthStruct.GetLength(ref t, ref input); } - public void InitialUpdater(ref Key key, ref Input input, ref Value value, long address) + public void InitialUpdater(ref Key key, ref Input input, ref Value value) { - _clientSession.functions.InitialUpdater(ref key, ref input, ref value, address); + _clientSession.functions.InitialUpdater(ref key, ref input, ref value); } - public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address) - => !_clientSession.fht.SupportsMutableIndexes || _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount == 0 - ? _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, address) - : InPlaceUpdaterSI(ref key, ref input, ref value, address); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) + => !this.SupportsLocks + ? InPlaceUpdaterNoLock(ref key, ref input, ref value, ref recordInfo, address) + : InPlaceUpdaterLock(ref key, ref input, ref value, ref recordInfo, address); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool InPlaceUpdaterNoLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) + => _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, ref recordInfo, address) + && _clientSession.fht.UpdateSIForIPU(ref value, address); - private bool InPlaceUpdaterSI(ref Key key, ref Input input, ref Value value, long address) + private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) { - ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref value); + long context = 0; + this.Lock(ref recordInfo, ref key, ref value, OperationType.RMW, ref context); try { - if (!recordInfo.Tombstone && _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, address)) - { - // KeyIndexes do not need notification of in-place updates because the key does not change. - _clientSession.fht.SecondaryIndexBroker.Upsert(ref value, address); - return true; - } + // KeyIndexes do not need notification of in-place updates because the key does not change. + return !recordInfo.Tombstone && InPlaceUpdaterNoLock(ref key, ref input, ref value, ref recordInfo, address); } finally { - this.Unlock(ref recordInfo, ref key, ref value); + this.Unlock(ref recordInfo, ref key, ref value, OperationType.RMW, context); } - return false; } public void ReadCompletionCallback(ref Key key, ref Input input, ref Output output, Context ctx, Status status, RecordInfo recordInfo) @@ -849,9 +882,9 @@ public void SingleReader(ref Key key, ref Input input, ref Value value, ref Outp _clientSession.functions.SingleReader(ref key, ref input, ref value, ref dst, address); } - public void SingleWriter(ref Key key, ref Value src, ref Value dst, long address) + public void SingleWriter(ref Key key, ref Value src, ref Value dst) { - _clientSession.functions.SingleWriter(ref key, ref src, ref dst, address); + _clientSession.functions.SingleWriter(ref key, ref src, ref dst); } public void UnsafeResumeThread() @@ -877,21 +910,11 @@ public IHeapContainer GetHeapContainer(ref Input input) return new VarLenHeapContainer(ref input, _clientSession.inputVariableLengthStruct, _clientSession.fht.hlog.bufferPool); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ref RecordInfo Lock(long address, ref Key key, ref Value value) - { - ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.GetRecordInfo(address); - _clientSession.functions.Lock(ref recordInfo, ref key, ref value); - return ref recordInfo; - } - public bool SupportsLocks => _clientSession.functions.SupportsLocks; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value); + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value, opType, ref context); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value); + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value, opType, context); } } } diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index 242bea9c9..91a7df2b4 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -35,7 +35,7 @@ public sealed class ClientSession internal readonly IVariableLengthStruct variableLengthStruct; internal readonly IVariableLengthStruct inputVariableLengthStruct; - internal readonly AsyncFasterSession FasterSession; + internal readonly InternalFasterSession FasterSession; internal const string NotAsyncSessionErr = "Session does not support async operations"; @@ -51,7 +51,10 @@ internal ClientSession( this.functions = functions; SupportAsync = supportAsync; LatestCommitPoint = new CommitPoint { UntilSerialNo = -1, ExcludedSerialNos = null }; - FasterSession = new AsyncFasterSession(this); + FasterSession = new InternalFasterSession(this); + + if (fht.SupportsMutableIndexes && !supportAsync) + throw new FasterException("Cannot specify thread-affinitized sessions with mutable secondary indexes"); this.variableLengthStruct = sessionVariableLengthStructSettings?.valueLength; if (this.variableLengthStruct == default) @@ -725,11 +728,11 @@ void IClientSession.AtomicSwitch(int version) } // This is a struct to allow JIT to inline calls (and bypass default interface call mechanism) - internal struct AsyncFasterSession : IFasterSession + internal struct InternalFasterSession : IFasterSession { private readonly ClientSession _clientSession; - public AsyncFasterSession(ClientSession clientSession) + public InternalFasterSession(ClientSession clientSession) { _clientSession = clientSession; } @@ -740,129 +743,90 @@ public void CheckpointCompletionCallback(string guid, CommitPoint commitPoint) _clientSession.LatestCommitPoint = commitPoint; } - public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, long address) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, ref RecordInfo recordInfo, long address) { - _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst); + if (!this.SupportsLocks) + _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst); + else + ConcurrentReaderLock(ref key, ref input, ref value, ref dst, ref recordInfo); } -#if false // internal no-lock - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) + public void ConcurrentReaderLock(ref Key key, ref Input input, ref Value value, ref Output dst, ref RecordInfo recordInfo) { - if (_clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) + for (bool retry = true; retry; /* updated in loop */) { - // KeyIndexes do not need notification of in-place updates because the key does not change. - if (_clientSession.fht.SupportsMutableIndexes && _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) - _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); - return true; + long context = 0; + this.Lock(ref recordInfo, ref key, ref value, OperationType.READ, ref context); + try + { + _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst); + } + finally + { + retry = this.Unlock(ref recordInfo, ref key, ref value, OperationType.READ, context); + } } - return false; } -#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) - { - return _clientSession.functions.SupportsLocks - ? ConcurrentWriterLock(ref key, ref src, ref dst, address) - : ConcurrentWriterNoLock(ref key, ref src, ref dst, address); - } + public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) + => !this.SupportsLocks + ? ConcurrentWriterNoLock(ref key, ref src, ref dst, address) + : ConcurrentWriterLock(ref key, ref src, ref dst, ref recordInfo, address); [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, long address) - { - if (_clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) - { - // KeyIndexes do not need notification of in-place updates because the key does not change. - if (_clientSession.fht.SupportsMutableIndexes && _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) - _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); - return true; - } - return false; - } + // KeyIndexes do not need notification of in-place updates because the key does not change. + => _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst) + && _clientSession.fht.UpdateSIForIPU(ref dst, address); - private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, long address) + private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) { - RecordInfo recordInfo = default; // TODO: Get a real recordInfo here - this.Lock(ref recordInfo, ref key, ref dst); + long context = 0; + this.Lock(ref recordInfo, ref key, ref dst, OperationType.UPSERT, ref context); try { - // KeyIndexes do not need notification of in-place updates because the key does not change. - if (!recordInfo.Tombstone && _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) - { - if (_clientSession.fht.SupportsMutableIndexes && _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) - _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); - return true; - } + return !recordInfo.Tombstone && ConcurrentWriterNoLock(ref key, ref src, ref dst, address); } finally { - this.Unlock(ref recordInfo, ref key, ref dst); + this.Unlock(ref recordInfo, ref key, ref dst, OperationType.UPSERT, context); } - return false; } -#if false // outer -- experimental - private bool ConcurrentWriterSI(ref RecordInfo recordInfo, ref Key key, ref Value src, ref Value dst, long address) - { -#if true - if (!recordInfo.Tombstone && _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) - { - _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); - return true; - } - return false; -#else - //ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref dst); - RecordInfo recordInfo = default; - try - { - if (!recordInfo.Tombstone && _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst)) - { - // KeyIndexes do not need notification of in-place updates because the key does not change. - _clientSession.fht.SecondaryIndexBroker.Upsert(ref dst, address); - return true; - } - } - finally - { - //this.Unlock(ref recordInfo, ref key, ref dst); - } - //this.Unlock(ref recordInfo, ref key, ref dst); - - return false; -#endif - } -#endif // outer - experimental + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ConcurrentDeleter(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) + => !this.SupportsLocks + ? ConcurrentDeleterNoLock(ref key, ref value, ref recordInfo, address) + : ConcurrentDeleterLock(ref key, ref value, ref recordInfo, address); - public bool ConcurrentDeleter(ref Key key, ref Value value, long address) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool ConcurrentDeleterNoLock(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) + { // Non-Advanced IFunctions has no ConcurrentDeleter - => !_clientSession.fht.SupportsMutableIndexes || _clientSession.fht.SecondaryIndexBroker.Count == 0 - ? false - : ConcurrentDeleterSI(ref key, ref value, address); + _clientSession.fht.SetRecordDeleted(ref value, ref recordInfo); + return _clientSession.fht.UpdateSIForDelete(ref key, address, isNewRecord: false); + } - private bool ConcurrentDeleterSI(ref Key key, ref Value value, long address) + private bool ConcurrentDeleterLock(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) { - ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref value); + long context = 0; + this.Lock(ref recordInfo, ref key, ref value, OperationType.DELETE, ref context); try { - if (_clientSession.fht.SecondaryIndexBroker.MutableKeyIndexCount > 0) - _clientSession.fht.SecondaryIndexBroker.Delete(ref key); - if (_clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount > 0) - _clientSession.fht.SecondaryIndexBroker.Delete(ref value, address); - _clientSession.fht.SetRecordDeleted(ref recordInfo, ref value); + return ConcurrentDeleterNoLock(ref key, ref value, ref recordInfo, address); } finally { - this.Unlock(ref recordInfo, ref key, ref value); + this.Unlock(ref recordInfo, ref key, ref value, OperationType.DELETE, context); } - return true; } public bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) => _clientSession.functions.NeedCopyUpdate(ref key, ref input, ref oldValue); - public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long address) + public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue) { _clientSession.functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue); } @@ -882,33 +846,35 @@ public int GetLength(ref Value t, ref Input input) return _clientSession.variableLengthStruct.GetLength(ref t, ref input); } - public void InitialUpdater(ref Key key, ref Input input, ref Value value, long address) + public void InitialUpdater(ref Key key, ref Input input, ref Value value) { _clientSession.functions.InitialUpdater(ref key, ref input, ref value); } - public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address) - => !_clientSession.fht.SupportsMutableIndexes || _clientSession.fht.SecondaryIndexBroker.MutableValueIndexCount == 0 - ? _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value) - : InPlaceUpdaterSI(ref key, ref input, ref value, address); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) + => !this.SupportsLocks + ? InPlaceUpdaterNoLock(ref key, ref input, ref value, address) + : InPlaceUpdaterLock(ref key, ref input, ref value, ref recordInfo, address); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool InPlaceUpdaterNoLock(ref Key key, ref Input input, ref Value value, long address) + => _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value) + && _clientSession.fht.UpdateSIForIPU(ref value, address); - private bool InPlaceUpdaterSI(ref Key key, ref Input input, ref Value value, long address) + private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) { - ref RecordInfo recordInfo = ref this.Lock(address, ref key, ref value); + long context = 0; + this.Lock(ref recordInfo, ref key, ref value, OperationType.RMW, ref context); try { - if (!recordInfo.Tombstone && _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value)) - { - // KeyIndexes do not need notification of in-place updates because the key does not change. - _clientSession.fht.SecondaryIndexBroker.Upsert(ref value, address); - return true; - } + // KeyIndexes do not need notification of in-place updates because the key does not change. + return !recordInfo.Tombstone && InPlaceUpdaterNoLock(ref key, ref input, ref value, address); } finally { - this.Unlock(ref recordInfo, ref key, ref value); + this.Unlock(ref recordInfo, ref key, ref value, OperationType.RMW, context); } - return false; } public void ReadCompletionCallback(ref Key key, ref Input input, ref Output output, Context ctx, Status status, RecordInfo recordInfo) @@ -926,7 +892,7 @@ public void SingleReader(ref Key key, ref Input input, ref Value value, ref Outp _clientSession.functions.SingleReader(ref key, ref input, ref value, ref dst); } - public void SingleWriter(ref Key key, ref Value src, ref Value dst, long address) + public void SingleWriter(ref Key key, ref Value src, ref Value dst) { _clientSession.functions.SingleWriter(ref key, ref src, ref dst); } @@ -954,21 +920,11 @@ public IHeapContainer GetHeapContainer(ref Input input) return new VarLenHeapContainer(ref input, _clientSession.inputVariableLengthStruct, _clientSession.fht.hlog.bufferPool); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ref RecordInfo Lock(long address, ref Key key, ref Value value) - { - ref RecordInfo recordInfo = ref _clientSession.fht.RecordAccessor.GetRecordInfo(address); - _clientSession.functions.Lock(ref recordInfo, ref key, ref value); - return ref recordInfo; - } - public bool SupportsLocks => _clientSession.functions.SupportsLocks; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value); + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value, opType, ref context); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value); + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value, opType, context); } } } diff --git a/cs/src/core/ClientSession/FASTERAsync.cs b/cs/src/core/ClientSession/FASTERAsync.cs index e79a704b1..9fe5462c7 100644 --- a/cs/src/core/ClientSession/FASTERAsync.cs +++ b/cs/src/core/ClientSession/FASTERAsync.cs @@ -450,18 +450,26 @@ internal ValueTask> RmwAsync>(new RmwAsyncResult((Status)internalStatus, default)); + status = (Status)internalStatus; } else { - var status = HandleOperationStatus(currentCtx, currentCtx, ref pcontext, fasterSession, internalStatus, true, out diskRequest); + status = HandleOperationStatus(currentCtx, currentCtx, ref pcontext, fasterSession, internalStatus, true, out diskRequest); + } - if (status != Status.PENDING) - return new ValueTask>(new RmwAsyncResult(status, default)); + if (this.SupportsMutableIndexes && (status == Status.OK || status == Status.NOTFOUND) && pcontext.IsNewRecord) + { + long physicalAddress = this.hlog.GetPhysicalAddress(pcontext.logicalAddress); + ref RecordInfo recordInfo = ref this.hlog.GetInfo(physicalAddress); + ref Value value = ref this.hlog.GetValue(physicalAddress); + UpdateSIForInsert>(ref key, ref value, ref recordInfo, pcontext.logicalAddress, fasterSession); } + + if (status != Status.PENDING) + return new ValueTask>(new RmwAsyncResult(status, default)); } finally { diff --git a/cs/src/core/Index/Common/Contexts.cs b/cs/src/core/Index/Common/Contexts.cs index 2dcb2ee10..226a0a36c 100644 --- a/cs/src/core/Index/Common/Contexts.cs +++ b/cs/src/core/Index/Common/Contexts.cs @@ -12,12 +12,20 @@ namespace FASTER.core { - internal enum OperationType + /// + /// The type of FASTER operation being done + /// + public enum OperationType { + /// Read operation READ, + /// Read-Modify-Write operation RMW, + /// Upsert operation UPSERT, + /// Insert operation (either Upsert or RMW resulted in a new record) INSERT, + /// Delete operation DELETE } diff --git a/cs/src/core/Index/Common/RecordInfo.cs b/cs/src/core/Index/Common/RecordInfo.cs index 0156a18f4..e8d3adcce 100644 --- a/cs/src/core/Index/Common/RecordInfo.cs +++ b/cs/src/core/Index/Common/RecordInfo.cs @@ -144,33 +144,10 @@ public void Unpin() throw new InvalidOperationException(); } #endif - /// - /// The RecordInfo locked by this thread, if any. - /// - [ThreadStatic] - internal static RecordInfo* threadLockedRecord; - - /// - /// The number of times the current thread has (re-)entered the lock. - /// - [ThreadStatic] - internal static int threadLockedRecordEntryCount; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SpinLock() { - // Check for a re-entrant lock. - if (threadLockedRecord == (RecordInfo*)Unsafe.AsPointer(ref this)) - { - Debug.Assert(threadLockedRecordEntryCount > 0); - ++threadLockedRecordEntryCount; - return; - } - - // RecordInfo locking is intended for use in concurrent callbacks only (ConcurrentReader, ConcurrentWriter, InPlaceUpdater), - // so only the RecordInfo for that callback should be locked. A different RecordInfo being locked implies a missing Unlock. - Debug.Assert(threadLockedRecord == null); - Debug.Assert(threadLockedRecordEntryCount == 0); while (true) { long expected_word = word; @@ -178,11 +155,7 @@ public void SpinLock() { var found_word = Interlocked.CompareExchange(ref word, expected_word | kLatchBitMask, expected_word); if (found_word == expected_word) - { - threadLockedRecord = (RecordInfo*)Unsafe.AsPointer(ref this); - threadLockedRecordEntryCount = 1; return; - } } Thread.Yield(); } @@ -191,16 +164,7 @@ public void SpinLock() [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Unlock() { - Debug.Assert(threadLockedRecord == (RecordInfo*)Unsafe.AsPointer(ref this)); - if (threadLockedRecord == (RecordInfo*)Unsafe.AsPointer(ref this)) - { - Debug.Assert(threadLockedRecordEntryCount > 0); - if (--threadLockedRecordEntryCount == 0) - { - word &= ~kLatchBitMask; - threadLockedRecord = null; - } - } + word &= ~kLatchBitMask; } public bool IsNull() diff --git a/cs/src/core/Index/FASTER/FASTER.cs b/cs/src/core/Index/FASTER/FASTER.cs index 49da900b7..1624c699f 100644 --- a/cs/src/core/Index/FASTER/FASTER.cs +++ b/cs/src/core/Index/FASTER/FASTER.cs @@ -77,11 +77,6 @@ public partial class FasterKV : FasterBase, /// public LogAccessor ReadCache { get; } - /// - /// An accessor to the record at a given logical address, for use in IFunctions callbacks. - /// - public RecordAccessor RecordAccessor { get; } - internal ConcurrentDictionary _recoveredSessions; /// @@ -131,8 +126,6 @@ public FasterKV(long size, LogSettings logSettings, } } - this.RecordAccessor = new RecordAccessor(this); - if (checkpointSettings == null) checkpointSettings = new CheckpointSettings(); @@ -602,31 +595,60 @@ internal Status ContextUpsert(ref Key key if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) { status = (Status)internalStatus; -#if false // TODO indexing - if (this.SupportsMutableIndexes && pcontext.IsNewRecord) - { - ref RecordInfo recordInfo = ref this.RecordAccessor.SpinLockRecordInfo(pcontext.logicalAddress); - if (!recordInfo.Invalid && !recordInfo.Tombstone) - { - if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) - this.SecondaryIndexBroker.Insert(ref key); - if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Insert(ref value, pcontext.logicalAddress); - } - recordInfo.Unlock(); - } -#endif } else { status = HandleOperationStatus(sessionCtx, sessionCtx, ref pcontext, fasterSession, internalStatus, false, out _); } + if (this.SupportsMutableIndexes && (status == Status.OK || status == Status.NOTFOUND) && pcontext.IsNewRecord) + { + ref RecordInfo recordInfo = ref this.hlog.GetInfo(this.hlog.GetPhysicalAddress(pcontext.logicalAddress)); + UpdateSIForInsert(ref key, ref value, ref recordInfo, pcontext.logicalAddress, fasterSession); + } + Debug.Assert(serialNo >= sessionCtx.serialNum, "Operation serial numbers must be non-decreasing"); sessionCtx.serialNum = serialNo; return status; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateSIForInsert(ref Key key, ref Value value, ref RecordInfo recordInfo, long address, FasterSession fasterSession) + where FasterSession : IFasterSession + { + if (!fasterSession.SupportsLocks) + UpdateSIForInsertNoLock(ref key, ref value, ref recordInfo, address); + else + UpdateSIForInsertLock(ref key, ref value, ref recordInfo, address, fasterSession); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateSIForInsertNoLock(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) + { + if (!recordInfo.Invalid && !recordInfo.Tombstone) + { + if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) + this.SecondaryIndexBroker.Insert(ref key); + if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) + this.SecondaryIndexBroker.Insert(ref value, address); + } + } + + private void UpdateSIForInsertLock(ref Key key, ref Value value, ref RecordInfo recordInfo, long address, FasterSession fasterSession) + where FasterSession : IFasterSession + { + long context = 0; + fasterSession.Lock(ref recordInfo, ref key, ref value, OperationType.INSERT, ref context); + try + { + UpdateSIForInsertNoLock(ref key, ref value, ref recordInfo, address); + } + finally + { + fasterSession.Unlock(ref recordInfo, ref key, ref value, OperationType.INSERT, context); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Status ContextRMW(ref Key key, ref Input input, Context context, FasterSession fasterSession, long serialNo, FasterExecutionContext sessionCtx) @@ -643,26 +665,20 @@ internal Status ContextRMW(ref Key key, r if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) { status = (Status)internalStatus; -#if false // TODO indexing - if (this.SupportsMutableIndexes && pcontext.IsNewRecord) - { - ref RecordInfo recordInfo = ref this.RecordAccessor.SpinLockRecordInfo(pcontext.logicalAddress); - if (!recordInfo.Invalid && !recordInfo.Tombstone) - { - if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) - this.SecondaryIndexBroker.Insert(ref key); - if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Insert(ref this.hlog.GetValue(this.hlog.GetPhysicalAddress(pcontext.logicalAddress)), pcontext.logicalAddress); - } - recordInfo.Unlock(); - } -#endif } else { status = HandleOperationStatus(sessionCtx, sessionCtx, ref pcontext, fasterSession, internalStatus, false, out _); } + if (this.SupportsMutableIndexes && (status == Status.OK || status == Status.NOTFOUND) && pcontext.IsNewRecord) + { + long physicalAddress = this.hlog.GetPhysicalAddress(pcontext.logicalAddress); + ref RecordInfo recordInfo = ref this.hlog.GetInfo(physicalAddress); + ref Value value = ref this.hlog.GetValue(physicalAddress); + UpdateSIForInsert(ref key, ref value, ref recordInfo, pcontext.logicalAddress, fasterSession); + } + Debug.Assert(serialNo >= sessionCtx.serialNum, "Operation serial numbers must be non-decreasing"); sessionCtx.serialNum = serialNo; return status; @@ -694,6 +710,13 @@ internal Status ContextDelete( status = HandleOperationStatus(sessionCtx, sessionCtx, ref pcontext, fasterSession, internalStatus, false, out _); } + if (this.SupportsMutableIndexes && status == Status.OK && pcontext.IsNewRecord) + { + // No need to lock here; we have just written a new record with a tombstone, so it will not be changed + // TODO - but this can race with an INSERT... + this.UpdateSIForDelete(ref key, pcontext.logicalAddress, isNewRecord: true); + } + Debug.Assert(serialNo >= sessionCtx.serialNum, "Operation serial numbers must be non-decreasing"); sessionCtx.serialNum = serialNo; return status; diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index 98b683a21..a81345335 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -162,11 +162,12 @@ internal OperationStatus InternalRead( // Mutable region (even fuzzy region is included here) if (logicalAddress >= hlog.SafeReadOnlyAddress) { - pendingContext.recordInfo = hlog.GetInfo(physicalAddress); + ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); + pendingContext.recordInfo = recordInfo; if (pendingContext.recordInfo.Tombstone) return OperationStatus.NOTFOUND; - fasterSession.ConcurrentReader(ref key, ref input, ref hlog.GetValue(physicalAddress), ref output, logicalAddress); + fasterSession.ConcurrentReader(ref key, ref input, ref hlog.GetValue(physicalAddress), ref output, ref recordInfo, logicalAddress); return OperationStatus.SUCCESS; } @@ -337,28 +338,15 @@ internal OperationStatus InternalUpsert( #endregion // Optimization for most common case - ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); - if (sessionCtx.phase == Phase.REST && logicalAddress >= hlog.ReadOnlyAddress && !recordInfo.Tombstone) + if (sessionCtx.phase == Phase.REST && logicalAddress >= hlog.ReadOnlyAddress) { -#if true // original - if (fasterSession.ConcurrentWriter(ref key, ref value, ref hlog.GetValue(physicalAddress), logicalAddress)) - { - return OperationStatus.SUCCESS; - } -#else - ref Value recordValue = ref hlog.GetValue(physicalAddress); - fasterSession.Lock(ref recordInfo, ref key, ref recordValue); - try + ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); + if (!recordInfo.Tombstone) { - if (fasterSession.ConcurrentWriter(ref key, ref value, ref recordValue, logicalAddress)) + if (fasterSession.ConcurrentWriter(ref key, ref value, ref hlog.GetValue(physicalAddress), ref recordInfo, logicalAddress)) return OperationStatus.SUCCESS; + goto CreateNewRecord; } - finally - { - fasterSession.Unlock(ref recordInfo, ref key, ref recordValue); - } -#endif - goto CreateNewRecord; } #region Entry latch operation @@ -440,7 +428,7 @@ internal OperationStatus InternalUpsert( // Mutable Region: Update the record in-place if (logicalAddress >= hlog.ReadOnlyAddress && !hlog.GetInfo(physicalAddress).Tombstone) { - if (fasterSession.ConcurrentWriter(ref key, ref value, ref hlog.GetValue(physicalAddress), logicalAddress)) + if (fasterSession.ConcurrentWriter(ref key, ref value, ref hlog.GetValue(physicalAddress), ref hlog.GetInfo(physicalAddress), logicalAddress)) { status = OperationStatus.SUCCESS; goto LatchRelease; // Release shared latch (if acquired) @@ -463,7 +451,7 @@ internal OperationStatus InternalUpsert( latestLogicalAddress); hlog.Serialize(ref key, newPhysicalAddress); fasterSession.SingleWriter(ref key, ref value, - ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize), newLogicalAddress); + ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); var updatedEntry = default(HashBucketEntry); updatedEntry.Tag = tag; @@ -526,9 +514,18 @@ internal OperationStatus InternalUpsert( return status; } -#endregion + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool UpdateSIForIPU(ref Value value, long address) + { + // KeyIndexes do not need notification of in-place updates because the key does not change. + if (this.SupportsMutableIndexes && this.SecondaryIndexBroker.MutableValueIndexCount > 0) + this.SecondaryIndexBroker.Upsert(ref value, address); + return true; + } + + #endregion -#region RMW Operation + #region RMW Operation /// /// Read-Modify-Write Operation. Updates value of 'key' using 'input' and current value. @@ -613,12 +610,12 @@ internal OperationStatus InternalRMW( out physicalAddress); } } -#endregion + #endregion // Optimization for the most common case if (sessionCtx.phase == Phase.REST && logicalAddress >= hlog.ReadOnlyAddress && !hlog.GetInfo(physicalAddress).Tombstone) { - if (fasterSession.InPlaceUpdater(ref key, ref input, ref hlog.GetValue(physicalAddress), logicalAddress)) + if (fasterSession.InPlaceUpdater(ref key, ref input, ref hlog.GetValue(physicalAddress), ref hlog.GetInfo(physicalAddress), logicalAddress)) { return OperationStatus.SUCCESS; } @@ -714,7 +711,7 @@ internal OperationStatus InternalRMW( Debug.Assert(hlog.GetInfo(physicalAddress).Version == sessionCtx.version); } - if (fasterSession.InPlaceUpdater(ref key, ref input, ref hlog.GetValue(physicalAddress), logicalAddress)) + if (fasterSession.InPlaceUpdater(ref key, ref input, ref hlog.GetValue(physicalAddress), ref hlog.GetInfo(physicalAddress), logicalAddress)) { status = OperationStatus.SUCCESS; goto LatchRelease; // Release shared latch (if acquired) @@ -793,21 +790,21 @@ internal OperationStatus InternalRMW( if (logicalAddress < hlog.BeginAddress) { - fasterSession.InitialUpdater(ref key, ref input, ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize), newLogicalAddress); + fasterSession.InitialUpdater(ref key, ref input, ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); status = OperationStatus.NOTFOUND; } else if (logicalAddress >= hlog.HeadAddress) { if (hlog.GetInfo(physicalAddress).Tombstone) { - fasterSession.InitialUpdater(ref key, ref input, ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize), newLogicalAddress); + fasterSession.InitialUpdater(ref key, ref input, ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); status = OperationStatus.NOTFOUND; } else { fasterSession.CopyUpdater(ref key, ref input, ref hlog.GetValue(physicalAddress), - ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize), newLogicalAddress); + ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); status = OperationStatus.SUCCESS; } } @@ -1044,22 +1041,25 @@ internal OperationStatus InternalDelete( // Mutable Region: Update the record in-place if (logicalAddress >= hlog.ReadOnlyAddress) { - if (!fasterSession.ConcurrentDeleter(ref hlog.GetKey(physicalAddress), ref hlog.GetValue(physicalAddress), logicalAddress)) - SetRecordDeleted(ref hlog.GetInfo(physicalAddress), ref hlog.GetValue(physicalAddress)); + ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); + + // Ignore return value of ConcurrentDeleter; the InternalFasterSession wrappers handle the false return. + fasterSession.ConcurrentDeleter(ref hlog.GetKey(physicalAddress), ref hlog.GetValue(physicalAddress), ref recordInfo, logicalAddress); // Try to update hash chain and completely elide record only if previous address points to invalid address - if (entry.Address == logicalAddress && hlog.GetInfo(physicalAddress).PreviousAddress < hlog.BeginAddress) + if (entry.Address == logicalAddress && recordInfo.PreviousAddress < hlog.BeginAddress) { var updatedEntry = default(HashBucketEntry); updatedEntry.Tag = 0; - if (hlog.GetInfo(physicalAddress).PreviousAddress == Constants.kTempInvalidAddress) + if (recordInfo.PreviousAddress == Constants.kTempInvalidAddress) updatedEntry.Address = Constants.kInvalidAddress; else - updatedEntry.Address = hlog.GetInfo(physicalAddress).PreviousAddress; + updatedEntry.Address = recordInfo.PreviousAddress; updatedEntry.Pending = entry.Pending; updatedEntry.Tentative = false; - // Ignore return value; this is a performance optimization to keep the hash table clean if we can + // Ignore return value; this is a performance optimization to keep the hash table clean if we can, so if we fail it just means + // the hashtable entry has already been updated by someone else. Interlocked.CompareExchange(ref bucket->bucket_entries[slot], updatedEntry.word, entry.word); } @@ -1097,6 +1097,8 @@ internal OperationStatus InternalDelete( if (foundEntry.word == entry.word) { + pendingContext.logicalAddress = newLogicalAddress; + pendingContext.IsNewRecord = true; status = OperationStatus.SUCCESS; goto LatchRelease; } @@ -1143,12 +1145,23 @@ internal OperationStatus InternalDelete( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetRecordDeleted(ref RecordInfo recordInfo, ref Value value) + internal void SetRecordDeleted(ref Value value, ref RecordInfo recordInfo) { recordInfo.Tombstone = true; if (hlog.ValueHasObjects()) value = default; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool UpdateSIForDelete(ref Key key, long address, bool isNewRecord) + { + if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) + this.SecondaryIndexBroker.Delete(ref key); + if (!isNewRecord && this.SecondaryIndexBroker.MutableValueIndexCount > 0) + this.SecondaryIndexBroker.Delete(address); + return true; + } + #endregion #region ContainsKeyInMemory @@ -1347,8 +1360,7 @@ internal void InternalContinuePendingReadCopyToTail(ref key, ref recordValue, ref recordInfo, newLogicalAddress, fasterSession); + } return status; } else diff --git a/cs/src/core/Index/FASTER/FASTERLegacy.cs b/cs/src/core/Index/FASTER/FASTERLegacy.cs index e785fe337..4cbe08d1f 100644 --- a/cs/src/core/Index/FASTER/FASTERLegacy.cs +++ b/cs/src/core/Index/FASTER/FASTERLegacy.cs @@ -327,17 +327,17 @@ public void CheckpointCompletionCallback(string guid, CommitPoint commitPoint) _fasterKV._functions.CheckpointCompletionCallback(guid, commitPoint); } - public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, long address) + public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, ref RecordInfo recordInfo, long address) { _fasterKV._functions.ConcurrentReader(ref key, ref input, ref value, ref dst); } - public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) + public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) { return _fasterKV._functions.ConcurrentWriter(ref key, ref src, ref dst); } - public bool ConcurrentDeleter(ref Key key, ref Value value, long address) + public bool ConcurrentDeleter(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) { return false; } @@ -345,7 +345,7 @@ public bool ConcurrentDeleter(ref Key key, ref Value value, long address) public bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) => _fasterKV._functions.NeedCopyUpdate(ref key, ref input, ref oldValue); - public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long address) + public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue) { _fasterKV._functions.CopyUpdater(ref key, ref input, ref oldValue, ref newValue); } @@ -365,12 +365,12 @@ public int GetLength(ref Value t, ref Input input) return _fasterKV._variableLengthStructForInput.GetLength(ref t, ref input); } - public void InitialUpdater(ref Key key, ref Input input, ref Value value, long address) + public void InitialUpdater(ref Key key, ref Input input, ref Value value) { _fasterKV._functions.InitialUpdater(ref key, ref input, ref value); } - public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address) + public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) { return _fasterKV._functions.InPlaceUpdater(ref key, ref input, ref value); } @@ -390,7 +390,7 @@ public void SingleReader(ref Key key, ref Input input, ref Value value, ref Outp _fasterKV._functions.SingleReader(ref key, ref input, ref value, ref dst); } - public void SingleWriter(ref Key key, ref Value src, ref Value dst, long address) + public void SingleWriter(ref Key key, ref Value src, ref Value dst) { _fasterKV._functions.SingleWriter(ref key, ref src, ref dst); } @@ -415,9 +415,9 @@ public IHeapContainer GetHeapContainer(ref Input input) public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } - public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; } } diff --git a/cs/src/core/Index/FASTER/LogCompactionFunctions.cs b/cs/src/core/Index/FASTER/LogCompactionFunctions.cs index ef371dd3f..8141c16dc 100644 --- a/cs/src/core/Index/FASTER/LogCompactionFunctions.cs +++ b/cs/src/core/Index/FASTER/LogCompactionFunctions.cs @@ -32,8 +32,8 @@ public void SingleReader(ref Key key, ref Empty input, ref Value value, ref Empt public void UpsertCompletionCallback(ref Key key, ref Value value, Empty ctx) { } public void DeleteCompletionCallback(ref Key key, Empty ctx) { } public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } - public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; } internal sealed class LogCompactFunctions : IFunctions @@ -60,8 +60,8 @@ public void SingleReader(ref Key key, ref Empty input, ref Value value, ref Empt public void UpsertCompletionCallback(ref Key key, ref Value value, Empty ctx) { } public void DeleteCompletionCallback(ref Key key, Empty ctx) { } public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } - public void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; } internal unsafe struct DefaultVariableCompactionFunctions : ICompactionFunctions diff --git a/cs/src/core/Index/FASTER/RecordAccessor.cs b/cs/src/core/Index/FASTER/RecordAccessor.cs deleted file mode 100644 index 8b5c1375f..000000000 --- a/cs/src/core/Index/FASTER/RecordAccessor.cs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace FASTER.core -{ - /// - /// Provides an accessor to the record at a given logical address, for use in IAdvancedFunctions callbacks. - /// - /// - /// - public class RecordAccessor - { - private readonly FasterKV fkv; - - internal RecordAccessor(FasterKV fkv) => this.fkv = fkv; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void VerifyAddress(long logicalAddress) - { - if (!IsValid(logicalAddress)) - throw new FasterException("Invalid logical address"); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void VerifyIsInMemoryAddress(long logicalAddress) - { - VerifyAddress(logicalAddress); - if (!IsInMemory(logicalAddress)) - throw new FasterException("Address is not in the in-memory portion of the log"); - } - - /// - /// Indicates whether the address is within the FasterKV HybridLog - /// - /// The address to verify - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsReadCacheAddress(long logicalAddress) - => this.fkv.UseReadCache && ((logicalAddress & Constants.kReadCacheBitMask) != 0); - - #region public interface - - /// - /// Gets the record header for the address - /// - /// The address to get the record header for - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref RecordInfo GetRecordInfo(long logicalAddress) - { - VerifyIsInMemoryAddress(logicalAddress); - return ref this.fkv.hlog.GetInfo(this.fkv.hlog.GetPhysicalAddress(logicalAddress)); - } - - /// - /// Indicates whether the address is within the FasterKV HybridLog logical address space (does not verify alignment) - /// - /// The address to verify - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsValid(long logicalAddress) => logicalAddress >= this.fkv.Log.BeginAddress && logicalAddress <= this.fkv.Log.TailAddress && !IsReadCacheAddress(logicalAddress); - - /// - /// Indicates whether the address is in memory within the FasterKV HybridLog - /// - /// The address to verify - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsInMemory(long logicalAddress) => IsValid(logicalAddress) && logicalAddress >= this.fkv.Log.HeadAddress; - - /// - /// Returns the previous logical address in the hash collision chain that includes the given logical address - /// - /// The address to examine - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long GetPreviousAddress(long logicalAddress) => GetRecordInfo(logicalAddress).PreviousAddress; - - /// - /// Returns whether the record at the given logical address is deleted - /// - /// The address to examine - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsTombstone(long logicalAddress) => GetRecordInfo(logicalAddress).Tombstone; - - /// - /// Set the record at the given logical address to deleted - /// - /// The address to examine - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetRecordDeleted(long logicalAddress) - { - ref RecordInfo recordInfo = ref GetRecordInfo(logicalAddress); - recordInfo.Tombstone = true; - if (this.fkv.hlog.ValueHasObjects()) - this.fkv.hlog.GetValue(this.fkv.hlog.GetPhysicalAddress(logicalAddress)) = default; - } - - /// - /// Returns the version number of the record at the given logical address - /// - /// The address to examine - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Version(long logicalAddress) => GetRecordInfo(logicalAddress).Version; - - /// - /// Locks the RecordInfo at address - /// - /// The address to examine - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SpinLock(long logicalAddress) - { - Debug.Assert(logicalAddress >= this.fkv.Log.ReadOnlyAddress); - GetRecordInfo(logicalAddress).SpinLock(); - } - - /// - /// Locks the RecordInfo at address, and returns that RecordInfo reference (which can be used for unlocking). - /// - /// The address to examine - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref RecordInfo SpinLockRecordInfo(long logicalAddress) - { - Debug.Assert(logicalAddress >= this.fkv.Log.ReadOnlyAddress); - ref RecordInfo recordInfo = ref GetRecordInfo(logicalAddress); - recordInfo.SpinLock(); - return ref recordInfo; - } - - /// - /// Unlocks the RecordInfo at address - /// - /// The address to examine - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Unlock(long logicalAddress) => GetRecordInfo(logicalAddress).Unlock(); - - #endregion public interface - } -} diff --git a/cs/src/core/Index/Interfaces/FunctionsBase.cs b/cs/src/core/Index/Interfaces/FunctionsBase.cs index fa86b302f..8f5741592 100644 --- a/cs/src/core/Index/Interfaces/FunctionsBase.cs +++ b/cs/src/core/Index/Interfaces/FunctionsBase.cs @@ -17,6 +17,10 @@ namespace FASTER.core /// public abstract class FunctionsBase : IFunctions { + protected readonly bool locking; + + protected FunctionsBase(bool locking = false) => this.locking = locking; + public virtual void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst) { } public virtual void SingleReader(ref Key key, ref Input input, ref Value value, ref Output dst) { } @@ -26,7 +30,7 @@ public virtual void SingleReader(ref Key key, ref Input input, ref Value value, public virtual void InitialUpdater(ref Key key, ref Input input, ref Value value) { } public virtual bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) => true; public virtual void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue) { } - public virtual bool InPlaceUpdater(ref Key key, ref Input input, ref Value value) { return true; } + public virtual bool InPlaceUpdater(ref Key key, ref Input input, ref Value value) => true; public virtual void ReadCompletionCallback(ref Key key, ref Input input, ref Output output, Context ctx, Status status) { } public virtual void RMWCompletionCallback(ref Key key, ref Input input, Context ctx, Status status) { } @@ -34,11 +38,9 @@ public virtual void UpsertCompletionCallback(ref Key key, ref Value value, Conte public virtual void DeleteCompletionCallback(ref Key key, Context ctx) { } public virtual void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) { } -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } - public virtual void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } -#endif + public virtual bool SupportsLocks => locking; + public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } + public virtual bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; } /// @@ -49,6 +51,8 @@ public virtual void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value val /// public class SimpleFunctions : FunctionsBase { + public SimpleFunctions(bool locking = false) : base(locking) { } + private readonly Func merger; public SimpleFunctions() => merger = (l, r) => l; public SimpleFunctions(Func merger) => this.merger = merger; @@ -86,18 +90,22 @@ public SimpleFunctions(Func merger) : base(merger) { } /// public abstract class AdvancedFunctionsBase : IAdvancedFunctions { - public virtual void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, long address) { } + protected readonly bool locking; + + protected AdvancedFunctionsBase(bool locking = false) => this.locking = locking; + + public virtual void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, ref RecordInfo recordInfo, long address) { } public virtual void SingleReader(ref Key key, ref Input input, ref Value value, ref Output dst, long address) { } - public virtual bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) { dst = src; return true; } - public virtual void SingleWriter(ref Key key, ref Value src, ref Value dst, long address) => dst = src; + public virtual bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) { dst = src; return true; } + public virtual void SingleWriter(ref Key key, ref Value src, ref Value dst) => dst = src; - public virtual void InitialUpdater(ref Key key, ref Input input, ref Value value, long address) { } + public virtual void InitialUpdater(ref Key key, ref Input input, ref Value value) { } public virtual bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) => true; - public virtual void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long address) { } - public virtual bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address) { return true; } + public virtual void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue) { } + public virtual bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) => true; - public virtual bool ConcurrentDeleter(ref Key key, ref Value value, long address) { return false; } + public virtual bool ConcurrentDeleter(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) { return false; } public virtual void ReadCompletionCallback(ref Key key, ref Input input, ref Output output, Context ctx, Status status, RecordInfo recordInfo) { } public virtual void RMWCompletionCallback(ref Key key, ref Input input, Context ctx, Status status) { } @@ -105,9 +113,9 @@ public virtual void UpsertCompletionCallback(ref Key key, ref Value value, Conte public virtual void DeleteCompletionCallback(ref Key key, Context ctx) { } public virtual void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) { } - public bool SupportsLocks => false; - public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } - public virtual void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) { } + public virtual bool SupportsLocks => locking; + public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } + public virtual bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; } /// @@ -118,19 +126,21 @@ public virtual void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value val /// public class AdvancedSimpleFunctions : AdvancedFunctionsBase { + public AdvancedSimpleFunctions(bool locking = false) : base(locking) { } + private readonly Func merger; public AdvancedSimpleFunctions() => merger = (l, r) => l; public AdvancedSimpleFunctions(Func merger) => this.merger = merger; - public override void ConcurrentReader(ref Key key, ref Value input, ref Value value, ref Value dst, long address) => dst = value; + public override void ConcurrentReader(ref Key key, ref Value input, ref Value value, ref Value dst, ref RecordInfo recordInfo, long address) => dst = value; public override void SingleReader(ref Key key, ref Value input, ref Value value, ref Value dst, long address) => dst = value; - public override bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) { dst = src; return true; } - public override void SingleWriter(ref Key key, ref Value src, ref Value dst, long address) => dst = src; + public override bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) { dst = src; return true; } + public override void SingleWriter(ref Key key, ref Value src, ref Value dst) => dst = src; - public override void InitialUpdater(ref Key key, ref Value input, ref Value value, long address) => value = input; - public override void CopyUpdater(ref Key key, ref Value input, ref Value oldValue, ref Value newValue, long address) => newValue = merger(input, oldValue); - public override bool InPlaceUpdater(ref Key key, ref Value input, ref Value value, long address) { value = merger(input, value); return true; } + public override void InitialUpdater(ref Key key, ref Value input, ref Value value) => value = input; + public override void CopyUpdater(ref Key key, ref Value input, ref Value oldValue, ref Value newValue) => newValue = merger(input, oldValue); + public override bool InPlaceUpdater(ref Key key, ref Value input, ref Value value, ref RecordInfo recordInfo, long address) { value = merger(input, value); return true; } public override void ReadCompletionCallback(ref Key key, ref Value input, ref Value output, Context ctx, Status status, RecordInfo recordInfo) { } public override void RMWCompletionCallback(ref Key key, ref Value input, Context ctx, Status status) { } diff --git a/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs b/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs index 713a60bc2..989cacfd1 100644 --- a/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs +++ b/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs @@ -61,8 +61,7 @@ public interface IAdvancedFunctions /// The key for this record /// The user input to be used for computing the updated /// The destination to be updated; because this is an insert, there is no previous value there. - /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to - void InitialUpdater(ref Key key, ref Input input, ref Value value, long address); + void InitialUpdater(ref Key key, ref Input input, ref Value value); /// /// Whether we need to invoke copy-update for RMW @@ -83,8 +82,7 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The user input to be used for computing from /// The previous value to be copied/updated /// The destination to be updated; because this is an copy to a new location, there is no previous value there. - /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to - void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue, long address); + void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Value newValue); /// /// In-place update for RMW @@ -92,8 +90,9 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The key for this record /// The user input to be used for computing the updated /// The destination to be updated; because this is an in-place update, there is a previous value there. - /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to - bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, long address); + /// A reference to the header of the record; may be used by + /// The logical address of the record being updated; used as a RecordId by indexing + bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address); /// /// Non-concurrent reader. @@ -102,9 +101,7 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The user input for computing from /// The value for the record being read /// The location where is to be copied - /// The logical address of the record being read from; can be used as a RecordId by indexing and for liveness checking or passed to . - /// Note that this address may be in the immutable region, or may not be in memory because this method is called for a read that has gone pending. - /// Use to test before dereferencing. + /// The logical address of the record being read from, or zero if this was a readcache write; used as a RecordId by indexing if nonzero void SingleReader(ref Key key, ref Input input, ref Value value, ref Output dst, long address); /// @@ -114,8 +111,9 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The user input for computing from /// The value for the record being read /// The location where is to be copied - /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to - void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, long address); + /// A reference to the header of the record; may be used by + /// The logical address of the record being copied to; used as a RecordId by indexing + void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, ref RecordInfo recordInfo, long address); /// /// Non-concurrent writer; called on an Upsert that does not find the key so does an insert or finds the key's record in the immutable region so does a read/copy/update (RCU), @@ -124,8 +122,7 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The key for this record /// The previous value to be copied/updated /// The destination to be updated; because this is an copy to a new location, there is no previous value there. - /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to - void SingleWriter(ref Key key, ref Value src, ref Value dst, long address); + void SingleWriter(ref Key key, ref Value src, ref Value dst); /// /// Concurrent writer; called on an Upsert that finds the record in the mutable range. @@ -133,27 +130,25 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The key for the record to be written /// The value to be copied to /// The location where is to be copied; because this method is called only for in-place updates, there is a previous value there. - /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to - bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address); + /// A reference to the header of the record; may be used by + /// The logical address of the record being copied to; used as a RecordId by indexing"/> + bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address); /// /// Concurrent deleter; called on an Delete that finds the record in the mutable range. /// /// The key for the record to be deleted /// The value for the record being deleted; because this method is called only for in-place updates, there is a previous value there. Usually this is ignored or assigned 'default'. - /// The logical address of the record being copied to; can be used as a RecordId by indexing or passed to + /// A reference to the header of the record; may be used by + /// The logical address of the record being copied to; used as a RecordId by indexing /// True if handled by the Functions implementation, else false - public bool ConcurrentDeleter(ref Key key, ref Value value, long address); + public bool ConcurrentDeleter(ref Key key, ref Value value, ref RecordInfo recordInfo, long address); /// - /// Whether this Functions implementation actually locks in and + /// Whether this Functions implementation actually locks in + /// and /// - bool SupportsLocks -#if NETSTANDARD2_1 - => false; -#else - { get; } -#endif + bool SupportsLocks { get; } /// /// User-provided lock call, defaulting to no-op. A default exclusive implementation is available via . @@ -162,15 +157,12 @@ bool SupportsLocks /// The header for the current record /// The key for the current record /// The value for the current record + /// The type of FASTER operation being done (can be used to decide whether to obtain a read vs. exclusinve lock). + /// Context-specific information; will be passed to /// /// This is called only for records guaranteed to be in the mutable range. /// - void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) -#if NETSTANDARD2_1 - {} -#else - ; -#endif + void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context); /// /// User-provided unlock call, defaulting to no-op. A default exclusive implementation is available via . @@ -179,14 +171,15 @@ void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) /// The header for the current record /// The key for the current record /// The value for the current record + /// The type of FASTER operation being done, as passed to + /// The context returned from /// /// This is called only for records guaranteed to be in the mutable range. /// - void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) -#if NETSTANDARD2_1 - {} -#else - ; -#endif + /// + /// True if no inconsistencies detected. Otherwise, the lock and user's callback are reissued. + /// Currently this is handled only for . + /// + bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context); } } \ No newline at end of file diff --git a/cs/src/core/Index/Interfaces/IFunctions.cs b/cs/src/core/Index/Interfaces/IFunctions.cs index a483df970..2f7e79086 100644 --- a/cs/src/core/Index/Interfaces/IFunctions.cs +++ b/cs/src/core/Index/Interfaces/IFunctions.cs @@ -127,31 +127,24 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst); /// - /// Whether this Functions implementation actually locks in and + /// Whether this Functions implementation actually locks in + /// and /// - bool SupportsLocks -#if NETSTANDARD2_1 - => false; -#else - { get; } -#endif + bool SupportsLocks { get; } - /// - /// User-provided lock call, defaulting to no-op. A default exclusive implementation is available via . - /// See also to use two bits of an existing int value. - /// - /// The header for the current record - /// The key for the current record - /// The value for the current record - /// - /// This is called only for records guaranteed to be in the mutable range. - /// - void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) -#if NETSTANDARD2_1 - {} -#else - ; -#endif + /// + /// User-provided lock call, defaulting to no-op. A default exclusive implementation is available via . + /// See also to use two bits of an existing int value. + /// + /// The header for the current record + /// The key for the current record + /// The value for the current record + /// The type of FASTER operation being done (can be used to decide whether to obtain a read vs. exclusinve lock). + /// Context-specific information; will be passed to + /// + /// This is called only for records guaranteed to be in the mutable range. + /// + void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context); /// /// User-provided unlock call, defaulting to no-op. A default exclusive implementation is available via . @@ -160,15 +153,16 @@ void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value) /// The header for the current record /// The key for the current record /// The value for the current record + /// The type of FASTER operation being done, as passed to + /// The context returned from /// /// This is called only for records guaranteed to be in the mutable range. /// - void Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value) -#if NETSTANDARD2_1 - {} -#else - ; -#endif + /// + /// True if no inconsistencies detected. Otherwise, the lock and user's callback are reissued. + /// Currently this is handled only for . + /// + bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context); } /// diff --git a/cs/src/core/SecondaryIndex/ISecondaryIndex.cs b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs index 099f9d8b8..a157fab98 100644 --- a/cs/src/core/SecondaryIndex/ISecondaryIndex.cs +++ b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs @@ -87,10 +87,9 @@ public interface ISecondaryValueIndex : ISecondaryIndex void Upsert(ref TKVValue value, long recordId, bool isMutable); /// - /// Removes a recordId from the secondary index's key(s) derived from the . Called only for mutable indexes. + /// Removes a recordId from the secondary index. Called only for mutable indexes. /// - /// The value from whose derived key(s) the is to be removed /// The recordId to be removed - void Delete(ref TKVValue value, long recordId); + void Delete(long recordId); } } \ No newline at end of file diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs index 258fbd220..7fd3e6049 100644 --- a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs +++ b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs @@ -126,10 +126,10 @@ public void Upsert(ref TKVValue value, long recordId) /// Deletes a recordId keyed by a mutable value from all mutable secondary value indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Delete(ref TKVValue value, long recordId) + public void Delete(long recordId) { foreach (var valueIndex in mutableValueIndexes) - valueIndex.Delete(ref value, recordId); + valueIndex.Delete(recordId); } #endregion Mutable ValueIndexes diff --git a/cs/src/core/VarLen/MemoryFunctions.cs b/cs/src/core/VarLen/MemoryFunctions.cs index efcf17d29..449611aa4 100644 --- a/cs/src/core/VarLen/MemoryFunctions.cs +++ b/cs/src/core/VarLen/MemoryFunctions.cs @@ -11,17 +11,15 @@ public class MemoryFunctions : FunctionsBase, Me where T : unmanaged { readonly MemoryPool memoryPool; - readonly bool locking; /// /// Constructor /// /// /// Whether we lock values before concurrent operations (implemented using a spin lock on length header bit) - public MemoryFunctions(MemoryPool memoryPool = default, bool locking = false) + public MemoryFunctions(MemoryPool memoryPool = default, bool locking = false) : base(locking) { this.memoryPool = memoryPool ?? MemoryPool.Shared; - this.locking = locking; } /// diff --git a/cs/src/core/VarLen/SpanByteFunctions.cs b/cs/src/core/VarLen/SpanByteFunctions.cs index a522a6fbe..82cc595bb 100644 --- a/cs/src/core/VarLen/SpanByteFunctions.cs +++ b/cs/src/core/VarLen/SpanByteFunctions.cs @@ -10,19 +10,11 @@ namespace FASTER.core /// public class SpanByteFunctions : FunctionsBase { - /// - /// Whether we lock values before concurrent operations - /// - protected readonly bool locking; - /// /// Constructor /// /// - public SpanByteFunctions(bool locking = false) - { - this.locking = locking; - } + public SpanByteFunctions(bool locking = false) : base(locking) { } /// public override void SingleWriter(ref Key key, ref SpanByte src, ref SpanByte dst) diff --git a/cs/test/AsyncTests.cs b/cs/test/AsyncTests.cs index b34b149bf..50123e548 100644 --- a/cs/test/AsyncTests.cs +++ b/cs/test/AsyncTests.cs @@ -20,7 +20,7 @@ public class RecoveryTests { private FasterKV fht1; private FasterKV fht2; - private readonly SimpleFunctions functions = new SimpleFunctions(); + private readonly AdSimpleFunctions functions = new AdSimpleFunctions(); private IDevice log; @@ -55,9 +55,9 @@ public async Task AsyncRecoveryTest1(CheckpointType checkpointType) AdInput inputArg = default; Output output = default; - var s0 = fht1.For(functions).NewSession(); - var s1 = fht1.For(functions).NewSession(); - var s2 = fht1.For(functions).NewSession(); + var s0 = fht1.For(functions).NewSession(); + var s1 = fht1.For(functions).NewSession(); + var s2 = fht1.For(functions).NewSession(); for (int key = 0; key < numOps; key++) { @@ -88,7 +88,7 @@ public async Task AsyncRecoveryTest1(CheckpointType checkpointType) fht2.Recover(token); // sync, does not require session var guid = s1.ID; - using (var s3 = fht2.For(functions).ResumeSession(guid, out CommitPoint lsn)) + using (var s3 = fht2.For(functions).ResumeSession(guid, out CommitPoint lsn)) { Assert.IsTrue(lsn.UntilSerialNo == numOps - 1); @@ -111,77 +111,34 @@ public async Task AsyncRecoveryTest1(CheckpointType checkpointType) } } - public class SimpleFunctions : IFunctions + public class AdSimpleFunctions : FunctionsBase { - public void RMWCompletionCallback(ref AdId key, ref AdInput input, Empty ctx, Status status) - { - } - - public void ReadCompletionCallback(ref AdId key, ref AdInput input, ref Output output, Empty ctx, Status status) + public override void ReadCompletionCallback(ref AdId key, ref AdInput input, ref Output output, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); Assert.IsTrue(output.value.numClicks == key.adId); } - public void UpsertCompletionCallback(ref AdId key, ref NumClicks input, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref AdId key, Empty ctx) - { - } - - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - // Read functions - public void SingleReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) - { - dst.value = value; - } + public override void SingleReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) => dst.value = value; - public void ConcurrentReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) - { - dst.value = value; - } - - // Upsert functions - public void SingleWriter(ref AdId key, ref NumClicks src, ref NumClicks dst) - { - dst = src; - } - - public bool ConcurrentWriter(ref AdId key, ref NumClicks src, ref NumClicks dst) - { - dst = src; - return true; - } + public override void ConcurrentReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) => dst.value = value; // RMW functions - public void InitialUpdater(ref AdId key, ref AdInput input, ref NumClicks value) - { - value = input.numClicks; - } + public override void InitialUpdater(ref AdId key, ref AdInput input, ref NumClicks value) => value = input.numClicks; - public bool InPlaceUpdater(ref AdId key, ref AdInput input, ref NumClicks value) + public override bool InPlaceUpdater(ref AdId key, ref AdInput input, ref NumClicks value) { Interlocked.Add(ref value.numClicks, input.numClicks.numClicks); return true; } - public bool NeedCopyUpdate(ref AdId key, ref AdInput input, ref NumClicks oldValue) => true; + public override bool NeedCopyUpdate(ref AdId key, ref AdInput input, ref NumClicks oldValue) => true; - public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, ref NumClicks newValue) + public override void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, ref NumClicks newValue) { newValue.numClicks += oldValue.numClicks + input.numClicks.numClicks; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } - public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } -#endif } } \ No newline at end of file diff --git a/cs/test/LockTests.cs b/cs/test/LockTests.cs index 58e8ee748..6c4be63c4 100644 --- a/cs/test/LockTests.cs +++ b/cs/test/LockTests.cs @@ -16,28 +16,28 @@ internal class LockTests { internal class Functions : AdvancedSimpleFunctions { - private readonly RecordAccessor recordAccessor; - - internal Functions(RecordAccessor accessor) => this.recordAccessor = accessor; - - public override void ConcurrentReader(ref int key, ref int input, ref int value, ref int dst, long address) + public override void ConcurrentReader(ref int key, ref int input, ref int value, ref int dst, ref RecordInfo recordInfo, long address) { - this.recordAccessor.SpinLock(address); dst = value; - this.recordAccessor.Unlock(address); } - bool LockAndIncrement(ref int dst, long address) + bool Increment(ref int dst) { - this.recordAccessor.SpinLock(address); ++dst; - this.recordAccessor.Unlock(address); return true; } - public override bool ConcurrentWriter(ref int key, ref int src, ref int dst, long address) => LockAndIncrement(ref dst, address); + public override bool ConcurrentWriter(ref int key, ref int src, ref int dst, ref RecordInfo recordInfo, long address) => Increment(ref dst); + + public override bool InPlaceUpdater(ref int key, ref int input, ref int value, ref RecordInfo recordInfo, long address) => Increment(ref value); - public override bool InPlaceUpdater(ref int key, ref int input, ref int value, long address) => LockAndIncrement(ref value, address); + public override bool SupportsLocks => true; + public override void Lock(ref RecordInfo recordInfo, ref int key, ref int value, OperationType opType, ref long context) => recordInfo.SpinLock(); + public override bool Unlock(ref RecordInfo recordInfo, ref int key, ref int value, OperationType opType, long context) + { + recordInfo.Unlock(); + return true; + } } private FasterKV fkv; @@ -49,7 +49,7 @@ public void Setup() { log = Devices.CreateLogDevice(TestContext.CurrentContext.TestDirectory + "/GenericStringTests.log", deleteOnClose: true); fkv = new FasterKV( 1L << 20, new LogSettings { LogDevice = log, ObjectLogDevice = null } ); - session = fkv.For(new Functions(fkv.RecordAccessor)).NewSession(); + session = fkv.For(new Functions()).NewSession(); } [TearDown] @@ -66,25 +66,13 @@ public void TearDown() [Test] public unsafe void RecordInfoLockTest() { - // Re-entrancy check - static void checkLatch(RecordInfo* ptr, long count) + for (var ii = 0; ii < 10; ++ii) { - Assert.IsTrue(RecordInfo.threadLockedRecord == ptr); - Assert.IsTrue(RecordInfo.threadLockedRecordEntryCount == count); + RecordInfo recordInfo = new RecordInfo(); + RecordInfo* ri = &recordInfo; + + XLockTest(() => ri->SpinLock(), () => ri->Unlock()); } - RecordInfo recordInfo = new RecordInfo(); - RecordInfo* ri = (RecordInfo*)Unsafe.AsPointer(ref recordInfo); - checkLatch(null, 0); - recordInfo.SpinLock(); - checkLatch(ri, 1); - recordInfo.SpinLock(); - checkLatch(ri, 2); - recordInfo.Unlock(); - checkLatch(ri, 1); - recordInfo.Unlock(); - checkLatch(null, 0); - - XLockTest(() => recordInfo.SpinLock(), () => recordInfo.Unlock()); } private void XLockTest(Action locker, Action unlocker) @@ -94,7 +82,14 @@ private void XLockTest(Action locker, Action unlocker) const int numIters = 5000; var tasks = Enumerable.Range(0, numThreads).Select(ii => Task.Factory.StartNew(XLockTestFunc)).ToArray(); - Task.WaitAll(tasks); + try + { + Task.WaitAll(tasks); + } + catch (AggregateException ex) + { + Assert.Fail(ex.InnerExceptions.First().Message); + } Assert.AreEqual(numThreads * numIters, lockTestValue); diff --git a/cs/test/ObjectRecoveryTest2.cs b/cs/test/ObjectRecoveryTest2.cs index 3350bdd9e..5b672ddae 100644 --- a/cs/test/ObjectRecoveryTest2.cs +++ b/cs/test/ObjectRecoveryTest2.cs @@ -224,12 +224,12 @@ internal void FinalizeRead(ref Status status, ref MyOutput g1) } - public class MyFunctions : IFunctions + public class MyFunctions : FunctionsBase { - public void InitialUpdater(ref MyKey key, ref MyInput input, ref MyValue value) => value.value = input.value; - public bool NeedCopyUpdate(ref MyKey key, ref MyInput input, ref MyValue oldValue) => true; - public void CopyUpdater(ref MyKey key, ref MyInput input, ref MyValue oldValue, ref MyValue newValue) => newValue = oldValue; - public bool InPlaceUpdater(ref MyKey key, ref MyInput input, ref MyValue value) + public override void InitialUpdater(ref MyKey key, ref MyInput input, ref MyValue value) => value.value = input.value; + public override bool NeedCopyUpdate(ref MyKey key, ref MyInput input, ref MyValue oldValue) => true; + public override void CopyUpdater(ref MyKey key, ref MyInput input, ref MyValue oldValue, ref MyValue newValue) => newValue = oldValue; + public override bool InPlaceUpdater(ref MyKey key, ref MyInput input, ref MyValue value) { if (value.value.Length < input.value.Length) return false; @@ -238,10 +238,10 @@ public bool InPlaceUpdater(ref MyKey key, ref MyInput input, ref MyValue value) } - public void SingleReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) => dst.value = value; - public void SingleWriter(ref MyKey key, ref MyValue src, ref MyValue dst) => dst = src; - public void ConcurrentReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) => dst.value = value; - public bool ConcurrentWriter(ref MyKey key, ref MyValue src, ref MyValue dst) + public override void SingleReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) => dst.value = value; + public override void SingleWriter(ref MyKey key, ref MyValue src, ref MyValue dst) => dst = src; + public override void ConcurrentReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) => dst.value = value; + public override bool ConcurrentWriter(ref MyKey key, ref MyValue src, ref MyValue dst) { if (src == null) return false; @@ -253,16 +253,6 @@ public bool ConcurrentWriter(ref MyKey key, ref MyValue src, ref MyValue dst) return true; } - public void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyOutput output, MyContext ctx, Status status) => ctx.Populate(ref status, ref output); - public void UpsertCompletionCallback(ref MyKey key, ref MyValue value, MyContext ctx) { } - public void RMWCompletionCallback(ref MyKey key, ref MyInput input, MyContext ctx, Status status) { } - public void DeleteCompletionCallback(ref MyKey key, MyContext ctx) { } - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) { } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } -#endif + public override void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyOutput output, MyContext ctx, Status status) => ctx.Populate(ref status, ref output); } } diff --git a/cs/test/ObjectRecoveryTestTypes.cs b/cs/test/ObjectRecoveryTestTypes.cs index a7ee0f58e..78db146bb 100644 --- a/cs/test/ObjectRecoveryTestTypes.cs +++ b/cs/test/ObjectRecoveryTestTypes.cs @@ -76,74 +76,27 @@ public class Output public NumClicks value; } - public class Functions : IFunctions + public class Functions : FunctionsBase { - public void RMWCompletionCallback(ref AdId key, ref Input input, Empty ctx, Status status) - { - } - - public void ReadCompletionCallback(ref AdId key, ref Input input, ref Output output, Empty ctx, Status status) - { - } - - public void UpsertCompletionCallback(ref AdId key, ref NumClicks input, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref AdId key, Empty ctx) - { - } - - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - // Read functions - public void SingleReader(ref AdId key, ref Input input, ref NumClicks value, ref Output dst) - { - dst.value = value; - } - - public void ConcurrentReader(ref AdId key, ref Input input, ref NumClicks value, ref Output dst) - { - dst.value = value; - } - - // Upsert functions - public void SingleWriter(ref AdId key, ref NumClicks src, ref NumClicks dst) - { - dst = src; - } + public override void SingleReader(ref AdId key, ref Input input, ref NumClicks value, ref Output dst) => dst.value = value; - public bool ConcurrentWriter(ref AdId key, ref NumClicks src, ref NumClicks dst) - { - dst = src; - return true; - } + public override void ConcurrentReader(ref AdId key, ref Input input, ref NumClicks value, ref Output dst) => dst.value = value; // RMW functions - public void InitialUpdater(ref AdId key, ref Input input, ref NumClicks value) - { - value = input.numClicks; - } + public override void InitialUpdater(ref AdId key, ref Input input, ref NumClicks value) => value = input.numClicks; - public bool InPlaceUpdater(ref AdId key, ref Input input, ref NumClicks value) + public override bool InPlaceUpdater(ref AdId key, ref Input input, ref NumClicks value) { Interlocked.Add(ref value.numClicks, input.numClicks.numClicks); return true; } - public bool NeedCopyUpdate(ref AdId key, ref Input input, ref NumClicks oldValue) => true; + public override bool NeedCopyUpdate(ref AdId key, ref Input input, ref NumClicks oldValue) => true; - public void CopyUpdater(ref AdId key, ref Input input, ref NumClicks oldValue, ref NumClicks newValue) + public override void CopyUpdater(ref AdId key, ref Input input, ref NumClicks oldValue, ref NumClicks newValue) { newValue = new NumClicks { numClicks = oldValue.numClicks + input.numClicks.numClicks }; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } - public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } -#endif } } diff --git a/cs/test/ObjectTestTypes.cs b/cs/test/ObjectTestTypes.cs index 238aba929..cd62b0215 100644 --- a/cs/test/ObjectTestTypes.cs +++ b/cs/test/ObjectTestTypes.cs @@ -75,27 +75,27 @@ public class MyOutput public MyValue value; } - public class MyFunctions : IFunctions + public class MyFunctions : FunctionsBase { - public void InitialUpdater(ref MyKey key, ref MyInput input, ref MyValue value) + public override void InitialUpdater(ref MyKey key, ref MyInput input, ref MyValue value) { value = new MyValue { value = input.value }; } - public bool InPlaceUpdater(ref MyKey key, ref MyInput input, ref MyValue value) + public override bool InPlaceUpdater(ref MyKey key, ref MyInput input, ref MyValue value) { value.value += input.value; return true; } - public bool NeedCopyUpdate(ref MyKey key, ref MyInput input, ref MyValue oldValue) => true; + public override bool NeedCopyUpdate(ref MyKey key, ref MyInput input, ref MyValue oldValue) => true; - public void CopyUpdater(ref MyKey key, ref MyInput input, ref MyValue oldValue, ref MyValue newValue) + public override void CopyUpdater(ref MyKey key, ref MyInput input, ref MyValue oldValue, ref MyValue newValue) { newValue = new MyValue { value = oldValue.value + input.value }; } - public void ConcurrentReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) + public override void ConcurrentReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) { if (dst == default) dst = new MyOutput(); @@ -103,93 +103,70 @@ public void ConcurrentReader(ref MyKey key, ref MyInput input, ref MyValue value dst.value = value; } - public bool ConcurrentWriter(ref MyKey key, ref MyValue src, ref MyValue dst) + public override bool ConcurrentWriter(ref MyKey key, ref MyValue src, ref MyValue dst) { dst.value = src.value; return true; } - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - - public void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyOutput output, Empty ctx, Status status) + public override void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyOutput output, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); Assert.IsTrue(key.key == output.value.value); } - public void RMWCompletionCallback(ref MyKey key, ref MyInput input, Empty ctx, Status status) + public override void RMWCompletionCallback(ref MyKey key, ref MyInput input, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); } - public void UpsertCompletionCallback(ref MyKey key, ref MyValue value, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref MyKey key, Empty ctx) - { - } - - public void SingleReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) + public override void SingleReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) { if (dst == default) dst = new MyOutput(); dst.value = value; } - public void SingleWriter(ref MyKey key, ref MyValue src, ref MyValue dst) + public override void SingleWriter(ref MyKey key, ref MyValue src, ref MyValue dst) { dst = src; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } -#endif } - public class MyFunctionsDelete : IFunctions + public class MyFunctionsDelete : FunctionsBase { - public void InitialUpdater(ref MyKey key, ref MyInput input, ref MyValue value) + public override void InitialUpdater(ref MyKey key, ref MyInput input, ref MyValue value) { value = new MyValue { value = input.value }; } - public bool InPlaceUpdater(ref MyKey key, ref MyInput input, ref MyValue value) + public override bool InPlaceUpdater(ref MyKey key, ref MyInput input, ref MyValue value) { value.value += input.value; return true; } - public bool NeedCopyUpdate(ref MyKey key, ref MyInput input, ref MyValue oldValue) => true; + public override bool NeedCopyUpdate(ref MyKey key, ref MyInput input, ref MyValue oldValue) => true; - public void CopyUpdater(ref MyKey key, ref MyInput input, ref MyValue oldValue, ref MyValue newValue) + public override void CopyUpdater(ref MyKey key, ref MyInput input, ref MyValue oldValue, ref MyValue newValue) { newValue = new MyValue { value = oldValue.value + input.value }; } - public void ConcurrentReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) + public override void ConcurrentReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) { if (dst == null) dst = new MyOutput(); - dst.value = value; } - public bool ConcurrentWriter(ref MyKey key, ref MyValue src, ref MyValue dst) + public override bool ConcurrentWriter(ref MyKey key, ref MyValue src, ref MyValue dst) { dst = src; return true; } - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - - public void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyOutput output, int ctx, Status status) + public override void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyOutput output, int ctx, Status status) { if (ctx == 0) { @@ -202,7 +179,7 @@ public void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyOutpu } } - public void RMWCompletionCallback(ref MyKey key, ref MyInput input, int ctx, Status status) + public override void RMWCompletionCallback(ref MyKey key, ref MyInput input, int ctx, Status status) { if (ctx == 0) Assert.IsTrue(status == Status.OK); @@ -210,100 +187,59 @@ public void RMWCompletionCallback(ref MyKey key, ref MyInput input, int ctx, Sta Assert.IsTrue(status == Status.NOTFOUND); } - public void UpsertCompletionCallback(ref MyKey key, ref MyValue value, int ctx) - { - } - - public void DeleteCompletionCallback(ref MyKey key, int ctx) - { - } - - public void SingleReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) + public override void SingleReader(ref MyKey key, ref MyInput input, ref MyValue value, ref MyOutput dst) { if (dst == null) dst = new MyOutput(); - dst.value = value; } - public void SingleWriter(ref MyKey key, ref MyValue src, ref MyValue dst) + public override void SingleWriter(ref MyKey key, ref MyValue src, ref MyValue dst) { dst = src; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyValue value) { } -#endif } - public class MixedFunctions : IFunctions + public class MixedFunctions : FunctionsBase { - public void InitialUpdater(ref int key, ref MyInput input, ref MyValue value) + public override void InitialUpdater(ref int key, ref MyInput input, ref MyValue value) { value = new MyValue { value = input.value }; } - public bool InPlaceUpdater(ref int key, ref MyInput input, ref MyValue value) + public override bool InPlaceUpdater(ref int key, ref MyInput input, ref MyValue value) { value.value += input.value; return true; } - public bool NeedCopyUpdate(ref int key, ref MyInput input, ref MyValue oldValue) => true; + public override bool NeedCopyUpdate(ref int key, ref MyInput input, ref MyValue oldValue) => true; - public void CopyUpdater(ref int key, ref MyInput input, ref MyValue oldValue, ref MyValue newValue) + public override void CopyUpdater(ref int key, ref MyInput input, ref MyValue oldValue, ref MyValue newValue) { newValue = new MyValue { value = oldValue.value + input.value }; } - public void ConcurrentReader(ref int key, ref MyInput input, ref MyValue value, ref MyOutput dst) + public override void ConcurrentReader(ref int key, ref MyInput input, ref MyValue value, ref MyOutput dst) { dst.value = value; } - public bool ConcurrentWriter(ref int key, ref MyValue src, ref MyValue dst) + public override bool ConcurrentWriter(ref int key, ref MyValue src, ref MyValue dst) { dst.value = src.value; return true; } - - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - - public void ReadCompletionCallback(ref int key, ref MyInput input, ref MyOutput output, Empty ctx, Status status) - { - } - - public void RMWCompletionCallback(ref int key, ref MyInput input, Empty ctx, Status status) - { - } - - public void UpsertCompletionCallback(ref int key, ref MyValue value, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref int key, Empty ctx) - { - } - - public void SingleReader(ref int key, ref MyInput input, ref MyValue value, ref MyOutput dst) + + public override void SingleReader(ref int key, ref MyInput input, ref MyValue value, ref MyOutput dst) { dst.value = value; } - public void SingleWriter(ref int key, ref MyValue src, ref MyValue dst) + public override void SingleWriter(ref int key, ref MyValue src, ref MyValue dst) { dst = src; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref int key, ref MyValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref int key, ref MyValue value) { } -#endif } public class MyLargeValue @@ -312,7 +248,6 @@ public class MyLargeValue public MyLargeValue() { - } public MyLargeValue(int size) @@ -346,13 +281,13 @@ public class MyLargeOutput public MyLargeValue value; } - public class MyLargeFunctions : IFunctions + public class MyLargeFunctions : FunctionsBase { - public void RMWCompletionCallback(ref MyKey key, ref MyInput input, Empty ctx, Status status) + public override void RMWCompletionCallback(ref MyKey key, ref MyInput input, Empty ctx, Status status) { } - public void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyLargeOutput output, Empty ctx, Status status) + public override void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyLargeOutput output, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); for (int i = 0; i < output.value.value.Length; i++) @@ -361,59 +296,25 @@ public void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyLarge } } - - public void UpsertCompletionCallback(ref MyKey key, ref MyLargeValue value, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref MyKey key, Empty ctx) - { - } - - public bool NeedCopyUpdate(ref MyKey key, ref MyInput input, ref MyLargeValue oldValue) => true; - - public void CopyUpdater(ref MyKey key, ref MyInput input, ref MyLargeValue oldValue, ref MyLargeValue newValue) - { - } - - public void InitialUpdater(ref MyKey key, ref MyInput input, ref MyLargeValue value) - { - } - - public bool InPlaceUpdater(ref MyKey key, ref MyInput input, ref MyLargeValue value) - { - return true; - } - - public void SingleReader(ref MyKey key, ref MyInput input, ref MyLargeValue value, ref MyLargeOutput dst) + public override void SingleReader(ref MyKey key, ref MyInput input, ref MyLargeValue value, ref MyLargeOutput dst) { dst.value = value; } - public void ConcurrentReader(ref MyKey key, ref MyInput input, ref MyLargeValue value, ref MyLargeOutput dst) + public override void ConcurrentReader(ref MyKey key, ref MyInput input, ref MyLargeValue value, ref MyLargeOutput dst) { dst.value = value; } - public bool ConcurrentWriter(ref MyKey key, ref MyLargeValue src, ref MyLargeValue dst) + public override bool ConcurrentWriter(ref MyKey key, ref MyLargeValue src, ref MyLargeValue dst) { dst = src; return true; } - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - - public void SingleWriter(ref MyKey key, ref MyLargeValue src, ref MyLargeValue dst) + public override void SingleWriter(ref MyKey key, ref MyLargeValue src, ref MyLargeValue dst) { dst = src; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref MyKey key, ref MyLargeValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref MyKey key, ref MyLargeValue value) { } -#endif } } diff --git a/cs/test/ReadAddressTests.cs b/cs/test/ReadAddressTests.cs index 763a30f8f..1a04b6f17 100644 --- a/cs/test/ReadAddressTests.cs +++ b/cs/test/ReadAddressTests.cs @@ -6,8 +6,6 @@ using System.IO; using NUnit.Framework; using System.Threading.Tasks; -using System.Collections.Generic; -using System.Diagnostics; namespace FASTER.test.readaddress { @@ -61,41 +59,35 @@ public void Reset() } } + private class InsertValueIndex : ISecondaryValueIndex + { + public long lastWriteAddress; + + public string Name => nameof(InsertValueIndex); + + public bool IsMutable => true; + + public void Delete(long recordId) { } + + public void Insert(ref Value value, long recordId) => lastWriteAddress = recordId; + + public void Upsert(ref Value value, long recordId, bool isMutable) { } + } + private static long SetReadOutput(long key, long value) => (key << 32) | value; internal class Functions : AdvancedSimpleFunctions { - internal long lastWriteAddress = Constants.kInvalidAddress; - - public override void ConcurrentReader(ref Key key, ref Value input, ref Value value, ref Value dst, long address) + public override void ConcurrentReader(ref Key key, ref Value input, ref Value value, ref Value dst, ref RecordInfo recordInfo, long address) => dst.value = SetReadOutput(key.key, value.value); public override void SingleReader(ref Key key, ref Value input, ref Value value, ref Value dst, long address) => dst.value = SetReadOutput(key.key, value.value); // Return false to force a chain of values. - public override bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, long address) => false; + public override bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) => false; - public override bool InPlaceUpdater(ref Key key, ref Value input, ref Value value, long address) => false; - - // Record addresses - public override void SingleWriter(ref Key key, ref Value src, ref Value dst, long address) - { - this.lastWriteAddress = address; - base.SingleWriter(ref key, ref src, ref dst, address); - } - - public override void InitialUpdater(ref Key key, ref Value input, ref Value value, long address) - { - this.lastWriteAddress = address; - base.InitialUpdater(ref key, ref input, ref value, address); - } - - public override void CopyUpdater(ref Key key, ref Value input, ref Value oldValue, ref Value newValue, long newAddress) - { - this.lastWriteAddress = newAddress; - base.CopyUpdater(ref key, ref input, ref oldValue, ref newValue, newAddress); - } + public override bool InPlaceUpdater(ref Key key, ref Value input, ref Value value, ref RecordInfo recordInfo, long address) => false; // Track the recordInfo for its PreviousAddress. public override void ReadCompletionCallback(ref Key key, ref Value input, ref Value output, Context ctx, Status status, RecordInfo recordInfo) @@ -126,6 +118,7 @@ private class TestStore : IDisposable internal IDevice logDevice; internal string testDir; private readonly bool flush; + InsertValueIndex insertValueIndex = new InsertValueIndex(); internal long[] InsertAddresses = new long[numKeys]; @@ -151,8 +144,11 @@ internal TestStore(bool useReadCache, CopyReadsToTail copyReadsToTail, bool flus logSettings: logSettings, checkpointSettings: new CheckpointSettings { CheckpointDir = $"{this.testDir}/CheckpointDir" }, serializerSettings: null, - comparer: new Key.Comparer() + comparer: new Key.Comparer(), + supportsMutableIndexes: true ); + + this.fkv.SecondaryIndexBroker.AddIndex(insertValueIndex); } internal async ValueTask Flush() @@ -195,7 +191,7 @@ internal async Task Populate(bool useRMW, bool useAsync) if (status == Status.PENDING) await session.CompletePendingAsync(); - InsertAddresses[ii] = functions.lastWriteAddress; + InsertAddresses[ii] = insertValueIndex.lastWriteAddress; //Assert.IsTrue(session.ctx.HasNoPendingRequests); // Illustrate that deleted records can be shown as well (unless overwritten by in-place operations, which are not done here) diff --git a/cs/test/RecoverContinueTests.cs b/cs/test/RecoverContinueTests.cs index 8a2ca2e5a..4004df260 100644 --- a/cs/test/RecoverContinueTests.cs +++ b/cs/test/RecoverContinueTests.cs @@ -72,14 +72,14 @@ public async ValueTask RecoverContinueTest([Values]bool isAsync) { long sno = 0; - var firstsession = fht1.For(new SimpleFunctions()).NewSession("first"); + var firstsession = fht1.For(new AdSimpleFunctions()).NewSession("first"); IncrementAllValues(ref firstsession, ref sno); fht1.TakeFullCheckpoint(out _); fht1.CompleteCheckpointAsync().GetAwaiter().GetResult(); firstsession.Dispose(); // Check if values after checkpoint are correct - var session1 = fht1.For(new SimpleFunctions()).NewSession(); + var session1 = fht1.For(new AdSimpleFunctions()).NewSession(); CheckAllValues(ref session1, 1); session1.Dispose(); @@ -88,12 +88,12 @@ public async ValueTask RecoverContinueTest([Values]bool isAsync) await fht2.RecoverAsync(); else fht2.Recover(); - var session2 = fht2.For(new SimpleFunctions()).NewSession(); + var session2 = fht2.For(new AdSimpleFunctions()).NewSession(); CheckAllValues(ref session2, 1); session2.Dispose(); // Continue and increment values - var continuesession = fht2.For(new SimpleFunctions()).ResumeSession("first", out CommitPoint cp); + var continuesession = fht2.For(new AdSimpleFunctions()).ResumeSession("first", out CommitPoint cp); long newSno = cp.UntilSerialNo; Assert.IsTrue(newSno == sno - 1); IncrementAllValues(ref continuesession, ref sno); @@ -102,7 +102,7 @@ public async ValueTask RecoverContinueTest([Values]bool isAsync) continuesession.Dispose(); // Check if values after continue checkpoint are correct - var session3 = fht2.For(new SimpleFunctions()).NewSession(); + var session3 = fht2.For(new AdSimpleFunctions()).NewSession(); CheckAllValues(ref session3, 2); session3.Dispose(); @@ -112,7 +112,7 @@ public async ValueTask RecoverContinueTest([Values]bool isAsync) else fht3.Recover(); - var nextsession = fht3.For(new SimpleFunctions()).ResumeSession("first", out cp); + var nextsession = fht3.For(new AdSimpleFunctions()).ResumeSession("first", out cp); long newSno2 = cp.UntilSerialNo; Assert.IsTrue(newSno2 == sno - 1); CheckAllValues(ref nextsession, 2); @@ -120,7 +120,7 @@ public async ValueTask RecoverContinueTest([Values]bool isAsync) } private void CheckAllValues( - ref ClientSession fht, + ref ClientSession fht, int value) { AdInput inputArg = default; @@ -142,7 +142,7 @@ private void CheckAllValues( } private void IncrementAllValues( - ref ClientSession fht, + ref ClientSession fht, ref long sno) { AdInput inputArg = default; @@ -158,76 +158,36 @@ private void IncrementAllValues( } - public class SimpleFunctions : IFunctions + public class AdSimpleFunctions : FunctionsBase { - public void RMWCompletionCallback(ref AdId key, ref AdInput input, Empty ctx, Status status) - { - } - - public void ReadCompletionCallback(ref AdId key, ref AdInput input, ref Output output, Empty ctx, Status status) + public override void ReadCompletionCallback(ref AdId key, ref AdInput input, ref Output output, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); Assert.IsTrue(output.value.numClicks == key.adId); } - public void UpsertCompletionCallback(ref AdId key, ref NumClicks input, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref AdId key, Empty ctx) - { - } - - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - // Read functions - public void SingleReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) - { - dst.value = value; - } - - public void ConcurrentReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) - { - dst.value = value; - } - - // Upsert functions - public void SingleWriter(ref AdId key, ref NumClicks src, ref NumClicks dst) - { - dst = src; - } + public override void SingleReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) => dst.value = value; - public bool ConcurrentWriter(ref AdId key, ref NumClicks src, ref NumClicks dst) - { - dst = src; - return true; - } + public override void ConcurrentReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) => dst.value = value; // RMW functions - public void InitialUpdater(ref AdId key, ref AdInput input, ref NumClicks value) + public override void InitialUpdater(ref AdId key, ref AdInput input, ref NumClicks value) { value = input.numClicks; } - public bool InPlaceUpdater(ref AdId key, ref AdInput input, ref NumClicks value) + public override bool InPlaceUpdater(ref AdId key, ref AdInput input, ref NumClicks value) { Interlocked.Add(ref value.numClicks, input.numClicks.numClicks); return true; } - public bool NeedCopyUpdate(ref AdId key, ref AdInput input, ref NumClicks oldValue) => true; + public override bool NeedCopyUpdate(ref AdId key, ref AdInput input, ref NumClicks oldValue) => true; - public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, ref NumClicks newValue) + public override void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, ref NumClicks newValue) { newValue.numClicks += oldValue.numClicks + input.numClicks.numClicks; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } - public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } -#endif } } diff --git a/cs/test/RecoveryTestTypes.cs b/cs/test/RecoveryTestTypes.cs index a1effd42a..14f5e165c 100644 --- a/cs/test/RecoveryTestTypes.cs +++ b/cs/test/RecoveryTestTypes.cs @@ -44,74 +44,36 @@ public struct Output public NumClicks value; } - public class Functions : IFunctions + public class Functions : FunctionsBase { - public void RMWCompletionCallback(ref AdId key, ref AdInput input, Empty ctx, Status status) - { - } - - public void ReadCompletionCallback(ref AdId key, ref AdInput input, ref Output output, Empty ctx, Status status) - { - } - - public void UpsertCompletionCallback(ref AdId key, ref NumClicks input, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref AdId key, Empty ctx) - { - } - - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - // Read functions - public void SingleReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) + public override void SingleReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) { dst.value = value; } - public void ConcurrentReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) + public override void ConcurrentReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) { dst.value = value; } - // Upsert functions - public void SingleWriter(ref AdId key, ref NumClicks src, ref NumClicks dst) - { - dst = src; - } - - public bool ConcurrentWriter(ref AdId key, ref NumClicks src, ref NumClicks dst) - { - dst = src; - return true; - } - // RMW functions - public void InitialUpdater(ref AdId key, ref AdInput input, ref NumClicks value) + public override void InitialUpdater(ref AdId key, ref AdInput input, ref NumClicks value) { value = input.numClicks; } - public bool InPlaceUpdater(ref AdId key, ref AdInput input, ref NumClicks value) + public override bool InPlaceUpdater(ref AdId key, ref AdInput input, ref NumClicks value) { Interlocked.Add(ref value.numClicks, input.numClicks.numClicks); return true; } - public bool NeedCopyUpdate(ref AdId key, ref AdInput input, ref NumClicks oldValue) => true; + public override bool NeedCopyUpdate(ref AdId key, ref AdInput input, ref NumClicks oldValue) => true; - public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, ref NumClicks newValue) + public override void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, ref NumClicks newValue) { newValue.numClicks += oldValue.numClicks + input.numClicks.numClicks; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } - public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } -#endif } } diff --git a/cs/test/SimpleRecoveryTest.cs b/cs/test/SimpleRecoveryTest.cs index 2a3508d75..ad4a7ce26 100644 --- a/cs/test/SimpleRecoveryTest.cs +++ b/cs/test/SimpleRecoveryTest.cs @@ -88,7 +88,7 @@ private async ValueTask SimpleRecoveryTest1_Worker(CheckpointType checkpointType AdInput inputArg = default; Output output = default; - var session1 = fht1.NewSession(new SimpleFunctions()); + var session1 = fht1.NewSession(new AdSimpleFunctions()); for (int key = 0; key < numOps; key++) { value.numClicks = key; @@ -103,7 +103,7 @@ private async ValueTask SimpleRecoveryTest1_Worker(CheckpointType checkpointType else fht2.Recover(token); - var session2 = fht2.NewSession(new SimpleFunctions()); + var session2 = fht2.NewSession(new AdSimpleFunctions()); for (int key = 0; key < numOps; key++) { var status = session2.Read(ref inputArray[key], ref inputArg, ref output, Empty.Default, 0); @@ -160,7 +160,7 @@ public async ValueTask SimpleRecoveryTest2([Values]CheckpointType checkpointType AdInput inputArg = default; Output output = default; - var session1 = fht1.NewSession(new SimpleFunctions()); + var session1 = fht1.NewSession(new AdSimpleFunctions()); for (int key = 0; key < numOps; key++) { value.numClicks = key; @@ -175,7 +175,7 @@ public async ValueTask SimpleRecoveryTest2([Values]CheckpointType checkpointType else fht2.Recover(token); - var session2 = fht2.NewSession(new SimpleFunctions()); + var session2 = fht2.NewSession(new AdSimpleFunctions()); for (int key = 0; key < numOps; key++) { var status = session2.Read(ref inputArray[key], ref inputArg, ref output, Empty.Default, 0); @@ -228,7 +228,7 @@ public async ValueTask ShouldRecoverBeginAddress([Values]bool isAsync) NumClicks value; - var session1 = fht1.NewSession(new SimpleFunctions()); + var session1 = fht1.NewSession(new AdSimpleFunctions()); var address = 0L; for (int key = 0; key < numOps; key++) { @@ -259,77 +259,37 @@ public async ValueTask ShouldRecoverBeginAddress([Values]bool isAsync) } } - public class SimpleFunctions : IFunctions + public class AdSimpleFunctions : FunctionsBase { - public void RMWCompletionCallback(ref AdId key, ref AdInput input, Empty ctx, Status status) - { - } - - public void ReadCompletionCallback(ref AdId key, ref AdInput input, ref Output output, Empty ctx, Status status) + public override void ReadCompletionCallback(ref AdId key, ref AdInput input, ref Output output, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); Assert.IsTrue(output.value.numClicks == key.adId); } - public void UpsertCompletionCallback(ref AdId key, ref NumClicks input, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref AdId key, Empty ctx) - { - } - - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - // Read functions - public void SingleReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) - { - dst.value = value; - } - - public void ConcurrentReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) - { - dst.value = value; - } - - // Upsert functions - public void SingleWriter(ref AdId key, ref NumClicks src, ref NumClicks dst) - { - dst = src; - } + public override void SingleReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) => dst.value = value; - public bool ConcurrentWriter(ref AdId key, ref NumClicks src, ref NumClicks dst) - { - dst = src; - return true; - } + public override void ConcurrentReader(ref AdId key, ref AdInput input, ref NumClicks value, ref Output dst) => dst.value = value; // RMW functions - public void InitialUpdater(ref AdId key, ref AdInput input, ref NumClicks value) + public override void InitialUpdater(ref AdId key, ref AdInput input, ref NumClicks value) { value = input.numClicks; } - public bool InPlaceUpdater(ref AdId key, ref AdInput input, ref NumClicks value) + public override bool InPlaceUpdater(ref AdId key, ref AdInput input, ref NumClicks value) { Interlocked.Add(ref value.numClicks, input.numClicks.numClicks); return true; } - public bool NeedCopyUpdate(ref AdId key, ref AdInput input, ref NumClicks oldValue) => true; + public override bool NeedCopyUpdate(ref AdId key, ref AdInput input, ref NumClicks oldValue) => true; - public void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, ref NumClicks newValue) + public override void CopyUpdater(ref AdId key, ref AdInput input, ref NumClicks oldValue, ref NumClicks newValue) { newValue.numClicks += oldValue.numClicks + input.numClicks.numClicks; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } - public void Unlock(ref RecordInfo recordInfo, ref AdId key, ref NumClicks value) { } -#endif } } \ No newline at end of file diff --git a/cs/test/TestTypes.cs b/cs/test/TestTypes.cs index 7c9aa5d10..bcf97aaab 100644 --- a/cs/test/TestTypes.cs +++ b/cs/test/TestTypes.cs @@ -52,92 +52,56 @@ public struct OutputStruct public ValueStruct value; } - public class Functions : IFunctions + public class Functions : FunctionsBase { - public void RMWCompletionCallback(ref KeyStruct key, ref InputStruct input, Empty ctx, Status status) + public override void RMWCompletionCallback(ref KeyStruct key, ref InputStruct input, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); } - public void ReadCompletionCallback(ref KeyStruct key, ref InputStruct input, ref OutputStruct output, Empty ctx, Status status) + public override void ReadCompletionCallback(ref KeyStruct key, ref InputStruct input, ref OutputStruct output, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); Assert.IsTrue(output.value.vfield1 == key.kfield1); Assert.IsTrue(output.value.vfield2 == key.kfield2); } - public void UpsertCompletionCallback(ref KeyStruct key, ref ValueStruct output, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref KeyStruct key, Empty ctx) - { - } - - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - // Read functions - public void SingleReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) - { - dst.value = value; - } - - public void ConcurrentReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) - { - dst.value = value; - } - - // Upsert functions - public void SingleWriter(ref KeyStruct key, ref ValueStruct src, ref ValueStruct dst) - { - dst = src; - } + public override void SingleReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) => dst.value = value; - public bool ConcurrentWriter(ref KeyStruct key, ref ValueStruct src, ref ValueStruct dst) - { - dst = src; - return true; - } + public override void ConcurrentReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) => dst.value = value; // RMW functions - public void InitialUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) + public override void InitialUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) { value.vfield1 = input.ifield1; value.vfield2 = input.ifield2; } - public bool InPlaceUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) + public override bool InPlaceUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) { value.vfield1 += input.ifield1; value.vfield2 += input.ifield2; return true; } - public bool NeedCopyUpdate(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue) => true; + public override bool NeedCopyUpdate(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue) => true; - public void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue, ref ValueStruct newValue) + public override void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue, ref ValueStruct newValue) { newValue.vfield1 = oldValue.vfield1 + input.ifield1; newValue.vfield2 = oldValue.vfield2 + input.ifield2; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } - public void Unlock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } -#endif } - public class FunctionsCompaction : IFunctions + public class FunctionsCompaction : FunctionsBase { - public void RMWCompletionCallback(ref KeyStruct key, ref InputStruct input, int ctx, Status status) + public override void RMWCompletionCallback(ref KeyStruct key, ref InputStruct input, int ctx, Status status) { Assert.IsTrue(status == Status.OK); } - public void ReadCompletionCallback(ref KeyStruct key, ref InputStruct input, ref OutputStruct output, int ctx, Status status) + public override void ReadCompletionCallback(ref KeyStruct key, ref InputStruct input, ref OutputStruct output, int ctx, Status status) { if (ctx == 0) { @@ -151,71 +115,35 @@ public void ReadCompletionCallback(ref KeyStruct key, ref InputStruct input, ref } } - public void UpsertCompletionCallback(ref KeyStruct key, ref ValueStruct output, int ctx) - { - } - - public void DeleteCompletionCallback(ref KeyStruct key, int ctx) - { - } - - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - // Read functions - public void SingleReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) - { - dst.value = value; - } + public override void SingleReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) => dst.value = value; - public void ConcurrentReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) - { - dst.value = value; - } - - // Upsert functions - public void SingleWriter(ref KeyStruct key, ref ValueStruct src, ref ValueStruct dst) - { - dst = src; - } - - public bool ConcurrentWriter(ref KeyStruct key, ref ValueStruct src, ref ValueStruct dst) - { - dst = src; - return true; - } + public override void ConcurrentReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) => dst.value = value; // RMW functions - public void InitialUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) + public override void InitialUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) { value.vfield1 = input.ifield1; value.vfield2 = input.ifield2; } - public bool InPlaceUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) + public override bool InPlaceUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) { value.vfield1 += input.ifield1; value.vfield2 += input.ifield2; return true; } - public bool NeedCopyUpdate(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue) => true; + public override bool NeedCopyUpdate(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue) => true; - public void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue, ref ValueStruct newValue) + public override void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue, ref ValueStruct newValue) { newValue.vfield1 = oldValue.vfield1 + input.ifield1; newValue.vfield2 = oldValue.vfield2 + input.ifield2; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } - public void Unlock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } -#endif } - public class FunctionsCopyOnWrite : IFunctions + public class FunctionsCopyOnWrite : FunctionsBase { private int _concurrentWriterCallCount; private int _inPlaceUpdaterCallCount; @@ -223,78 +151,51 @@ public class FunctionsCopyOnWrite : IFunctions _concurrentWriterCallCount; public int InPlaceUpdaterCallCount => _inPlaceUpdaterCallCount; - public void RMWCompletionCallback(ref KeyStruct key, ref InputStruct input, Empty ctx, Status status) + public override void RMWCompletionCallback(ref KeyStruct key, ref InputStruct input, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); } - public void ReadCompletionCallback(ref KeyStruct key, ref InputStruct input, ref OutputStruct output, Empty ctx, Status status) + public override void ReadCompletionCallback(ref KeyStruct key, ref InputStruct input, ref OutputStruct output, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); Assert.IsTrue(output.value.vfield1 == key.kfield1); Assert.IsTrue(output.value.vfield2 == key.kfield2); } - public void UpsertCompletionCallback(ref KeyStruct key, ref ValueStruct output, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref KeyStruct key, Empty ctx) - { - } - - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - // Read functions - public void SingleReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) - { - dst.value = value; - } + public override void SingleReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) => dst.value = value; - public void ConcurrentReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) - { - dst.value = value; - } + public override void ConcurrentReader(ref KeyStruct key, ref InputStruct input, ref ValueStruct value, ref OutputStruct dst) => dst.value = value; // Upsert functions - public void SingleWriter(ref KeyStruct key, ref ValueStruct src, ref ValueStruct dst) - { - dst = src; - } + public override void SingleWriter(ref KeyStruct key, ref ValueStruct src, ref ValueStruct dst) => dst = src; - public bool ConcurrentWriter(ref KeyStruct key, ref ValueStruct src, ref ValueStruct dst) + public override bool ConcurrentWriter(ref KeyStruct key, ref ValueStruct src, ref ValueStruct dst) { Interlocked.Increment(ref _concurrentWriterCallCount); return false; } // RMW functions - public void InitialUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) + public override void InitialUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) { value.vfield1 = input.ifield1; value.vfield2 = input.ifield2; } - public bool InPlaceUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) + public override bool InPlaceUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct value) { Interlocked.Increment(ref _inPlaceUpdaterCallCount); return false; } - public bool NeedCopyUpdate(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue) => true; + public override bool NeedCopyUpdate(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue) => true; - public void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue, ref ValueStruct newValue) + public override void CopyUpdater(ref KeyStruct key, ref InputStruct input, ref ValueStruct oldValue, ref ValueStruct newValue) { newValue.vfield1 = oldValue.vfield1 + input.ifield1; newValue.vfield2 = oldValue.vfield2 + input.ifield2; } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } - public void Unlock(ref RecordInfo recordInfo, ref KeyStruct key, ref ValueStruct value) { } -#endif } } diff --git a/cs/test/VLTestTypes.cs b/cs/test/VLTestTypes.cs index 619649bba..aa4e5c21e 100644 --- a/cs/test/VLTestTypes.cs +++ b/cs/test/VLTestTypes.cs @@ -112,14 +112,14 @@ public struct Input public long input; } - public class VLFunctions : IFunctions + public class VLFunctions : FunctionsBase { - public void RMWCompletionCallback(ref Key key, ref Input input, Empty ctx, Status status) + public override void RMWCompletionCallback(ref Key key, ref Input input, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); } - public void ReadCompletionCallback(ref Key key, ref Input input, ref int[] output, Empty ctx, Status status) + public override void ReadCompletionCallback(ref Key key, ref Input input, ref int[] output, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); for (int i = 0; i < output.Length; i++) @@ -128,36 +128,24 @@ public void ReadCompletionCallback(ref Key key, ref Input input, ref int[] outpu } } - public void UpsertCompletionCallback(ref Key key, ref VLValue output, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref Key key, Empty ctx) - { - } - - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - // Read functions - public void SingleReader(ref Key key, ref Input input, ref VLValue value, ref int[] dst) + public override void SingleReader(ref Key key, ref Input input, ref VLValue value, ref int[] dst) { value.ToIntArray(ref dst); } - public void ConcurrentReader(ref Key key, ref Input input, ref VLValue value, ref int[] dst) + public override void ConcurrentReader(ref Key key, ref Input input, ref VLValue value, ref int[] dst) { value.ToIntArray(ref dst); } // Upsert functions - public void SingleWriter(ref Key key, ref VLValue src, ref VLValue dst) + public override void SingleWriter(ref Key key, ref VLValue src, ref VLValue dst) { src.CopyTo(ref dst); } - public bool ConcurrentWriter(ref Key key, ref VLValue src, ref VLValue dst) + public override bool ConcurrentWriter(ref Key key, ref VLValue src, ref VLValue dst) { if (src.length != dst.length) return false; @@ -165,38 +153,16 @@ public bool ConcurrentWriter(ref Key key, ref VLValue src, ref VLValue dst) src.CopyTo(ref dst); return true; } - - // RMW functions - public void InitialUpdater(ref Key key, ref Input input, ref VLValue value) - { - } - - public bool InPlaceUpdater(ref Key key, ref Input input, ref VLValue value) - { - return true; - } - - public bool NeedCopyUpdate(ref Key key, ref Input input, ref VLValue oldValue) => true; - - public void CopyUpdater(ref Key key, ref Input input, ref VLValue oldValue, ref VLValue newValue) - { - } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref Key key, ref VLValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref Key key, ref VLValue value) { } -#endif } - public class VLFunctions2 : IFunctions + public class VLFunctions2 : FunctionsBase { - public void RMWCompletionCallback(ref VLValue key, ref Input input, Empty ctx, Status status) + public override void RMWCompletionCallback(ref VLValue key, ref Input input, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); } - public void ReadCompletionCallback(ref VLValue key, ref Input input, ref int[] output, Empty ctx, Status status) + public override void ReadCompletionCallback(ref VLValue key, ref Input input, ref int[] output, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); for (int i = 0; i < output.Length; i++) @@ -205,36 +171,24 @@ public void ReadCompletionCallback(ref VLValue key, ref Input input, ref int[] o } } - public void UpsertCompletionCallback(ref VLValue key, ref VLValue output, Empty ctx) - { - } - - public void DeleteCompletionCallback(ref VLValue key, Empty ctx) - { - } - - public void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) - { - } - // Read functions - public void SingleReader(ref VLValue key, ref Input input, ref VLValue value, ref int[] dst) + public override void SingleReader(ref VLValue key, ref Input input, ref VLValue value, ref int[] dst) { value.ToIntArray(ref dst); } - public void ConcurrentReader(ref VLValue key, ref Input input, ref VLValue value, ref int[] dst) + public override void ConcurrentReader(ref VLValue key, ref Input input, ref VLValue value, ref int[] dst) { value.ToIntArray(ref dst); } // Upsert functions - public void SingleWriter(ref VLValue key, ref VLValue src, ref VLValue dst) + public override void SingleWriter(ref VLValue key, ref VLValue src, ref VLValue dst) { src.CopyTo(ref dst); } - public bool ConcurrentWriter(ref VLValue key, ref VLValue src, ref VLValue dst) + public override bool ConcurrentWriter(ref VLValue key, ref VLValue src, ref VLValue dst) { if (src.length != dst.length) return false; @@ -242,27 +196,5 @@ public bool ConcurrentWriter(ref VLValue key, ref VLValue src, ref VLValue dst) src.CopyTo(ref dst); return true; } - - // RMW functions - public void InitialUpdater(ref VLValue key, ref Input input, ref VLValue value) - { - } - - public bool InPlaceUpdater(ref VLValue key, ref Input input, ref VLValue value) - { - return true; - } - - public bool NeedCopyUpdate(ref VLValue key, ref Input input, ref VLValue oldValue) => true; - - public void CopyUpdater(ref VLValue key, ref Input input, ref VLValue oldValue, ref VLValue newValue) - { - } - -#if !NETSTANDARD2_1 - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref VLValue key, ref VLValue value) { } - public void Unlock(ref RecordInfo recordInfo, ref VLValue key, ref VLValue value) { } -#endif } } From 047e27d71ff2a9f12619d9dfbff2ef68ce82cffc Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Wed, 24 Feb 2021 21:51:08 -0800 Subject: [PATCH 06/37] Additional locking work: - enable locking in YCSB benchmark - re-allow threadAffinitized sessions with indexes (still need to add some SI calls in pending handlers) - rename to .SupportsLocking - create LockType enum rather than reusing OperationType - make tests for context.IsNewRecord more efficient in ContextUpsert et al. --- cs/benchmark/FasterYcsbBenchmark.cs | 13 +++--- cs/benchmark/Functions.cs | 21 ++++++--- .../ClientSession/AdvancedClientSession.cs | 35 +++++++-------- cs/src/core/ClientSession/ClientSession.cs | 38 +++++++--------- cs/src/core/Index/Common/Contexts.cs | 11 +---- cs/src/core/Index/FASTER/FASTER.cs | 26 ++++++++--- cs/src/core/Index/FASTER/FASTERImpl.cs | 15 ++----- cs/src/core/Index/FASTER/FASTERLegacy.cs | 6 +-- .../Index/FASTER/LogCompactionFunctions.cs | 12 ++--- cs/src/core/Index/Interfaces/FunctionsBase.cs | 34 +++++++++++--- .../Index/Interfaces/IAdvancedFunctions.cs | 45 +++++++++++++------ cs/src/core/Index/Interfaces/IFunctions.cs | 18 ++++---- cs/src/core/Utilities/LockType.cs | 21 +++++++++ cs/test/LockTests.cs | 17 +++---- 14 files changed, 184 insertions(+), 128 deletions(-) create mode 100644 cs/src/core/Utilities/LockType.cs diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index c28ba0470..e603009e0 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -78,8 +78,8 @@ public enum Op : ulong readonly int numaStyle; readonly string distribution; readonly int readPercent; - readonly Functions functions = new Functions(); - SecondaryIndexType secondaryIndexType = SecondaryIndexType.None; + readonly Functions functions; + readonly SecondaryIndexType secondaryIndexType = SecondaryIndexType.None; volatile bool done = false; @@ -94,6 +94,9 @@ public FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distributio distribution = distribution_; readPercent = readPercent_; this.backupMode = (BackupMode)backupOptions_; + secondaryIndexType = (SecondaryIndexType)indexType_; + functions = new Functions(secondaryIndexType != SecondaryIndexType.None); + Console.WriteLine($"SupportsLocking: {functions.SupportsLocking}"); #if DASHBOARD statsWritten = new AutoResetEvent[threadCount]; @@ -123,7 +126,7 @@ public FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distributio new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }, supportsMutableIndexes: secondaryIndexType != SecondaryIndexType.None); - if (secondaryIndexType != SecondaryIndexType.None) + if (functions.SupportsLocking) { if (secondaryIndexType.HasFlag(SecondaryIndexType.Key)) store.SecondaryIndexBroker.AddIndex(new NullKeyIndex()); @@ -159,7 +162,7 @@ private void RunYcsb(int thread_idx) int count = 0; #endif - var session = store.For(functions).NewSession(null, threadAffinitized: secondaryIndexType == SecondaryIndexType.None); + var session = store.For(functions).NewSession(null, kAffinitizedSession); while (!done) { @@ -396,7 +399,7 @@ private void SetupYcsb(int thread_idx) else Native32.AffinitizeThreadShardedNuma((uint)thread_idx, 2); // assuming two NUMA sockets - var session = store.For(functions).NewSession(null, threadAffinitized: secondaryIndexType == SecondaryIndexType.None); + var session = store.For(functions).NewSession(null, kAffinitizedSession); #if DASHBOARD var tstart = Stopwatch.GetTimestamp(); diff --git a/cs/benchmark/Functions.cs b/cs/benchmark/Functions.cs index b5a6714db..4837e37b4 100644 --- a/cs/benchmark/Functions.cs +++ b/cs/benchmark/Functions.cs @@ -3,16 +3,18 @@ #pragma warning disable 1591 -using System; using System.Runtime.CompilerServices; using System.Diagnostics; using FASTER.core; -using System.Collections.Generic; namespace FASTER.benchmark { public struct Functions : IFunctions { + readonly bool locking; + + public Functions(bool locking) => this.locking = locking; + public void RMWCompletionCallback(ref Key key, ref Input input, Empty ctx, Status status) { } @@ -81,10 +83,19 @@ public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Va newValue.value = input.value + oldValue.value; } - public bool SupportsLocks => false; + public bool SupportsLocking => locking; - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) + { + //if (locking) + // recordInfo.SpinLock(); + } - public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) + { + //if (locking) + // recordInfo.Unlock(); + return true; + } } } diff --git a/cs/src/core/ClientSession/AdvancedClientSession.cs b/cs/src/core/ClientSession/AdvancedClientSession.cs index f8aa8c592..cfa3a6a7c 100644 --- a/cs/src/core/ClientSession/AdvancedClientSession.cs +++ b/cs/src/core/ClientSession/AdvancedClientSession.cs @@ -55,9 +55,6 @@ internal AdvancedClientSession( LatestCommitPoint = new CommitPoint { UntilSerialNo = -1, ExcludedSerialNos = null }; FasterSession = new InternalFasterSession(this); - if (fht.SupportsMutableIndexes && !supportAsync) - throw new FasterException("Cannot specify thread-affinitized sessions with mutable secondary indexes"); - this.variableLengthStruct = sessionVariableLengthStructSettings?.valueLength; if (this.variableLengthStruct == default) { @@ -718,7 +715,7 @@ void IClientSession.AtomicSwitch(int version) } // This is a struct to allow JIT to inline calls (and bypass default interface call mechanism) - internal struct InternalFasterSession : IFasterSession + internal readonly struct InternalFasterSession : IFasterSession { private readonly AdvancedClientSession _clientSession; @@ -736,7 +733,7 @@ public void CheckpointCompletionCallback(string guid, CommitPoint commitPoint) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, ref RecordInfo recordInfo, long address) { - if (!this.SupportsLocks) + if (!this.SupportsLocking) _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst, ref recordInfo, address); else ConcurrentReaderLock(ref key, ref input, ref value, ref dst, ref recordInfo, address); @@ -747,21 +744,21 @@ public void ConcurrentReaderLock(ref Key key, ref Input input, ref Value value, for (bool retry = true; retry; /* updated in loop */) { long context = 0; - this.Lock(ref recordInfo, ref key, ref value, OperationType.READ, ref context); + this.Lock(ref recordInfo, ref key, ref value, LockType.Shared, ref context); try { _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst, ref recordInfo, address); } finally { - retry = !this.Unlock(ref recordInfo, ref key, ref value, OperationType.READ, context); + retry = !this.Unlock(ref recordInfo, ref key, ref value, LockType.Shared, context); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) - => !this.SupportsLocks + => !this.SupportsLocking ? ConcurrentWriterNoLock(ref key, ref src, ref dst, ref recordInfo, address) : ConcurrentWriterLock(ref key, ref src, ref dst, ref recordInfo, address); @@ -773,7 +770,7 @@ private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, r private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) { long context = 0; - this.Lock(ref recordInfo, ref key, ref dst, OperationType.UPSERT, ref context); + this.Lock(ref recordInfo, ref key, ref dst, LockType.Exclusive, ref context); try { // KeyIndexes do not need notification of in-place updates because the key does not change. @@ -781,13 +778,13 @@ private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref } finally { - this.Unlock(ref recordInfo, ref key, ref dst, OperationType.UPSERT, context); + this.Unlock(ref recordInfo, ref key, ref dst, LockType.Exclusive, context); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ConcurrentDeleter(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) - => !this.SupportsLocks + => !this.SupportsLocking ? ConcurrentDeleterNoLock(ref key, ref value, ref recordInfo, address) : ConcurrentDeleterLock(ref key, ref value, ref recordInfo, address); @@ -802,14 +799,14 @@ private bool ConcurrentDeleterNoLock(ref Key key, ref Value value, ref RecordInf private bool ConcurrentDeleterLock(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) { long context = 0; - this.Lock(ref recordInfo, ref key, ref value, OperationType.DELETE, ref context); + this.Lock(ref recordInfo, ref key, ref value, LockType.Exclusive, ref context); try { return ConcurrentDeleterNoLock(ref key, ref value, ref recordInfo, address); } finally { - this.Unlock(ref recordInfo, ref key, ref value, OperationType.DELETE, context); + this.Unlock(ref recordInfo, ref key, ref value, LockType.Exclusive, context); } } @@ -843,7 +840,7 @@ public void InitialUpdater(ref Key key, ref Input input, ref Value value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) - => !this.SupportsLocks + => !this.SupportsLocking ? InPlaceUpdaterNoLock(ref key, ref input, ref value, ref recordInfo, address) : InPlaceUpdaterLock(ref key, ref input, ref value, ref recordInfo, address); @@ -855,7 +852,7 @@ private bool InPlaceUpdaterNoLock(ref Key key, ref Input input, ref Value value, private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) { long context = 0; - this.Lock(ref recordInfo, ref key, ref value, OperationType.RMW, ref context); + this.Lock(ref recordInfo, ref key, ref value, LockType.Exclusive, ref context); try { // KeyIndexes do not need notification of in-place updates because the key does not change. @@ -863,7 +860,7 @@ private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, r } finally { - this.Unlock(ref recordInfo, ref key, ref value, OperationType.RMW, context); + this.Unlock(ref recordInfo, ref key, ref value, LockType.Exclusive, context); } } @@ -910,11 +907,11 @@ public IHeapContainer GetHeapContainer(ref Input input) return new VarLenHeapContainer(ref input, _clientSession.inputVariableLengthStruct, _clientSession.fht.hlog.bufferPool); } - public bool SupportsLocks => _clientSession.functions.SupportsLocks; + public bool SupportsLocking => _clientSession.functions.SupportsLocking; - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value, opType, ref context); + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value, lockType, ref context); - public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value, opType, context); + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value, lockType, context); } } } diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index 91a7df2b4..baa76641c 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -53,9 +53,6 @@ internal ClientSession( LatestCommitPoint = new CommitPoint { UntilSerialNo = -1, ExcludedSerialNos = null }; FasterSession = new InternalFasterSession(this); - if (fht.SupportsMutableIndexes && !supportAsync) - throw new FasterException("Cannot specify thread-affinitized sessions with mutable secondary indexes"); - this.variableLengthStruct = sessionVariableLengthStructSettings?.valueLength; if (this.variableLengthStruct == default) { @@ -728,7 +725,7 @@ void IClientSession.AtomicSwitch(int version) } // This is a struct to allow JIT to inline calls (and bypass default interface call mechanism) - internal struct InternalFasterSession : IFasterSession + internal readonly struct InternalFasterSession : IFasterSession { private readonly ClientSession _clientSession; @@ -746,7 +743,7 @@ public void CheckpointCompletionCallback(string guid, CommitPoint commitPoint) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, ref RecordInfo recordInfo, long address) { - if (!this.SupportsLocks) + if (!this.SupportsLocking) _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst); else ConcurrentReaderLock(ref key, ref input, ref value, ref dst, ref recordInfo); @@ -757,47 +754,47 @@ public void ConcurrentReaderLock(ref Key key, ref Input input, ref Value value, for (bool retry = true; retry; /* updated in loop */) { long context = 0; - this.Lock(ref recordInfo, ref key, ref value, OperationType.READ, ref context); + this.Lock(ref recordInfo, ref key, ref value, LockType.Shared, ref context); try { _clientSession.functions.ConcurrentReader(ref key, ref input, ref value, ref dst); } finally { - retry = this.Unlock(ref recordInfo, ref key, ref value, OperationType.READ, context); + retry = !this.Unlock(ref recordInfo, ref key, ref value, LockType.Shared, context); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) - => !this.SupportsLocks + => !this.SupportsLocking ? ConcurrentWriterNoLock(ref key, ref src, ref dst, address) : ConcurrentWriterLock(ref key, ref src, ref dst, ref recordInfo, address); [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, long address) - // KeyIndexes do not need notification of in-place updates because the key does not change. => _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst) && _clientSession.fht.UpdateSIForIPU(ref dst, address); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) { long context = 0; - this.Lock(ref recordInfo, ref key, ref dst, OperationType.UPSERT, ref context); + this.Lock(ref recordInfo, ref key, ref dst, LockType.Exclusive, ref context); try { return !recordInfo.Tombstone && ConcurrentWriterNoLock(ref key, ref src, ref dst, address); } finally { - this.Unlock(ref recordInfo, ref key, ref dst, OperationType.UPSERT, context); + this.Unlock(ref recordInfo, ref key, ref dst, LockType.Exclusive, context); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ConcurrentDeleter(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) - => !this.SupportsLocks + => !this.SupportsLocking ? ConcurrentDeleterNoLock(ref key, ref value, ref recordInfo, address) : ConcurrentDeleterLock(ref key, ref value, ref recordInfo, address); @@ -812,14 +809,14 @@ private bool ConcurrentDeleterNoLock(ref Key key, ref Value value, ref RecordInf private bool ConcurrentDeleterLock(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) { long context = 0; - this.Lock(ref recordInfo, ref key, ref value, OperationType.DELETE, ref context); + this.Lock(ref recordInfo, ref key, ref value, LockType.Exclusive, ref context); try { return ConcurrentDeleterNoLock(ref key, ref value, ref recordInfo, address); } finally { - this.Unlock(ref recordInfo, ref key, ref value, OperationType.DELETE, context); + this.Unlock(ref recordInfo, ref key, ref value, LockType.Exclusive, context); } } @@ -853,7 +850,7 @@ public void InitialUpdater(ref Key key, ref Input input, ref Value value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) - => !this.SupportsLocks + => !this.SupportsLocking ? InPlaceUpdaterNoLock(ref key, ref input, ref value, address) : InPlaceUpdaterLock(ref key, ref input, ref value, ref recordInfo, address); @@ -865,15 +862,14 @@ private bool InPlaceUpdaterNoLock(ref Key key, ref Input input, ref Value value, private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) { long context = 0; - this.Lock(ref recordInfo, ref key, ref value, OperationType.RMW, ref context); + this.Lock(ref recordInfo, ref key, ref value, LockType.Exclusive, ref context); try { - // KeyIndexes do not need notification of in-place updates because the key does not change. return !recordInfo.Tombstone && InPlaceUpdaterNoLock(ref key, ref input, ref value, address); } finally { - this.Unlock(ref recordInfo, ref key, ref value, OperationType.RMW, context); + this.Unlock(ref recordInfo, ref key, ref value, LockType.Exclusive, context); } } @@ -920,11 +916,11 @@ public IHeapContainer GetHeapContainer(ref Input input) return new VarLenHeapContainer(ref input, _clientSession.inputVariableLengthStruct, _clientSession.fht.hlog.bufferPool); } - public bool SupportsLocks => _clientSession.functions.SupportsLocks; + public bool SupportsLocking => _clientSession.functions.SupportsLocking; - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value, opType, ref context); + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value, lockType, ref context); - public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value, opType, context); + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value, lockType, context); } } } diff --git a/cs/src/core/Index/Common/Contexts.cs b/cs/src/core/Index/Common/Contexts.cs index 226a0a36c..028e4f09d 100644 --- a/cs/src/core/Index/Common/Contexts.cs +++ b/cs/src/core/Index/Common/Contexts.cs @@ -12,20 +12,11 @@ namespace FASTER.core { - /// - /// The type of FASTER operation being done - /// - public enum OperationType + internal enum OperationType { - /// Read operation READ, - /// Read-Modify-Write operation RMW, - /// Upsert operation UPSERT, - /// Insert operation (either Upsert or RMW resulted in a new record) - INSERT, - /// Delete operation DELETE } diff --git a/cs/src/core/Index/FASTER/FASTER.cs b/cs/src/core/Index/FASTER/FASTER.cs index 1624c699f..25e785b48 100644 --- a/cs/src/core/Index/FASTER/FASTER.cs +++ b/cs/src/core/Index/FASTER/FASTER.cs @@ -591,7 +591,6 @@ internal Status ContextUpsert(ref Key key while (internalStatus == OperationStatus.RETRY_NOW); Status status; - if (internalStatus == OperationStatus.SUCCESS || internalStatus == OperationStatus.NOTFOUND) { status = (Status)internalStatus; @@ -601,8 +600,9 @@ internal Status ContextUpsert(ref Key key status = HandleOperationStatus(sessionCtx, sessionCtx, ref pcontext, fasterSession, internalStatus, false, out _); } - if (this.SupportsMutableIndexes && (status == Status.OK || status == Status.NOTFOUND) && pcontext.IsNewRecord) + if (pcontext.IsNewRecord) { + Debug.Assert(status == Status.OK); ref RecordInfo recordInfo = ref this.hlog.GetInfo(this.hlog.GetPhysicalAddress(pcontext.logicalAddress)); UpdateSIForInsert(ref key, ref value, ref recordInfo, pcontext.logicalAddress, fasterSession); } @@ -612,11 +612,20 @@ internal Status ContextUpsert(ref Key key return status; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool UpdateSIForIPU(ref Value value, long address) + { + // KeyIndexes do not need notification of in-place updates because the key does not change. + if (this.SupportsMutableIndexes && this.SecondaryIndexBroker.MutableValueIndexCount > 0) + this.SecondaryIndexBroker.Upsert(ref value, address); + return true; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateSIForInsert(ref Key key, ref Value value, ref RecordInfo recordInfo, long address, FasterSession fasterSession) where FasterSession : IFasterSession { - if (!fasterSession.SupportsLocks) + if (!fasterSession.SupportsLocking) UpdateSIForInsertNoLock(ref key, ref value, ref recordInfo, address); else UpdateSIForInsertLock(ref key, ref value, ref recordInfo, address, fasterSession); @@ -638,14 +647,14 @@ private void UpdateSIForInsertLock(ref Ke where FasterSession : IFasterSession { long context = 0; - fasterSession.Lock(ref recordInfo, ref key, ref value, OperationType.INSERT, ref context); + fasterSession.Lock(ref recordInfo, ref key, ref value, LockType.Exclusive, ref context); try { UpdateSIForInsertNoLock(ref key, ref value, ref recordInfo, address); } finally { - fasterSession.Unlock(ref recordInfo, ref key, ref value, OperationType.INSERT, context); + fasterSession.Unlock(ref recordInfo, ref key, ref value, LockType.Exclusive, context); } } @@ -671,8 +680,9 @@ internal Status ContextRMW(ref Key key, r status = HandleOperationStatus(sessionCtx, sessionCtx, ref pcontext, fasterSession, internalStatus, false, out _); } - if (this.SupportsMutableIndexes && (status == Status.OK || status == Status.NOTFOUND) && pcontext.IsNewRecord) + if (pcontext.IsNewRecord) { + Debug.Assert(status == Status.OK || status == Status.NOTFOUND); long physicalAddress = this.hlog.GetPhysicalAddress(pcontext.logicalAddress); ref RecordInfo recordInfo = ref this.hlog.GetInfo(physicalAddress); ref Value value = ref this.hlog.GetValue(physicalAddress); @@ -710,8 +720,10 @@ internal Status ContextDelete( status = HandleOperationStatus(sessionCtx, sessionCtx, ref pcontext, fasterSession, internalStatus, false, out _); } - if (this.SupportsMutableIndexes && status == Status.OK && pcontext.IsNewRecord) + if (pcontext.IsNewRecord) { + Debug.Assert(status == Status.OK); + // No need to lock here; we have just written a new record with a tombstone, so it will not be changed // TODO - but this can race with an INSERT... this.UpdateSIForDelete(ref key, pcontext.logicalAddress, isNewRecord: true); diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index a81345335..afe883ea5 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -467,7 +467,7 @@ internal OperationStatus InternalUpsert( if (foundEntry.word == entry.word) { pendingContext.logicalAddress = newLogicalAddress; - pendingContext.IsNewRecord = true; + pendingContext.IsNewRecord = this.SupportsMutableIndexes; status = OperationStatus.SUCCESS; goto LatchRelease; } @@ -514,15 +514,6 @@ internal OperationStatus InternalUpsert( return status; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool UpdateSIForIPU(ref Value value, long address) - { - // KeyIndexes do not need notification of in-place updates because the key does not change. - if (this.SupportsMutableIndexes && this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Upsert(ref value, address); - return true; - } - #endregion #region RMW Operation @@ -830,7 +821,7 @@ ref hlog.GetValue(physicalAddress), if (foundEntry.word == entry.word) { pendingContext.logicalAddress = newLogicalAddress; - pendingContext.IsNewRecord = true; + pendingContext.IsNewRecord = this.SupportsMutableIndexes; goto LatchRelease; } else @@ -1098,7 +1089,7 @@ internal OperationStatus InternalDelete( if (foundEntry.word == entry.word) { pendingContext.logicalAddress = newLogicalAddress; - pendingContext.IsNewRecord = true; + pendingContext.IsNewRecord = this.SupportsMutableIndexes; status = OperationStatus.SUCCESS; goto LatchRelease; } diff --git a/cs/src/core/Index/FASTER/FASTERLegacy.cs b/cs/src/core/Index/FASTER/FASTERLegacy.cs index 4cbe08d1f..0ec1b64af 100644 --- a/cs/src/core/Index/FASTER/FASTERLegacy.cs +++ b/cs/src/core/Index/FASTER/FASTERLegacy.cs @@ -413,11 +413,11 @@ public IHeapContainer GetHeapContainer(ref Input input) return new StandardHeapContainer(ref input); } - public bool SupportsLocks => false; + public bool SupportsLocking => false; - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) { } - public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) => true; } } diff --git a/cs/src/core/Index/FASTER/LogCompactionFunctions.cs b/cs/src/core/Index/FASTER/LogCompactionFunctions.cs index 8141c16dc..e43fdab5a 100644 --- a/cs/src/core/Index/FASTER/LogCompactionFunctions.cs +++ b/cs/src/core/Index/FASTER/LogCompactionFunctions.cs @@ -31,9 +31,9 @@ public void SingleReader(ref Key key, ref Empty input, ref Value value, ref Empt public void SingleWriter(ref Key key, ref Value src, ref Value dst) { _functions.Copy(ref src, ref dst, _allocator.ValueLength); } public void UpsertCompletionCallback(ref Key key, ref Value value, Empty ctx) { } public void DeleteCompletionCallback(ref Key key, Empty ctx) { } - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } - public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; + public bool SupportsLocking => false; + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) { } + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) => true; } internal sealed class LogCompactFunctions : IFunctions @@ -59,9 +59,9 @@ public void SingleReader(ref Key key, ref Empty input, ref Value value, ref Empt public void SingleWriter(ref Key key, ref Value src, ref Value dst) { _functions.Copy(ref src, ref dst, null); } public void UpsertCompletionCallback(ref Key key, ref Value value, Empty ctx) { } public void DeleteCompletionCallback(ref Key key, Empty ctx) { } - public bool SupportsLocks => false; - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } - public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; + public bool SupportsLocking => false; + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) { } + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) => true; } internal unsafe struct DefaultVariableCompactionFunctions : ICompactionFunctions diff --git a/cs/src/core/Index/Interfaces/FunctionsBase.cs b/cs/src/core/Index/Interfaces/FunctionsBase.cs index 8f5741592..d20b56ec4 100644 --- a/cs/src/core/Index/Interfaces/FunctionsBase.cs +++ b/cs/src/core/Index/Interfaces/FunctionsBase.cs @@ -38,9 +38,20 @@ public virtual void UpsertCompletionCallback(ref Key key, ref Value value, Conte public virtual void DeleteCompletionCallback(ref Key key, Context ctx) { } public virtual void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) { } - public virtual bool SupportsLocks => locking; - public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } - public virtual bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; + public virtual bool SupportsLocking => locking; + + public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) + { + if (locking) + recordInfo.SpinLock(); + } + + public virtual bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) + { + if (locking) + recordInfo.Unlock(); + return true; + } } /// @@ -113,9 +124,20 @@ public virtual void UpsertCompletionCallback(ref Key key, ref Value value, Conte public virtual void DeleteCompletionCallback(ref Key key, Context ctx) { } public virtual void CheckpointCompletionCallback(string sessionId, CommitPoint commitPoint) { } - public virtual bool SupportsLocks => locking; - public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context) { } - public virtual bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context) => true; + public virtual bool SupportsLocking => locking; + + public virtual void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) + { + if (locking) + recordInfo.SpinLock(); + } + + public virtual bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) + { + if (locking) + recordInfo.Unlock(); + return true; + } } /// diff --git a/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs b/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs index 989cacfd1..b89083006 100644 --- a/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs +++ b/cs/src/core/Index/Interfaces/IAdvancedFunctions.cs @@ -90,7 +90,7 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The key for this record /// The user input to be used for computing the updated /// The destination to be updated; because this is an in-place update, there is a previous value there. - /// A reference to the header of the record; may be used by + /// A reference to the header of the record; may be used by /// The logical address of the record being updated; used as a RecordId by indexing bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address); @@ -111,7 +111,7 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The user input for computing from /// The value for the record being read /// The location where is to be copied - /// A reference to the header of the record; may be used by + /// A reference to the header of the record; may be used by /// The logical address of the record being copied to; used as a RecordId by indexing void ConcurrentReader(ref Key key, ref Input input, ref Value value, ref Output dst, ref RecordInfo recordInfo, long address); @@ -130,7 +130,7 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The key for the record to be written /// The value to be copied to /// The location where is to be copied; because this method is called only for in-place updates, there is a previous value there. - /// A reference to the header of the record; may be used by + /// A reference to the header of the record; may be used by /// The logical address of the record being copied to; used as a RecordId by indexing"/> bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address); @@ -139,16 +139,16 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// /// The key for the record to be deleted /// The value for the record being deleted; because this method is called only for in-place updates, there is a previous value there. Usually this is ignored or assigned 'default'. - /// A reference to the header of the record; may be used by + /// A reference to the header of the record; may be used by /// The logical address of the record being copied to; used as a RecordId by indexing /// True if handled by the Functions implementation, else false public bool ConcurrentDeleter(ref Key key, ref Value value, ref RecordInfo recordInfo, long address); /// - /// Whether this Functions implementation actually locks in - /// and + /// Whether this Functions implementation actually locks in + /// and /// - bool SupportsLocks { get; } + bool SupportsLocking { get; } /// /// User-provided lock call, defaulting to no-op. A default exclusive implementation is available via . @@ -157,12 +157,12 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The header for the current record /// The key for the current record /// The value for the current record - /// The type of FASTER operation being done (can be used to decide whether to obtain a read vs. exclusinve lock). - /// Context-specific information; will be passed to + /// The type of lock being taken + /// Context-specific information; will be passed to /// /// This is called only for records guaranteed to be in the mutable range. /// - void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context); + void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context); /// /// User-provided unlock call, defaulting to no-op. A default exclusive implementation is available via . @@ -171,8 +171,8 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The header for the current record /// The key for the current record /// The value for the current record - /// The type of FASTER operation being done, as passed to - /// The context returned from + /// The type of lock being released, as passed to + /// The context returned from /// /// This is called only for records guaranteed to be in the mutable range. /// @@ -180,6 +180,25 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// True if no inconsistencies detected. Otherwise, the lock and user's callback are reissued. /// Currently this is handled only for . /// - bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context); + bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context); + } + + /// + /// Callback functions to FASTER (two-param version) + /// + /// + /// + public interface IAdvancedFunctions : IAdvancedFunctions + { + } + + /// + /// Callback functions to FASTER (two-param version with context) + /// + /// + /// + /// + public interface IAdvancedFunctions : IAdvancedFunctions + { } } \ No newline at end of file diff --git a/cs/src/core/Index/Interfaces/IFunctions.cs b/cs/src/core/Index/Interfaces/IFunctions.cs index 2f7e79086..d0271cbc0 100644 --- a/cs/src/core/Index/Interfaces/IFunctions.cs +++ b/cs/src/core/Index/Interfaces/IFunctions.cs @@ -127,10 +127,10 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst); /// - /// Whether this Functions implementation actually locks in - /// and + /// Whether this Functions implementation actually locks in + /// and /// - bool SupportsLocks { get; } + bool SupportsLocking { get; } /// /// User-provided lock call, defaulting to no-op. A default exclusive implementation is available via . @@ -139,12 +139,12 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The header for the current record /// The key for the current record /// The value for the current record - /// The type of FASTER operation being done (can be used to decide whether to obtain a read vs. exclusinve lock). - /// Context-specific information; will be passed to + /// The type of lock being taken + /// Context-specific information; will be passed to /// /// This is called only for records guaranteed to be in the mutable range. /// - void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, ref long context); + void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context); /// /// User-provided unlock call, defaulting to no-op. A default exclusive implementation is available via . @@ -153,8 +153,8 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// The header for the current record /// The key for the current record /// The value for the current record - /// The type of FASTER operation being done, as passed to - /// The context returned from + /// The type of lock being released, as passed to + /// The context returned from /// /// This is called only for records guaranteed to be in the mutable range. /// @@ -162,7 +162,7 @@ bool NeedCopyUpdate(ref Key key, ref Input input, ref Value oldValue) /// True if no inconsistencies detected. Otherwise, the lock and user's callback are reissued. /// Currently this is handled only for . /// - bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, OperationType opType, long context); + bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context); } /// diff --git a/cs/src/core/Utilities/LockType.cs b/cs/src/core/Utilities/LockType.cs new file mode 100644 index 000000000..9bc3345a8 --- /dev/null +++ b/cs/src/core/Utilities/LockType.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace FASTER.core +{ + /// + /// Type of lock taken by FASTER on Read, Upsert, RMW, or Delete operations, either directly or within concurrent callback operations + /// + public enum LockType + { + /// + /// Shared lock, taken on Read + /// + Shared, + + /// + /// Exclusive lock, taken on Upsert, RMW, or Delete + /// + Exclusive + } +} diff --git a/cs/test/LockTests.cs b/cs/test/LockTests.cs index 6c4be63c4..06404e59a 100644 --- a/cs/test/LockTests.cs +++ b/cs/test/LockTests.cs @@ -31,9 +31,9 @@ bool Increment(ref int dst) public override bool InPlaceUpdater(ref int key, ref int input, ref int value, ref RecordInfo recordInfo, long address) => Increment(ref value); - public override bool SupportsLocks => true; - public override void Lock(ref RecordInfo recordInfo, ref int key, ref int value, OperationType opType, ref long context) => recordInfo.SpinLock(); - public override bool Unlock(ref RecordInfo recordInfo, ref int key, ref int value, OperationType opType, long context) + public override bool SupportsLocking => true; + public override void Lock(ref RecordInfo recordInfo, ref int key, ref int value, LockType lockType, ref long context) => recordInfo.SpinLock(); + public override bool Unlock(ref RecordInfo recordInfo, ref int key, ref int value, LockType lockType, long context) { recordInfo.Unlock(); return true; @@ -66,7 +66,7 @@ public void TearDown() [Test] public unsafe void RecordInfoLockTest() { - for (var ii = 0; ii < 10; ++ii) + for (var ii = 0; ii < 5; ++ii) { RecordInfo recordInfo = new RecordInfo(); RecordInfo* ri = &recordInfo; @@ -82,14 +82,7 @@ private void XLockTest(Action locker, Action unlocker) const int numIters = 5000; var tasks = Enumerable.Range(0, numThreads).Select(ii => Task.Factory.StartNew(XLockTestFunc)).ToArray(); - try - { - Task.WaitAll(tasks); - } - catch (AggregateException ex) - { - Assert.Fail(ex.InnerExceptions.First().Message); - } + Task.WaitAll(tasks); Assert.AreEqual(numThreads * numIters, lockTestValue); From 85c54a40ed5421491e6f7a63d53ae9b342a8ec07 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Thu, 25 Feb 2021 11:43:28 -0800 Subject: [PATCH 07/37] Update YCSB benchmark to separate locking and indexing parameters and add an iteration count; refactor Memory and Span Functions to use the new locking scheme --- cs/benchmark/ConcurrentDictionaryBenchmark.cs | 6 +- cs/benchmark/FasterSpanByteYcsbBenchmark.cs | 28 +++++-- cs/benchmark/FasterYcsbBenchmark.cs | 26 +++--- cs/benchmark/Functions.cs | 6 +- cs/benchmark/FunctionsSB.cs | 1 + cs/benchmark/Program.cs | 83 +++++++++++++++---- cs/src/core/VarLen/MemoryFunctions.cs | 23 +++-- cs/src/core/VarLen/SpanByteFunctions.cs | 34 +++++++- 8 files changed, 154 insertions(+), 53 deletions(-) diff --git a/cs/benchmark/ConcurrentDictionaryBenchmark.cs b/cs/benchmark/ConcurrentDictionaryBenchmark.cs index 36946ed25..fd0afd585 100644 --- a/cs/benchmark/ConcurrentDictionaryBenchmark.cs +++ b/cs/benchmark/ConcurrentDictionaryBenchmark.cs @@ -194,7 +194,7 @@ private void RunYcsb(int thread_idx) Interlocked.Add(ref total_ops_done, reads_done + writes_done); } - public unsafe void Run() + public unsafe double Run() { RandomGenerator rng = new RandomGenerator(); @@ -286,9 +286,11 @@ public unsafe void Run() double seconds = swatch.ElapsedMilliseconds / 1000.0; + double opsPerSecond = total_ops_done / seconds; Console.WriteLine("Total " + total_ops_done + " ops done " + " in " + seconds + " secs."); Console.WriteLine("##, " + distribution + ", " + numaStyle + ", " + readPercent + ", " - + threadCount + ", " + total_ops_done / seconds); + + threadCount + ", " + opsPerSecond); + return opsPerSecond; } private void SetupYcsb(int thread_idx) diff --git a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs index 6be76f392..d30b31686 100644 --- a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs +++ b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs @@ -35,7 +35,7 @@ public enum Op : ulong const int kPeriodicCheckpointMilliseconds = 0; #else const bool kDumpDistribution = false; - const bool kUseSmallData = false; + const bool kUseSmallData = true; //false; const bool kUseSyntheticData = false; const bool kSmallMemoryLog = false; const bool kAffinitizedSession = true; @@ -82,17 +82,20 @@ public enum Op : ulong readonly int numaStyle; readonly string distribution; readonly int readPercent; - readonly FunctionsSB functions = new FunctionsSB(); + readonly FunctionsSB functions; volatile bool done = false; - public FasterSpanByteYcsbBenchmark(int threadCount_, int numaStyle_, string distribution_, int readPercent_, int backupOptions_) + public FasterSpanByteYcsbBenchmark(int threadCount_, int numaStyle_, string distribution_, int readPercent_, int backupOptions_, int lockImpl_, int secondaryIndexType_) { threadCount = threadCount_; numaStyle = numaStyle_; distribution = distribution_; readPercent = readPercent_; this.backupMode = (BackupMode)backupOptions_; + var lockImpl = (LockImpl)lockImpl_; + var secondaryIndexType = (SecondaryIndexType)secondaryIndexType_; + functions = new FunctionsSB(lockImpl != LockImpl.None); #if DASHBOARD statsWritten = new AutoResetEvent[threadCount]; @@ -113,10 +116,19 @@ public FasterSpanByteYcsbBenchmark(int threadCount_, int numaStyle_, string dist if (kSmallMemoryLog) store = new FasterKV - (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); + (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, + new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }, + supportsMutableIndexes: secondaryIndexType != SecondaryIndexType.None); else store = new FasterKV - (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, MemorySizeBits = 35 }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); + (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, MemorySizeBits = 35 }, + new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }, + supportsMutableIndexes: secondaryIndexType != SecondaryIndexType.None); + + if (secondaryIndexType.HasFlag(SecondaryIndexType.Key)) + store.SecondaryIndexBroker.AddIndex(new NullKeyIndex()); + if (secondaryIndexType.HasFlag(SecondaryIndexType.Value)) + store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); } private void RunYcsb(int thread_idx) @@ -236,7 +248,7 @@ private void RunYcsb(int thread_idx) Interlocked.Add(ref total_ops_done, reads_done + writes_done); } - public unsafe void Run() + public unsafe double Run() { //Native32.AffinitizeThreadShardedNuma(0, 2); @@ -374,11 +386,13 @@ public unsafe void Run() long endTailAddress = store.Log.TailAddress; Console.WriteLine("End tail address = " + endTailAddress); + double opsPerSecond = total_ops_done / seconds; Console.WriteLine("Total " + total_ops_done + " ops done " + " in " + seconds + " secs."); Console.WriteLine("##, " + distribution + ", " + numaStyle + ", " + readPercent + ", " - + threadCount + ", " + total_ops_done / seconds + ", " + + threadCount + ", " + opsPerSecond + ", " + (endTailAddress - startTailAddress)); device.Dispose(); + return opsPerSecond; } private void SetupYcsb(int thread_idx) diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index e603009e0..d88788908 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -79,11 +79,10 @@ public enum Op : ulong readonly string distribution; readonly int readPercent; readonly Functions functions; - readonly SecondaryIndexType secondaryIndexType = SecondaryIndexType.None; volatile bool done = false; - public FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distribution_, int readPercent_, int backupOptions_, int indexType_) + public FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distribution_, int readPercent_, int backupOptions_, int lockImpl_, int secondaryIndexType_) { // Pin loading thread if it is not used for checkpointing if (kPeriodicCheckpointMilliseconds <= 0) @@ -94,9 +93,9 @@ public FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distributio distribution = distribution_; readPercent = readPercent_; this.backupMode = (BackupMode)backupOptions_; - secondaryIndexType = (SecondaryIndexType)indexType_; - functions = new Functions(secondaryIndexType != SecondaryIndexType.None); - Console.WriteLine($"SupportsLocking: {functions.SupportsLocking}"); + var lockImpl = (LockImpl)lockImpl_; + var secondaryIndexType = (SecondaryIndexType)secondaryIndexType_; + functions = new Functions(lockImpl != LockImpl.None); #if DASHBOARD statsWritten = new AutoResetEvent[threadCount]; @@ -126,13 +125,10 @@ public FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distributio new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }, supportsMutableIndexes: secondaryIndexType != SecondaryIndexType.None); - if (functions.SupportsLocking) - { - if (secondaryIndexType.HasFlag(SecondaryIndexType.Key)) - store.SecondaryIndexBroker.AddIndex(new NullKeyIndex()); - if (secondaryIndexType.HasFlag(SecondaryIndexType.Value)) - store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); - } + if (secondaryIndexType.HasFlag(SecondaryIndexType.Key)) + store.SecondaryIndexBroker.AddIndex(new NullKeyIndex()); + if (secondaryIndexType.HasFlag(SecondaryIndexType.Value)) + store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); } private void RunYcsb(int thread_idx) @@ -248,7 +244,7 @@ private void RunYcsb(int thread_idx) Interlocked.Add(ref total_ops_done, reads_done + writes_done); } - public unsafe void Run() + public unsafe double Run() { RandomGenerator rng = new RandomGenerator(); @@ -385,11 +381,13 @@ public unsafe void Run() long endTailAddress = store.Log.TailAddress; Console.WriteLine("End tail address = " + endTailAddress); + double opsPerSecond = total_ops_done / seconds; Console.WriteLine("Total " + total_ops_done + " ops done " + " in " + seconds + " secs."); Console.WriteLine("##, " + distribution + ", " + numaStyle + ", " + readPercent + ", " - + threadCount + ", " + total_ops_done / seconds + ", " + + threadCount + ", " + opsPerSecond + ", " + (endTailAddress - startTailAddress)); device.Dispose(); + return opsPerSecond; } private void SetupYcsb(int thread_idx) diff --git a/cs/benchmark/Functions.cs b/cs/benchmark/Functions.cs index 4837e37b4..ad5951138 100644 --- a/cs/benchmark/Functions.cs +++ b/cs/benchmark/Functions.cs @@ -87,14 +87,12 @@ public void CopyUpdater(ref Key key, ref Input input, ref Value oldValue, ref Va public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) { - //if (locking) - // recordInfo.SpinLock(); + if (locking) recordInfo.SpinLock(); } public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) { - //if (locking) - // recordInfo.Unlock(); + if (locking) recordInfo.Unlock(); return true; } } diff --git a/cs/benchmark/FunctionsSB.cs b/cs/benchmark/FunctionsSB.cs index fb80c07cf..664e1638b 100644 --- a/cs/benchmark/FunctionsSB.cs +++ b/cs/benchmark/FunctionsSB.cs @@ -7,5 +7,6 @@ namespace FASTER.benchmark { public sealed class FunctionsSB : SpanByteFunctions { + public FunctionsSB(bool locking) : base(locking:locking) { } } } diff --git a/cs/benchmark/Program.cs b/cs/benchmark/Program.cs index 5fb00c044..8e36ee273 100644 --- a/cs/benchmark/Program.cs +++ b/cs/benchmark/Program.cs @@ -3,13 +3,20 @@ using CommandLine; using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; +using System.Threading; namespace FASTER.benchmark { class Options { [Option('b', "benchmark", Required = false, Default = 0, - HelpText = "Benchmark to run (0 = YCSB)")] + HelpText = "Benchmark to run:" + + "\n 0 = YCSB" + + "\n 1 = YCSB with SpanByte" + + "\n 2 = ConcurrentDictionary")] public int Benchmark { get; set; } [Option('t', "threads", Required = false, Default = 8, @@ -17,7 +24,9 @@ class Options public int ThreadCount { get; set; } [Option('n', "numa", Required = false, Default = 0, - HelpText = "0 = No sharding across NUMA sockets, 1 = Sharding across NUMA sockets")] + HelpText = "NUMA options:" + + "\n 0 = No sharding across NUMA sockets" + + "\n 1 = Sharding across NUMA sockets")] public int NumaStyle { get; set; } [Option('k', "backup", Required = false, Default = 0, @@ -25,10 +34,16 @@ class Options "\n 0 = None; Populate FasterKV from data" + "\n 1 = Recover FasterKV from Checkpoint; if this fails, populate FasterKV from data" + "\n 2 = Checkpoint FasterKV (unless it was Recovered by option 1; if option 1 is not specified, this will overwrite an existing Checkpoint)" + - "\n 3 = Both (Recover FasterKV if the Checkpoint is available, else populate FasterKV from data and Checkpoint it so it can be Restored in a subsequent run)")] + "\n 3 = Both (Recover FasterKV if a Checkpoint is available, else populate FasterKV from data and Checkpoint it so it can be Restored in a subsequent run)")] public int Backup { get; set; } - [Option('i', "index", Required = false, Default = 0, + [Option('l', "locking", Required = false, Default = 0, + HelpText = "Locking Implementation:" + + "\n 0 = None (default)" + + "\n 1 = RecordInfo.SpinLock()")] + public int LockImpl { get; set; } + + [Option('x', "index", Required = false, Default = 0, HelpText = "Secondary index type(s); these implement a no-op index to test the overhead on FasterKV operations:" + "\n 0 = None (default)" + "\n 1 = Key-based index" + @@ -36,6 +51,10 @@ class Options "\n 3 = Both index types")] public int SecondaryIndexType { get; set; } + [Option('i', "iterations", Required = false, Default = 1, + HelpText = "Number of iterations of the test to run")] + public int IterationCount { get; set; } + [Option('r', "read_percent", Required = false, Default = 50, HelpText = "Percentage of reads (-1 for 100% read-modify-write")] public int ReadPercent { get; set; } @@ -55,6 +74,11 @@ enum BenchmarkType : int None, Restore, Backup, Both }; + enum LockImpl : int + { + None, RecordInfo + }; + [Flags] enum SecondaryIndexType : int { @@ -72,22 +96,49 @@ public static void Main(string[] args) } var options = result.MapResult(o => o, xs => new Options()); - BenchmarkType b = (BenchmarkType)options.Benchmark; + var b = (BenchmarkType)options.Benchmark; + Console.WriteLine($"Scenario: {b}, Locking: {(LockImpl)options.LockImpl}, Indexing: {(SecondaryIndexType)options.SecondaryIndexType}"); - if (b == BenchmarkType.Ycsb) - { - var test = new FASTER_YcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent, options.Backup, options.SecondaryIndexType); - test.Run(); - } - else if (b == BenchmarkType.SpanByte) + var opsPerRun = new List(); + + for (var iter = 0; iter < options.IterationCount; ++iter) { - var test = new FasterSpanByteYcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent, options.Backup); - test.Run(); + if (options.IterationCount > 1) + { + Console.WriteLine(); + Console.WriteLine($"Iteration {iter + 1} of {options.IterationCount}"); + } + + if (b == BenchmarkType.Ycsb) + { + var test = new FASTER_YcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent, options.Backup, options.LockImpl, options.SecondaryIndexType); + opsPerRun.Add(test.Run()); + } + else if (b == BenchmarkType.SpanByte) + { + var test = new FasterSpanByteYcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent, options.Backup, options.LockImpl, options.SecondaryIndexType); + opsPerRun.Add(test.Run()); + } + else if (b == BenchmarkType.ConcurrentDictionaryYcsb) + { + var test = new ConcurrentDictionary_YcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent); + opsPerRun.Add(test.Run()); + } + + if (iter < options.IterationCount - 1) + { + GC.Collect(); + Thread.Sleep(1000); + } } - else if (b == BenchmarkType.ConcurrentDictionaryYcsb) + + if (options.IterationCount > 1) { - var test = new ConcurrentDictionary_YcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent); - test.Run(); + var meanOpsPerRun = opsPerRun.Sum() / options.IterationCount; + var stddev = Math.Sqrt(opsPerRun.Sum(n => Math.Pow(n - meanOpsPerRun, 2)) / options.IterationCount); + + Console.WriteLine(); + Console.WriteLine($"Average ops per second: {meanOpsPerRun:N3} (stddev: {stddev:N3})"); } } } diff --git a/cs/src/core/VarLen/MemoryFunctions.cs b/cs/src/core/VarLen/MemoryFunctions.cs index 449611aa4..bbfe70316 100644 --- a/cs/src/core/VarLen/MemoryFunctions.cs +++ b/cs/src/core/VarLen/MemoryFunctions.cs @@ -31,14 +31,11 @@ public override void SingleWriter(ref Key key, ref Memory src, ref Memory /// public override bool ConcurrentWriter(ref Key key, ref Memory src, ref Memory dst) { - if (locking) dst.SpinLock(); - // We can write the source (src) data to the existing destination (dst) in-place, // only if there is sufficient space if (dst.Length < src.Length || dst.IsMarkedReadOnly()) { dst.MarkReadOnly(); - if (locking) dst.Unlock(); return false; } @@ -49,8 +46,6 @@ public override bool ConcurrentWriter(ref Key key, ref Memory src, ref Memory // We can adjust the length header on the serialized log, if we wish to. // This method will also zero out the extra space to retain log scan correctness. dst.ShrinkSerializedLength(src.Length); - - if (locking) dst.Unlock(); return true; } @@ -65,11 +60,9 @@ public override void SingleReader(ref Key key, ref Memory input, ref Memory public override void ConcurrentReader(ref Key key, ref Memory input, ref Memory value, ref (IMemoryOwner, int) dst) { - if (locking) value.SpinLock(); dst.Item1 = memoryPool.Rent(value.Length); dst.Item2 = value.Length; value.CopyTo(dst.Item1.Memory); - if (locking) value.Unlock(); } /// @@ -90,5 +83,21 @@ public override bool InPlaceUpdater(ref Key key, ref Memory input, ref Memory // The default implementation of IPU simply writes input to destination, if there is space return ConcurrentWriter(ref key, ref input, ref value); } + + /// + public override bool SupportsLocking => locking; + + /// + public override void Lock(ref RecordInfo recordInfo, ref Key key, ref Memory value, LockType lockType, ref long context) + { + if (locking) value.SpinLock(); + } + + /// + public override bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Memory value, LockType lockType, long context) + { + if (locking) value.Unlock(); + return true; + } } } \ No newline at end of file diff --git a/cs/src/core/VarLen/SpanByteFunctions.cs b/cs/src/core/VarLen/SpanByteFunctions.cs index 82cc595bb..034653c32 100644 --- a/cs/src/core/VarLen/SpanByteFunctions.cs +++ b/cs/src/core/VarLen/SpanByteFunctions.cs @@ -94,9 +94,23 @@ public unsafe override void SingleReader(ref SpanByte key, ref SpanByte input, r /// public unsafe override void ConcurrentReader(ref SpanByte key, ref SpanByte input, ref SpanByte value, ref SpanByteAndMemory dst) { - if (locking) value.SpinLock(); value.CopyTo(ref dst, memoryPool); + } + + /// + public override bool SupportsLocking => locking; + + /// + public override void Lock(ref RecordInfo recordInfo, ref SpanByte key, ref SpanByte value, LockType lockType, ref long context) + { + if (locking) value.SpinLock(); + } + + /// + public override bool Unlock(ref RecordInfo recordInfo, ref SpanByte key, ref SpanByte value, LockType lockType, long context) + { if (locking) value.Unlock(); + return true; } } @@ -120,9 +134,23 @@ public override void SingleReader(ref SpanByte key, ref SpanByte input, ref Span /// public override void ConcurrentReader(ref SpanByte key, ref SpanByte input, ref SpanByte value, ref byte[] dst) { - value.SpinLock(); dst = value.ToByteArray(); - value.Unlock(); + } + + /// + public override bool SupportsLocking => locking; + + /// + public override void Lock(ref RecordInfo recordInfo, ref SpanByte key, ref SpanByte value, LockType lockType, ref long context) + { + if (locking) value.SpinLock(); + } + + /// + public override bool Unlock(ref RecordInfo recordInfo, ref SpanByte key, ref SpanByte value, LockType lockType, long context) + { + if (locking) value.Unlock(); + return true; } } } From 11c028959c6a434cf3e777625c50b70d952b48b0 Mon Sep 17 00:00:00 2001 From: Badrish Chandramouli Date: Thu, 25 Feb 2021 22:28:47 -0800 Subject: [PATCH 08/37] updates --- cs/benchmark/FasterYcsbBenchmark.cs | 13 ++++++++++++- cs/benchmark/Program.cs | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index d88788908..03d602063 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -131,6 +131,14 @@ public FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distributio store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); } + public void Dispose() + { + store.Dispose(); + device.Dispose(); + init_keys_ = null; + txn_keys_ = null; + } + private void RunYcsb(int thread_idx) { RandomGenerator rng = new RandomGenerator((uint)(1 + thread_idx)); @@ -386,7 +394,6 @@ public unsafe double Run() Console.WriteLine("##, " + distribution + ", " + numaStyle + ", " + readPercent + ", " + threadCount + ", " + opsPerSecond + ", " + (endTailAddress - startTailAddress)); - device.Dispose(); return opsPerSecond; } @@ -554,6 +561,8 @@ private unsafe void LoadDataFromFile(string filePath) { throw new InvalidDataException("Init file load fail!"); } + + chunk_handle.Free(); } Console.WriteLine("loaded " + kInitCount + " keys."); @@ -596,6 +605,8 @@ private unsafe void LoadDataFromFile(string filePath) { throw new InvalidDataException("Txn file load fail!" + count + ":" + kTxnCount); } + + chunk_handle.Free(); } Console.WriteLine("loaded " + kTxnCount + " txns."); diff --git a/cs/benchmark/Program.cs b/cs/benchmark/Program.cs index 8e36ee273..8b8e22f4d 100644 --- a/cs/benchmark/Program.cs +++ b/cs/benchmark/Program.cs @@ -113,6 +113,7 @@ public static void Main(string[] args) { var test = new FASTER_YcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent, options.Backup, options.LockImpl, options.SecondaryIndexType); opsPerRun.Add(test.Run()); + test.Dispose(); } else if (b == BenchmarkType.SpanByte) { @@ -128,6 +129,7 @@ public static void Main(string[] args) if (iter < options.IterationCount - 1) { GC.Collect(); + GC.WaitForFullGCComplete(); Thread.Sleep(1000); } } From ac128365dcbdb70d9e2c7bf4a18b7b5bee9c0e38 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Thu, 25 Feb 2021 22:29:44 -0800 Subject: [PATCH 09/37] Improvments to multi-iteration runs --- cs/benchmark/ConcurrentDictionaryBenchmark.cs | 7 ++-- cs/benchmark/FasterSpanByteYcsbBenchmark.cs | 8 ++-- cs/benchmark/FasterYcsbBenchmark.cs | 9 ++-- cs/benchmark/Program.cs | 41 +++++++++++++++---- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/cs/benchmark/ConcurrentDictionaryBenchmark.cs b/cs/benchmark/ConcurrentDictionaryBenchmark.cs index fd0afd585..40ed6a2b5 100644 --- a/cs/benchmark/ConcurrentDictionaryBenchmark.cs +++ b/cs/benchmark/ConcurrentDictionaryBenchmark.cs @@ -194,7 +194,7 @@ private void RunYcsb(int thread_idx) Interlocked.Add(ref total_ops_done, reads_done + writes_done); } - public unsafe double Run() + public unsafe (double, double) Run() { RandomGenerator rng = new RandomGenerator(); @@ -236,7 +236,8 @@ public unsafe double Run() } sw.Stop(); - Console.WriteLine("Loading time: {0}ms", sw.ElapsedMilliseconds); + double initsPerSecond = ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; + Console.WriteLine($"Loading time: {sw.ElapsedMilliseconds}ms ({initsPerSecond:N3} inserts per sec)"); idx_ = 0; @@ -290,7 +291,7 @@ public unsafe double Run() Console.WriteLine("Total " + total_ops_done + " ops done " + " in " + seconds + " secs."); Console.WriteLine("##, " + distribution + ", " + numaStyle + ", " + readPercent + ", " + threadCount + ", " + opsPerSecond); - return opsPerSecond; + return (initsPerSecond, opsPerSecond); } private void SetupYcsb(int thread_idx) diff --git a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs index d30b31686..20ab74842 100644 --- a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs +++ b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs @@ -248,7 +248,7 @@ private void RunYcsb(int thread_idx) Interlocked.Add(ref total_ops_done, reads_done + writes_done); } - public unsafe double Run() + public unsafe (double, double) Run() { //Native32.AffinitizeThreadShardedNuma(0, 2); @@ -307,7 +307,8 @@ public unsafe double Run() } sw.Stop(); } - Console.WriteLine("Loading time: {0}ms", sw.ElapsedMilliseconds); + double initsPerSecond = storeWasRecovered ? 0 : ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; + Console.WriteLine($"Loading time: {sw.ElapsedMilliseconds}ms ({initsPerSecond:N3} inserts per sec)"); long startTailAddress = store.Log.TailAddress; Console.WriteLine("Start tail address = " + startTailAddress); @@ -391,8 +392,9 @@ public unsafe double Run() Console.WriteLine("##, " + distribution + ", " + numaStyle + ", " + readPercent + ", " + threadCount + ", " + opsPerSecond + ", " + (endTailAddress - startTailAddress)); + store.Dispose(); device.Dispose(); - return opsPerSecond; + return (initsPerSecond, opsPerSecond); } private void SetupYcsb(int thread_idx) diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index d88788908..5c9453a32 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -30,7 +30,7 @@ public enum Op : ulong const bool kUseSyntheticData = true; const bool kSmallMemoryLog = false; const bool kAffinitizedSession = true; - const int kRunSeconds = 30; + const int kRunSeconds = 3; const int kPeriodicCheckpointMilliseconds = 0; #else const bool kDumpDistribution = false; @@ -244,7 +244,7 @@ private void RunYcsb(int thread_idx) Interlocked.Add(ref total_ops_done, reads_done + writes_done); } - public unsafe double Run() + public unsafe (double, double) Run() { RandomGenerator rng = new RandomGenerator(); @@ -302,7 +302,8 @@ public unsafe double Run() } sw.Stop(); } - Console.WriteLine("Loading time: {0}ms", sw.ElapsedMilliseconds); + double initsPerSecond = storeWasRecovered ? 0 : ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; + Console.WriteLine($"Loading time: {sw.ElapsedMilliseconds}ms ({initsPerSecond:N3} inserts per sec)"); long startTailAddress = store.Log.TailAddress; Console.WriteLine("Start tail address = " + startTailAddress); @@ -387,7 +388,7 @@ public unsafe double Run() + threadCount + ", " + opsPerSecond + ", " + (endTailAddress - startTailAddress)); device.Dispose(); - return opsPerSecond; + return (initsPerSecond, opsPerSecond); } private void SetupYcsb(int thread_idx) diff --git a/cs/benchmark/Program.cs b/cs/benchmark/Program.cs index 8e36ee273..b2e4e2660 100644 --- a/cs/benchmark/Program.cs +++ b/cs/benchmark/Program.cs @@ -99,8 +99,15 @@ public static void Main(string[] args) var b = (BenchmarkType)options.Benchmark; Console.WriteLine($"Scenario: {b}, Locking: {(LockImpl)options.LockImpl}, Indexing: {(SecondaryIndexType)options.SecondaryIndexType}"); + var initsPerRun = new List(); var opsPerRun = new List(); + void addResult((double ips, double ops) result) + { + initsPerRun.Add(result.ips); + opsPerRun.Add(result.ops); + } + for (var iter = 0; iter < options.IterationCount; ++iter) { if (options.IterationCount > 1) @@ -112,33 +119,53 @@ public static void Main(string[] args) if (b == BenchmarkType.Ycsb) { var test = new FASTER_YcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent, options.Backup, options.LockImpl, options.SecondaryIndexType); - opsPerRun.Add(test.Run()); + addResult(test.Run()); } else if (b == BenchmarkType.SpanByte) { var test = new FasterSpanByteYcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent, options.Backup, options.LockImpl, options.SecondaryIndexType); - opsPerRun.Add(test.Run()); + addResult(test.Run()); } else if (b == BenchmarkType.ConcurrentDictionaryYcsb) { var test = new ConcurrentDictionary_YcsbBenchmark(options.ThreadCount, options.NumaStyle, options.Distribution, options.ReadPercent); - opsPerRun.Add(test.Run()); + addResult(test.Run()); } if (iter < options.IterationCount - 1) { GC.Collect(); - Thread.Sleep(1000); + GC.WaitForFullGCComplete(); } } if (options.IterationCount > 1) { - var meanOpsPerRun = opsPerRun.Sum() / options.IterationCount; - var stddev = Math.Sqrt(opsPerRun.Sum(n => Math.Pow(n - meanOpsPerRun, 2)) / options.IterationCount); + if (options.IterationCount >= 5) + { + static void discardHiLo(List vec) + { + vec.Sort(); + vec[0] = vec[vec.Count - 2]; // overwrite lowest with second-highest + vec.RemoveRange(vec.Count - 2, 2); // remove highest and (now-duplicated) second-highest + } + discardHiLo(initsPerRun); + discardHiLo(opsPerRun); + } Console.WriteLine(); - Console.WriteLine($"Average ops per second: {meanOpsPerRun:N3} (stddev: {stddev:N3})"); + var discardMessage = initsPerRun.Count < options.IterationCount ? " (high and low results discarded)" : string.Empty; + Console.WriteLine($"Averages per second{discardMessage}:"); + static void showStats(string tag, List vec) + { + var mean = vec.Sum() / vec.Count; + var stddev = Math.Sqrt(vec.Sum(n => Math.Pow(n - mean, 2)) / vec.Count); + var stddevpct = (stddev / mean) * 100; + Console.WriteLine($" {tag} per second: {mean:N3} (stddev: {stddev:N1}; {stddevpct:N1}%)"); + } + + showStats("Load Inserts", initsPerRun); + showStats("Transactions", opsPerRun); } } } From e340da76c3cb796f838dbe7bbb360e44dc58c082 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Fri, 26 Feb 2021 23:50:48 -0800 Subject: [PATCH 10/37] Benchmark: remove interface and make store, device, keys readonly --- cs/benchmark/ConcurrentDictionaryBenchmark.cs | 54 +++++----- cs/benchmark/FasterSpanByteYcsbBenchmark.cs | 62 +++++------- cs/benchmark/FasterYcsbBenchmark.cs | 59 +++++------ cs/benchmark/IBenchmarkTest.cs | 14 --- cs/benchmark/Program.cs | 98 ++++++++++++------- cs/src/core/ClientSession/FASTERAsync.cs | 3 +- cs/src/core/Index/FASTER/FASTER.cs | 7 +- cs/src/core/Index/FASTER/FASTERImpl.cs | 6 +- 8 files changed, 150 insertions(+), 153 deletions(-) delete mode 100644 cs/benchmark/IBenchmarkTest.cs diff --git a/cs/benchmark/ConcurrentDictionaryBenchmark.cs b/cs/benchmark/ConcurrentDictionaryBenchmark.cs index b814666cc..338fd8943 100644 --- a/cs/benchmark/ConcurrentDictionaryBenchmark.cs +++ b/cs/benchmark/ConcurrentDictionaryBenchmark.cs @@ -33,7 +33,7 @@ public int GetHashCode(Key obj) } } - public unsafe class ConcurrentDictionary_YcsbBenchmark : IBenchmarkTest + public unsafe class ConcurrentDictionary_YcsbBenchmark { public enum Op : ulong { @@ -60,18 +60,20 @@ public enum Op : ulong readonly int readPercent; readonly Input[] input_; - Key[] init_keys_; - Key[] txn_keys_; + readonly Key[] init_keys_; + readonly Key[] txn_keys_; - ConcurrentDictionary store; + readonly ConcurrentDictionary store; long idx_ = 0; long total_ops_done = 0; volatile bool done = false; Input* input_ptr; - public ConcurrentDictionary_YcsbBenchmark(int threadCount_, int numaStyle_, string distribution_, int readPercent_) + public ConcurrentDictionary_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, int threadCount_, int numaStyle_, string distribution_, int readPercent_) { + init_keys_ = i_keys_; + txn_keys_ = t_keys_; threadCount = threadCount_; numaStyle = numaStyle_; distribution = distribution_; @@ -93,22 +95,13 @@ public ConcurrentDictionary_YcsbBenchmark(int threadCount_, int numaStyle_, stri input_ = new Input[8]; for (int i = 0; i < 8; i++) input_[i].value = i; - } - public void CreateStore() - { store = new ConcurrentDictionary(threadCount, kMaxKey, new KeyComparer()); } - public void DisposeStore() + public void Dispose() { store.Clear(); - store = null; - - idx_ = 0; - total_ops_done = 0; - done = false; - input_ptr = null; } private void RunYcsb(int thread_idx) @@ -418,7 +411,7 @@ void DoContinuousMeasurements() #region Load Data - private void LoadDataFromFile(string filePath) + private static void LoadDataFromFile(string filePath, string distribution, out Key[] i_keys, out Key[] t_keys) { string init_filename = filePath + "/load_" + distribution + "_250M_raw.dat"; string txn_filename = filePath + "/run_" + distribution + "_250M_1000M_raw.dat"; @@ -428,7 +421,7 @@ private void LoadDataFromFile(string filePath) FileShare.Read)) { Console.WriteLine("loading keys from " + init_filename + " into memory..."); - init_keys_ = new Key[kInitCount]; + i_keys = new Key[kInitCount]; byte[] chunk = new byte[kFileChunkSize]; GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); @@ -442,7 +435,7 @@ private void LoadDataFromFile(string filePath) int size = stream.Read(chunk, 0, kFileChunkSize); for (int idx = 0; idx < size; idx += 8) { - init_keys_[count].value = *(long*)(chunk_ptr + idx); + i_keys[count].value = *(long*)(chunk_ptr + idx); ++count; } if (size == kFileChunkSize) @@ -471,7 +464,7 @@ private void LoadDataFromFile(string filePath) Console.WriteLine("loading txns from " + txn_filename + " into memory..."); - txn_keys_ = new Key[kTxnCount]; + t_keys = new Key[kTxnCount]; count = 0; long offset = 0; @@ -482,7 +475,7 @@ private void LoadDataFromFile(string filePath) int size = stream.Read(chunk, 0, kFileChunkSize); for (int idx = 0; idx < size; idx += 8) { - txn_keys_[count] = *((Key*)(chunk_ptr + idx)); + t_keys[count] = *((Key*)(chunk_ptr + idx)); ++count; } if (size == kFileChunkSize) @@ -503,11 +496,11 @@ private void LoadDataFromFile(string filePath) Console.WriteLine("loaded " + kTxnCount + " txns."); } - public void LoadData() + public static void LoadData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) { if (kUseSyntheticData) { - LoadSyntheticData(); + LoadSyntheticData(distribution, seed, out i_keys, out t_keys); return; } @@ -524,35 +517,36 @@ public void LoadData() if (Directory.Exists(filePath)) { - LoadDataFromFile(filePath); + LoadDataFromFile(filePath, distribution, out i_keys, out t_keys); + return; } else { Console.WriteLine("WARNING: Could not find YCSB directory, loading synthetic data instead"); - LoadSyntheticData(); + LoadSyntheticData(distribution, seed, out i_keys, out t_keys); } } - private void LoadSyntheticData() + private static void LoadSyntheticData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) { Console.WriteLine("Loading synthetic data (uniform distribution)"); - init_keys_ = new Key[kInitCount]; + i_keys = new Key[kInitCount]; long val = 0; for (int idx = 0; idx < kInitCount; idx++) { - init_keys_[idx] = new Key { value = val++ }; + i_keys[idx] = new Key { value = val++ }; } Console.WriteLine("loaded " + kInitCount + " keys."); - RandomGenerator generator = new RandomGenerator(); + RandomGenerator generator = new RandomGenerator(seed); - txn_keys_ = new Key[kTxnCount]; + t_keys = new Key[kTxnCount]; for (int idx = 0; idx < kTxnCount; idx++) { - txn_keys_[idx] = new Key { value = (long)generator.Generate64(kInitCount) }; + t_keys[idx] = new Key { value = (long)generator.Generate64(kInitCount) }; } Console.WriteLine("loaded " + kTxnCount + " txns."); diff --git a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs index a29a8e05d..3b3debb52 100644 --- a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs +++ b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs @@ -15,7 +15,7 @@ namespace FASTER.benchmark { - public class FasterSpanByteYcsbBenchmark : IBenchmarkTest + public class FasterSpanByteYcsbBenchmark { public enum Op : ulong { @@ -70,21 +70,23 @@ public enum Op : ulong readonly int readPercent; readonly FunctionsSB functions; readonly SecondaryIndexType secondaryIndexType = SecondaryIndexType.None; - readonly uint distributionSeed; readonly Input[] input_; - KeySpanByte[] init_keys_; - KeySpanByte[] txn_keys_; + readonly KeySpanByte[] init_keys_; + readonly KeySpanByte[] txn_keys_; - IDevice device; - FasterKV store; + readonly IDevice device; + readonly FasterKV store; long idx_ = 0; long total_ops_done = 0; volatile bool done = false; - internal FasterSpanByteYcsbBenchmark(int threadCount_, int numaStyle_, string distribution_, int readPercent_, BackupMode backupMode_, LockImpl lockImpl_, SecondaryIndexType secondaryIndexType_, int seed_) + internal FasterSpanByteYcsbBenchmark(KeySpanByte[] i_keys_, KeySpanByte[] t_keys_, int threadCount_, int numaStyle_, string distribution_, int readPercent_, + BackupMode backupMode_, LockImpl lockImpl_, SecondaryIndexType secondaryIndexType_) { + init_keys_ = i_keys_; + txn_keys_ = t_keys_; threadCount = threadCount_; numaStyle = numaStyle_; distribution = distribution_; @@ -93,7 +95,6 @@ internal FasterSpanByteYcsbBenchmark(int threadCount_, int numaStyle_, string di var lockImpl = lockImpl_; functions = new FunctionsSB(lockImpl != LockImpl.None); secondaryIndexType = secondaryIndexType_; - distributionSeed = (uint)seed_; #if DASHBOARD statsWritten = new AutoResetEvent[threadCount]; @@ -111,10 +112,7 @@ internal FasterSpanByteYcsbBenchmark(int threadCount_, int numaStyle_, string di input_ = new Input[8]; for (int i = 0; i < 8; i++) input_[i].value = i; - } - public void CreateStore() - { var path = "D:\\data\\FasterYcsbBenchmark\\"; device = Devices.CreateLogDevice(path + "hlog", preallocateFile: true); @@ -133,16 +131,10 @@ public void CreateStore() store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); } - public void DisposeStore() + public void Dispose() { store.Dispose(); - store = null; device.Dispose(); - device = null; - - idx_ = 0; - total_ops_done = 0; - done = false; } private void RunYcsb(int thread_idx) @@ -524,7 +516,7 @@ void DoContinuousMeasurements() #region Load Data - private unsafe void LoadDataFromFile(string filePath) + private static unsafe void LoadDataFromFile(string filePath, string distribution, out KeySpanByte[] i_keys, out KeySpanByte[] t_keys) { string init_filename = filePath + "/load_" + distribution + "_250M_raw.dat"; string txn_filename = filePath + "/run_" + distribution + "_250M_1000M_raw.dat"; @@ -534,7 +526,7 @@ private unsafe void LoadDataFromFile(string filePath) FileShare.Read)) { Console.WriteLine("loading keys from " + init_filename + " into memory..."); - init_keys_ = new KeySpanByte[kInitCount]; + i_keys = new KeySpanByte[kInitCount]; byte[] chunk = new byte[kFileChunkSize]; GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); @@ -548,8 +540,8 @@ private unsafe void LoadDataFromFile(string filePath) int size = stream.Read(chunk, 0, kFileChunkSize); for (int idx = 0; idx < size; idx += 8) { - init_keys_[count].length = kKeySize - 4; - init_keys_[count].value = *(long*)(chunk_ptr + idx); + i_keys[count].length = kKeySize - 4; + i_keys[count].value = *(long*)(chunk_ptr + idx); ++count; if (count == kInitCount) break; @@ -582,7 +574,7 @@ private unsafe void LoadDataFromFile(string filePath) Console.WriteLine("loading txns from " + txn_filename + " into memory..."); - txn_keys_ = new KeySpanByte[kTxnCount]; + t_keys = new KeySpanByte[kTxnCount]; count = 0; long offset = 0; @@ -593,8 +585,8 @@ private unsafe void LoadDataFromFile(string filePath) int size = stream.Read(chunk, 0, kFileChunkSize); for (int idx = 0; idx < size; idx += 8) { - txn_keys_[count].length = kKeySize - 4; - txn_keys_[count].value = *(long*)(chunk_ptr + idx); + t_keys[count].length = kKeySize - 4; + t_keys[count].value = *(long*)(chunk_ptr + idx); ++count; if (count == kTxnCount) break; @@ -619,11 +611,11 @@ private unsafe void LoadDataFromFile(string filePath) Console.WriteLine("loaded " + kTxnCount + " txns."); } - public void LoadData() + public static void LoadData(string distribution, uint seed, out KeySpanByte[] i_keys, out KeySpanByte[] t_keys) { if (kUseSyntheticData) { - LoadSyntheticData(); + LoadSyntheticData(distribution, seed, out i_keys, out t_keys); return; } @@ -640,35 +632,35 @@ public void LoadData() if (Directory.Exists(filePath)) { - LoadDataFromFile(filePath); + LoadDataFromFile(filePath, distribution, out i_keys, out t_keys); } else { Console.WriteLine("WARNING: Could not find YCSB directory, loading synthetic data instead"); - LoadSyntheticData(); + LoadSyntheticData(distribution, seed, out i_keys, out t_keys); } } - private void LoadSyntheticData() + private static void LoadSyntheticData(string distribution, uint seed, out KeySpanByte[] i_keys, out KeySpanByte[] t_keys) { Console.WriteLine("Loading synthetic data (uniform distribution)"); - init_keys_ = new KeySpanByte[kInitCount]; + i_keys = new KeySpanByte[kInitCount]; long val = 0; for (int idx = 0; idx < kInitCount; idx++) { - init_keys_[idx] = new KeySpanByte { length = kKeySize - 4, value = val++ }; + i_keys[idx] = new KeySpanByte { length = kKeySize - 4, value = val++ }; } Console.WriteLine("loaded " + kInitCount + " keys."); - RandomGenerator generator = new RandomGenerator(distributionSeed); + RandomGenerator generator = new RandomGenerator(seed); - txn_keys_ = new KeySpanByte[kTxnCount]; + t_keys = new KeySpanByte[kTxnCount]; for (int idx = 0; idx < kTxnCount; idx++) { - txn_keys_[idx] = new KeySpanByte { length = kKeySize - 4, value = (long)generator.Generate64(kInitCount) }; + t_keys[idx] = new KeySpanByte { length = kKeySize - 4, value = (long)generator.Generate64(kInitCount) }; } Console.WriteLine("loaded " + kTxnCount + " txns."); diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index 75ed2d1b6..a6051c685 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -15,7 +15,7 @@ namespace FASTER.benchmark { - internal class FASTER_YcsbBenchmark : IBenchmarkTest + internal class FASTER_YcsbBenchmark { public enum Op : ulong { @@ -67,25 +67,27 @@ public enum Op : ulong readonly int readPercent; readonly Functions functions; readonly SecondaryIndexType secondaryIndexType = SecondaryIndexType.None; - readonly uint distributionSeed; readonly Input[] input_; - Key[] init_keys_; - Key[] txn_keys_; + readonly Key[] init_keys_; + readonly Key[] txn_keys_; - IDevice device; - FasterKV store; + readonly IDevice device; + readonly FasterKV store; long idx_ = 0; long total_ops_done = 0; volatile bool done = false; - internal FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distribution_, int readPercent_, BackupMode backupMode_, LockImpl lockImpl_, SecondaryIndexType secondaryIndexType_, int seed_) + internal FASTER_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, int threadCount_, int numaStyle_, string distribution_, int readPercent_, + BackupMode backupMode_, LockImpl lockImpl_, SecondaryIndexType secondaryIndexType_) { // Pin loading thread if it is not used for checkpointing if (kPeriodicCheckpointMilliseconds <= 0) Native32.AffinitizeThreadShardedNuma(0, 2); + init_keys_ = i_keys_; + txn_keys_ = t_keys_; threadCount = threadCount_; numaStyle = numaStyle_; distribution = distribution_; @@ -94,7 +96,6 @@ internal FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distribut var lockImpl = lockImpl_; functions = new Functions(lockImpl != LockImpl.None); secondaryIndexType = secondaryIndexType_; - distributionSeed = (uint)seed_; #if DASHBOARD statsWritten = new AutoResetEvent[threadCount]; @@ -109,14 +110,10 @@ internal FASTER_YcsbBenchmark(int threadCount_, int numaStyle_, string distribut writeStats = new bool[threadCount]; freq = Stopwatch.Frequency; #endif - input_ = new Input[8]; for (int i = 0; i < 8; i++) input_[i].value = i; - } - public void CreateStore() - { var path = "D:\\data\\FasterYcsbBenchmark\\"; device = Devices.CreateLogDevice(path + "hlog", preallocateFile: true); @@ -135,16 +132,10 @@ public void CreateStore() store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); } - public void DisposeStore() + public void Dispose() { store.Dispose(); - store = null; device.Dispose(); - device = null; - - idx_ = 0; - total_ops_done = 0; - done = false; } private void RunYcsb(int thread_idx) @@ -520,7 +511,7 @@ void DoContinuousMeasurements() #region Load Data - private unsafe void LoadDataFromFile(string filePath) + private static unsafe void LoadDataFromFile(string filePath, string distribution, out Key[] i_keys, out Key[] t_keys) { string init_filename = filePath + "/load_" + distribution + "_250M_raw.dat"; string txn_filename = filePath + "/run_" + distribution + "_250M_1000M_raw.dat"; @@ -530,7 +521,7 @@ private unsafe void LoadDataFromFile(string filePath) FileShare.Read)) { Console.WriteLine("loading keys from " + init_filename + " into memory..."); - init_keys_ = new Key[kInitCount]; + i_keys = new Key[kInitCount]; byte[] chunk = new byte[kFileChunkSize]; GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); @@ -544,7 +535,7 @@ private unsafe void LoadDataFromFile(string filePath) int size = stream.Read(chunk, 0, kFileChunkSize); for (int idx = 0; idx < size; idx += 8) { - init_keys_[count].value = *(long*)(chunk_ptr + idx); + i_keys[count].value = *(long*)(chunk_ptr + idx); ++count; if (count == kInitCount) break; @@ -577,7 +568,7 @@ private unsafe void LoadDataFromFile(string filePath) Console.WriteLine("loading txns from " + txn_filename + " into memory..."); - txn_keys_ = new Key[kTxnCount]; + t_keys = new Key[kTxnCount]; count = 0; long offset = 0; @@ -588,7 +579,7 @@ private unsafe void LoadDataFromFile(string filePath) int size = stream.Read(chunk, 0, kFileChunkSize); for (int idx = 0; idx < size; idx += 8) { - txn_keys_[count].value = *(long*)(chunk_ptr + idx); + t_keys[count].value = *(long*)(chunk_ptr + idx); ++count; if (count == kTxnCount) break; @@ -613,11 +604,11 @@ private unsafe void LoadDataFromFile(string filePath) Console.WriteLine("loaded " + kTxnCount + " txns."); } - public void LoadData() + public static void LoadData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) { if (kUseSyntheticData) { - LoadSyntheticData(); + LoadSyntheticData(distribution, seed, out i_keys, out t_keys); return; } @@ -634,35 +625,35 @@ public void LoadData() if (Directory.Exists(filePath)) { - LoadDataFromFile(filePath); + LoadDataFromFile(filePath, distribution, out i_keys, out t_keys); } else { Console.WriteLine("WARNING: Could not find YCSB directory, loading synthetic data instead"); - LoadSyntheticData(); + LoadSyntheticData(distribution, seed, out i_keys, out t_keys); } } - private void LoadSyntheticData() + private static void LoadSyntheticData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) { Console.WriteLine("Loading synthetic data (uniform distribution)"); - init_keys_ = new Key[kInitCount]; + i_keys = new Key[kInitCount]; long val = 0; for (int idx = 0; idx < kInitCount; idx++) { - init_keys_[idx] = new Key { value = val++ }; + i_keys[idx] = new Key { value = val++ }; } Console.WriteLine("loaded " + kInitCount + " keys."); - RandomGenerator generator = new RandomGenerator(distributionSeed); + RandomGenerator generator = new RandomGenerator(seed); - txn_keys_ = new Key[kTxnCount]; + t_keys = new Key[kTxnCount]; for (int idx = 0; idx < kTxnCount; idx++) { - txn_keys_[idx] = new Key { value = (long)generator.Generate64(kInitCount) }; + t_keys[idx] = new Key { value = (long)generator.Generate64(kInitCount) }; } Console.WriteLine("loaded " + kTxnCount + " txns."); diff --git a/cs/benchmark/IBenchmarkTest.cs b/cs/benchmark/IBenchmarkTest.cs deleted file mode 100644 index 24cc1e0d9..000000000 --- a/cs/benchmark/IBenchmarkTest.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace FASTER.benchmark -{ - internal interface IBenchmarkTest - { - void LoadData(); - public void CreateStore(); - (double, double) Run(); - void DisposeStore(); - } -} diff --git a/cs/benchmark/Program.cs b/cs/benchmark/Program.cs index 1e089e38b..30df7318c 100644 --- a/cs/benchmark/Program.cs +++ b/cs/benchmark/Program.cs @@ -91,7 +91,7 @@ enum SecondaryIndexType : int public class Program { - const int kTrimResultCount = int.MaxValue; // Use some high value like int.MaxValue to bypass + const int kTrimResultCount = 3;// int.MaxValue; // Use some high value like int.MaxValue to bypass public static void Main(string[] args) { @@ -134,14 +134,40 @@ void addResult((double ips, double ops) result) opsPerRun.Add(result.ops); } - IBenchmarkTest test = b switch + Key[] init_keys_ = default; + Key[] txn_keys_ = default; + KeySpanByte[] init_span_keys_ = default; + KeySpanByte[] txn_span_keys_ = default; + + switch (b) + { + case BenchmarkType.Ycsb: + FASTER_YcsbBenchmark.LoadData(distribution, (uint)options.RandomSeed, out init_keys_, out txn_keys_); + break; + case BenchmarkType.SpanByte: + FasterSpanByteYcsbBenchmark.LoadData(distribution, (uint)options.RandomSeed, out init_span_keys_, out txn_span_keys_); + break; + case BenchmarkType.ConcurrentDictionaryYcsb: + ConcurrentDictionary_YcsbBenchmark.LoadData(distribution, (uint)options.RandomSeed, out init_keys_, out txn_keys_); + break; + default: + throw new ApplicationException("Unknown benchmark type"); + } + + static void showStats(string tag, List vec, string discardMessage = "") + { + var mean = vec.Sum() / vec.Count; + var stddev = Math.Sqrt(vec.Sum(n => Math.Pow(n - mean, 2)) / vec.Count); + var stddevpct = (stddev / mean) * 100; + Console.WriteLine($"###; {tag}; {mean:N3}; sd; {stddev:N1}; {stddevpct:N1}%"); + } + + void showAllStats(string discardMessage = "") { - BenchmarkType.Ycsb => new FASTER_YcsbBenchmark(options.ThreadCount, options.NumaStyle, distribution, options.ReadPercent, backupMode, lockImpl, secondaryIndexType, options.RandomSeed), - BenchmarkType.SpanByte => new FasterSpanByteYcsbBenchmark(options.ThreadCount, options.NumaStyle, distribution, options.ReadPercent, backupMode, lockImpl, secondaryIndexType, options.RandomSeed), - BenchmarkType.ConcurrentDictionaryYcsb => new ConcurrentDictionary_YcsbBenchmark(options.ThreadCount, options.NumaStyle, distribution, options.ReadPercent), - _ => throw new ApplicationException("Unknown benchmark type") - }; - test.LoadData(); + Console.WriteLine($"Averages per second over {initsPerRun.Count} iteration(s){discardMessage}:"); + showStats("ins/sec", initsPerRun); + showStats("ops/sec", opsPerRun); + } for (var iter = 0; iter < options.IterationCount; ++iter) { @@ -151,9 +177,29 @@ void addResult((double ips, double ops) result) Console.WriteLine($"Iteration {iter + 1} of {options.IterationCount}"); } - test.CreateStore(); - addResult(test.Run()); - test.DisposeStore(); + switch (b) + { + case BenchmarkType.Ycsb: + var yTest = new FASTER_YcsbBenchmark(init_keys_, txn_keys_, options.ThreadCount, options.NumaStyle, distribution, options.ReadPercent, backupMode, lockImpl, secondaryIndexType); + addResult(yTest.Run()); + yTest.Dispose(); + break; + case BenchmarkType.SpanByte: + var sTest = new FasterSpanByteYcsbBenchmark(init_span_keys_, txn_span_keys_, options.ThreadCount, options.NumaStyle, distribution, options.ReadPercent, backupMode, lockImpl, secondaryIndexType); + addResult(sTest.Run()); + sTest.Dispose(); + break; + case BenchmarkType.ConcurrentDictionaryYcsb: + var cTest = new ConcurrentDictionary_YcsbBenchmark(init_keys_, txn_keys_, options.ThreadCount, options.NumaStyle, distribution, options.ReadPercent); + addResult(cTest.Run()); + cTest.Dispose(); + break; + default: + throw new ApplicationException("Unknown benchmark type"); + } + + if (options.IterationCount > 1) + showAllStats(); if (iter < options.IterationCount - 1) { @@ -163,35 +209,21 @@ void addResult((double ips, double ops) result) } } - if (options.IterationCount > 1) + if (options.IterationCount >= kTrimResultCount) { - if (options.IterationCount >= kTrimResultCount) + static void discardHiLo(List vec) { - static void discardHiLo(List vec) - { - vec.Sort(); + vec.Sort(); #pragma warning disable IDE0056 // Use index operator (^ is not supported on .NET Framework or NETCORE pre-3.0) - vec[0] = vec[vec.Count - 2]; // overwrite lowest with second-highest + vec[0] = vec[vec.Count - 2]; // overwrite lowest with second-highest #pragma warning restore IDE0056 // Use index operator - vec.RemoveRange(vec.Count - 2, 2); // remove highest and (now-duplicated) second-highest - } - discardHiLo(initsPerRun); - discardHiLo(opsPerRun); + vec.RemoveRange(vec.Count - 2, 2); // remove highest and (now-duplicated) second-highest } + discardHiLo(initsPerRun); + discardHiLo(opsPerRun); Console.WriteLine(); - var discardMessage = initsPerRun.Count < options.IterationCount ? $" ({options.IterationCount} iterations with high and low discarded)" : string.Empty; - Console.WriteLine($"Averages per second over {initsPerRun.Count} run(s){discardMessage}:"); - static void showStats(string tag, List vec) - { - var mean = vec.Count == 1 ? 0.0 : vec.Sum() / vec.Count; - var stddev = vec.Count == 1 ? 0.0 : Math.Sqrt(vec.Sum(n => Math.Pow(n - mean, 2)) / vec.Count); - var stddevpct = (stddev / mean) * 100; - Console.WriteLine($"###; {tag}; {mean:N3}; sd; {stddev:N1}; {stddevpct:N1}%"); - } - - showStats("ins/sec", initsPerRun); - showStats("ops/sec", opsPerRun); + showAllStats($" ({options.IterationCount} iterations specified, with high and low discarded)"); } } } diff --git a/cs/src/core/ClientSession/FASTERAsync.cs b/cs/src/core/ClientSession/FASTERAsync.cs index fb810c9b9..02a87cc48 100644 --- a/cs/src/core/ClientSession/FASTERAsync.cs +++ b/cs/src/core/ClientSession/FASTERAsync.cs @@ -460,8 +460,9 @@ internal ValueTask> RmwAsync(ref Key key internal bool UpdateSIForIPU(ref Value value, long address) { // KeyIndexes do not need notification of in-place updates because the key does not change. - if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Upsert(ref value, address); - return true; + if (this.SecondaryIndexBroker.MutableValueIndexCount == 0) + return true; + this.SecondaryIndexBroker.Upsert(ref value, address); + return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index 47d35c0d1..6ce9355f4 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -467,7 +467,7 @@ internal OperationStatus InternalUpsert( if (foundEntry.word == entry.word) { pendingContext.logicalAddress = newLogicalAddress; - pendingContext.IsNewRecord = true; + pendingContext.IsNewRecord = this.SecondaryIndexBroker.HasMutableIndexes; status = OperationStatus.SUCCESS; goto LatchRelease; } @@ -821,7 +821,7 @@ ref hlog.GetValue(physicalAddress), if (foundEntry.word == entry.word) { pendingContext.logicalAddress = newLogicalAddress; - pendingContext.IsNewRecord = true; + pendingContext.IsNewRecord = this.SecondaryIndexBroker.HasMutableIndexes; goto LatchRelease; } else @@ -1089,7 +1089,7 @@ internal OperationStatus InternalDelete( if (foundEntry.word == entry.word) { pendingContext.logicalAddress = newLogicalAddress; - pendingContext.IsNewRecord = true; + pendingContext.IsNewRecord = this.SecondaryIndexBroker.HasMutableIndexes; status = OperationStatus.SUCCESS; goto LatchRelease; } From a094f81cba22606cd920ffa91e082458d90e2b99 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Sat, 27 Feb 2021 10:08:48 -0800 Subject: [PATCH 11/37] distinct delimiter on last and trimmed stats summary --- cs/benchmark/Program.cs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/cs/benchmark/Program.cs b/cs/benchmark/Program.cs index 30df7318c..9a5c85c5d 100644 --- a/cs/benchmark/Program.cs +++ b/cs/benchmark/Program.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http.Headers; using System.Threading; namespace FASTER.benchmark @@ -154,12 +153,14 @@ void addResult((double ips, double ops) result) throw new ApplicationException("Unknown benchmark type"); } - static void showStats(string tag, List vec, string discardMessage = "") + string delim = "###"; + + void showStats(string tag, List vec, string discardMessage = "") { var mean = vec.Sum() / vec.Count; var stddev = Math.Sqrt(vec.Sum(n => Math.Pow(n - mean, 2)) / vec.Count); var stddevpct = (stddev / mean) * 100; - Console.WriteLine($"###; {tag}; {mean:N3}; sd; {stddev:N1}; {stddevpct:N1}%"); + Console.WriteLine($"{delim}; {tag}; {mean:N3}; sd; {stddev:N1}; {stddevpct:N1}%"); } void showAllStats(string discardMessage = "") @@ -199,13 +200,19 @@ void showAllStats(string discardMessage = "") } if (options.IterationCount > 1) - showAllStats(); - - if (iter < options.IterationCount - 1) { - GC.Collect(); - GC.WaitForFullGCComplete(); - Thread.Sleep(1000); + if (iter < options.IterationCount - 1) + { + showAllStats(); + GC.Collect(); + GC.WaitForFullGCComplete(); + Thread.Sleep(1000); + } + else + { + delim = "#+#"; + showAllStats(); + } } } @@ -223,6 +230,7 @@ static void discardHiLo(List vec) discardHiLo(opsPerRun); Console.WriteLine(); + delim = "#-#"; showAllStats($" ({options.IterationCount} iterations specified, with high and low discarded)"); } } From 3b118b8b0955da8a3ee17ae917756bfe06e63504 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Mon, 1 Mar 2021 02:37:51 -0800 Subject: [PATCH 12/37] More YCSB benchmark updates: - Force synthetic data for kUseSmallData - Add the Zipf generator for synthetic data - Improve output formatting so it is more easily parsable and has all needed info --- cs/benchmark/ConcurrentDictionaryBenchmark.cs | 75 +- cs/benchmark/FasterSpanByteYcsbBenchmark.cs | 62 +- cs/benchmark/FasterYcsbBenchmark.cs | 91 +-- cs/benchmark/Options.cs | 68 ++ cs/benchmark/Program.cs | 141 ++-- cs/benchmark/YcsbGlobals.cs | 84 ++ cs/benchmark/ZipfGenerator.cs | 48 ++ cs/src/core/Index/FASTER/FASTERImpl.cs | 716 +++++++++--------- 8 files changed, 708 insertions(+), 577 deletions(-) create mode 100644 cs/benchmark/Options.cs create mode 100644 cs/benchmark/YcsbGlobals.cs create mode 100644 cs/benchmark/ZipfGenerator.cs diff --git a/cs/benchmark/ConcurrentDictionaryBenchmark.cs b/cs/benchmark/ConcurrentDictionaryBenchmark.cs index 338fd8943..7e5d9442c 100644 --- a/cs/benchmark/ConcurrentDictionaryBenchmark.cs +++ b/cs/benchmark/ConcurrentDictionaryBenchmark.cs @@ -11,7 +11,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Net; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; @@ -35,19 +34,6 @@ public int GetHashCode(Key obj) public unsafe class ConcurrentDictionary_YcsbBenchmark { - public enum Op : ulong - { - Upsert = 0, - Read = 1, - ReadModifyWrite = 2 - } - - const bool kUseSyntheticData = true; - const bool kUseSmallData = false; - const long kInitCount = kUseSmallData ? 2500480 : 250000000; - const long kTxnCount = kUseSmallData ? 10000000 : 1000000000; - const int kMaxKey = kUseSmallData ? 1 << 22 : 1 << 28; - const int kFileChunkSize = 4096; const long kChunkSize = 640; @@ -96,7 +82,7 @@ public ConcurrentDictionary_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, int thre for (int i = 0; i < 8; i++) input_[i].value = i; - store = new ConcurrentDictionary(threadCount, kMaxKey, new KeyComparer()); + store = new ConcurrentDictionary(threadCount, YcsbGlobals.kMaxKey, new KeyComparer()); } public void Dispose() @@ -130,9 +116,9 @@ private void RunYcsb(int thread_idx) while (!done) { long chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; - while (chunk_idx >= kTxnCount) + while (chunk_idx >= YcsbGlobals.kTxnCount) { - if (chunk_idx == kTxnCount) + if (chunk_idx == YcsbGlobals.kTxnCount) idx_ = 0; chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; } @@ -234,8 +220,8 @@ public unsafe (double, double) Run() } sw.Stop(); - double initsPerSecond = ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; - Console.WriteLine($"Loading time: {sw.ElapsedMilliseconds}ms ({initsPerSecond:N3} inserts per sec)"); + double insertsPerSecond = ((double)YcsbGlobals.kInitCount / sw.ElapsedMilliseconds) * 1000; + Console.WriteLine(YcsbGlobals.LoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); idx_ = 0; @@ -289,10 +275,9 @@ public unsafe (double, double) Run() double seconds = swatch.ElapsedMilliseconds / 1000.0; double opsPerSecond = total_ops_done / seconds; - Console.WriteLine("Total " + total_ops_done + " ops done " + " in " + seconds + " secs."); - Console.WriteLine("##, " + distribution + ", " + numaStyle + ", " + readPercent + ", " - + threadCount + ", " + opsPerSecond); - return (initsPerSecond, opsPerSecond); + Console.WriteLine(YcsbGlobals.TotalOpsString(total_ops_done, seconds)); + Console.WriteLine(YcsbGlobals.StatsLine(StatsLine.Iteration, YcsbGlobals.OpsPerSec, opsPerSecond)); + return (insertsPerSecond, opsPerSecond); } private void SetupYcsb(int thread_idx) @@ -312,7 +297,7 @@ private void SetupYcsb(int thread_idx) Value value = default; for (long chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; - chunk_idx < kInitCount; + chunk_idx < YcsbGlobals.kInitCount; chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize) { for (long idx = chunk_idx; idx < chunk_idx + kChunkSize; ++idx) @@ -421,7 +406,7 @@ private static void LoadDataFromFile(string filePath, string distribution, out K FileShare.Read)) { Console.WriteLine("loading keys from " + init_filename + " into memory..."); - i_keys = new Key[kInitCount]; + i_keys = new Key[YcsbGlobals.kInitCount]; byte[] chunk = new byte[kFileChunkSize]; GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); @@ -443,17 +428,17 @@ private static void LoadDataFromFile(string filePath, string distribution, out K else break; - if (count == kInitCount) + if (count == YcsbGlobals.kInitCount) break; } - if (count != kInitCount) + if (count != YcsbGlobals.kInitCount) { throw new InvalidDataException("Init file load fail!"); } } - Console.WriteLine("loaded " + kInitCount + " keys."); + Console.WriteLine("loaded " + YcsbGlobals.kInitCount + " keys."); using (FileStream stream = File.Open(txn_filename, FileMode.Open, FileAccess.Read, FileShare.Read)) @@ -464,7 +449,7 @@ private static void LoadDataFromFile(string filePath, string distribution, out K Console.WriteLine("loading txns from " + txn_filename + " into memory..."); - t_keys = new Key[kTxnCount]; + t_keys = new Key[YcsbGlobals.kTxnCount]; count = 0; long offset = 0; @@ -475,7 +460,7 @@ private static void LoadDataFromFile(string filePath, string distribution, out K int size = stream.Read(chunk, 0, kFileChunkSize); for (int idx = 0; idx < size; idx += 8) { - t_keys[count] = *((Key*)(chunk_ptr + idx)); + t_keys[count] = *(Key*)(chunk_ptr + idx); ++count; } if (size == kFileChunkSize) @@ -483,23 +468,25 @@ private static void LoadDataFromFile(string filePath, string distribution, out K else break; - if (count == kTxnCount) + if (count == YcsbGlobals.kTxnCount) break; } - if (count != kTxnCount) + if (count != YcsbGlobals.kTxnCount) { - throw new InvalidDataException("Txn file load fail!" + count + ":" + kTxnCount); + throw new InvalidDataException("Txn file load fail!" + count + ":" + YcsbGlobals.kTxnCount); } } - Console.WriteLine("loaded " + kTxnCount + " txns."); + Console.WriteLine("loaded " + YcsbGlobals.kTxnCount + " txns."); } public static void LoadData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) { - if (kUseSyntheticData) + if (YcsbGlobals.kUseSyntheticData || YcsbGlobals.kUseSmallData) { + if (!YcsbGlobals.kUseSyntheticData) + Console.WriteLine("WARNING: Forcing synthetic data due to kSmallData"); LoadSyntheticData(distribution, seed, out i_keys, out t_keys); return; } @@ -529,27 +516,29 @@ public static void LoadData(string distribution, uint seed, out Key[] i_keys, ou private static void LoadSyntheticData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) { - Console.WriteLine("Loading synthetic data (uniform distribution)"); + Console.WriteLine($"Loading synthetic data ({distribution} distribution), seed = {seed}"); - i_keys = new Key[kInitCount]; + i_keys = new Key[YcsbGlobals.kInitCount]; long val = 0; - for (int idx = 0; idx < kInitCount; idx++) + for (int idx = 0; idx < YcsbGlobals.kInitCount; idx++) { i_keys[idx] = new Key { value = val++ }; } - Console.WriteLine("loaded " + kInitCount + " keys."); + Console.WriteLine("loaded " + YcsbGlobals.kInitCount + " keys."); RandomGenerator generator = new RandomGenerator(seed); + var zipf = new ZipfGenerator(generator, (int)YcsbGlobals.kInitCount, theta: 0.99); - t_keys = new Key[kTxnCount]; + t_keys = new Key[YcsbGlobals.kTxnCount]; - for (int idx = 0; idx < kTxnCount; idx++) + for (int idx = 0; idx < YcsbGlobals.kTxnCount; idx++) { - t_keys[idx] = new Key { value = (long)generator.Generate64(kInitCount) }; + var rand = distribution == YcsbGlobals.UniformDist ? (long)generator.Generate64(YcsbGlobals.kInitCount) : zipf.Next(); + t_keys[idx] = new Key { value = rand }; } - Console.WriteLine("loaded " + kTxnCount + " txns."); + Console.WriteLine("loaded " + YcsbGlobals.kTxnCount + " txns."); } #endregion diff --git a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs index 3b3debb52..0220fee22 100644 --- a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs +++ b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs @@ -17,28 +17,13 @@ namespace FASTER.benchmark { public class FasterSpanByteYcsbBenchmark { - public enum Op : ulong - { - Upsert = 0, - Read = 1, - ReadModifyWrite = 2 - } - #if DEBUG const bool kDumpDistribution = false; - const bool kUseSmallData = true; - const bool kUseSyntheticData = true; - const bool kSmallMemoryLog = false; const bool kAffinitizedSession = true; - const int kRunSeconds = 30; const int kPeriodicCheckpointMilliseconds = 0; #else const bool kDumpDistribution = false; - const bool kUseSmallData = true; //false; - const bool kUseSyntheticData = false; - const bool kSmallMemoryLog = false; const bool kAffinitizedSession = true; - const int kRunSeconds = 30; const int kPeriodicCheckpointMilliseconds = 0; #endif @@ -49,14 +34,9 @@ public enum Op : ulong readonly BackupMode backupMode; // *** - const long kInitCount_ = kUseSmallData ? 2500480 : 250000000; - const long kTxnCount_ = kUseSmallData ? 10000000 : 1000000000; - // Ensure sizes are aligned to chunk sizes - const long kInitCount = kChunkSize * (kInitCount_ / kChunkSize); - const long kTxnCount = kChunkSize * (kTxnCount_ / kChunkSize); - - const int kMaxKey = kUseSmallData ? 1 << 22 : 1 << 28; + const long kInitCount = kChunkSize * (YcsbGlobals.kInitCount / kChunkSize); + const long kTxnCount = kChunkSize * (YcsbGlobals.kTxnCount / kChunkSize); public const int kKeySize = 16; public const int kValueSize = 100; @@ -91,7 +71,7 @@ internal FasterSpanByteYcsbBenchmark(KeySpanByte[] i_keys_, KeySpanByte[] t_keys numaStyle = numaStyle_; distribution = distribution_; readPercent = readPercent_; - this.backupMode = backupMode_; + backupMode = backupMode_; var lockImpl = lockImpl_; functions = new FunctionsSB(lockImpl != LockImpl.None); secondaryIndexType = secondaryIndexType_; @@ -116,13 +96,13 @@ internal FasterSpanByteYcsbBenchmark(KeySpanByte[] i_keys_, KeySpanByte[] t_keys var path = "D:\\data\\FasterYcsbBenchmark\\"; device = Devices.CreateLogDevice(path + "hlog", preallocateFile: true); - if (kSmallMemoryLog) + if (YcsbGlobals.kSmallMemoryLog) store = new FasterKV - (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, + (YcsbGlobals.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); else store = new FasterKV - (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, MemorySizeBits = 35 }, + (YcsbGlobals.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, MemorySizeBits = 35 }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); if (secondaryIndexType.HasFlag(SecondaryIndexType.Key)) @@ -305,8 +285,9 @@ public unsafe (double, double) Run() } sw.Stop(); } - double initsPerSecond = storeWasRecovered ? 0 : ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; - Console.WriteLine($"Loading time: {sw.ElapsedMilliseconds}ms ({initsPerSecond:N3} inserts per sec)"); + double insertsPerSecond = storeWasRecovered ? 0 : ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; + Console.WriteLine(YcsbGlobals.LoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); + Console.WriteLine(YcsbGlobals.AddressesLine(AddressLine.Before, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); long startTailAddress = store.Log.TailAddress; Console.WriteLine("Start tail address = " + startTailAddress); @@ -350,12 +331,12 @@ public unsafe (double, double) Run() if (kPeriodicCheckpointMilliseconds <= 0) { - Thread.Sleep(TimeSpan.FromSeconds(kRunSeconds)); + Thread.Sleep(TimeSpan.FromSeconds(YcsbGlobals.kRunSeconds)); } else { var checkpointTaken = 0; - while (swatch.ElapsedMilliseconds < 1000 * kRunSeconds) + while (swatch.ElapsedMilliseconds < 1000 * YcsbGlobals.kRunSeconds) { if (checkpointTaken < swatch.ElapsedMilliseconds / kPeriodicCheckpointMilliseconds) { @@ -382,15 +363,12 @@ public unsafe (double, double) Run() #endif double seconds = swatch.ElapsedMilliseconds / 1000.0; - long endTailAddress = store.Log.TailAddress; - Console.WriteLine("End tail address = " + endTailAddress); + Console.WriteLine(YcsbGlobals.AddressesLine(AddressLine.After, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); double opsPerSecond = total_ops_done / seconds; - Console.WriteLine("Total " + total_ops_done + " ops done " + " in " + seconds + " secs."); - Console.WriteLine("##, " + distribution + ", " + numaStyle + ", " + readPercent + ", " - + threadCount + ", " + opsPerSecond + ", " - + (endTailAddress - startTailAddress)); - return (initsPerSecond, opsPerSecond); + Console.WriteLine(YcsbGlobals.TotalOpsString(total_ops_done, seconds)); + Console.WriteLine(YcsbGlobals.StatsLine(StatsLine.Iteration, YcsbGlobals.OpsPerSec, opsPerSecond)); + return (insertsPerSecond, opsPerSecond); } private void SetupYcsb(int thread_idx) @@ -613,8 +591,10 @@ private static unsafe void LoadDataFromFile(string filePath, string distribution public static void LoadData(string distribution, uint seed, out KeySpanByte[] i_keys, out KeySpanByte[] t_keys) { - if (kUseSyntheticData) + if (YcsbGlobals.kUseSyntheticData || YcsbGlobals.kUseSmallData) { + if (!YcsbGlobals.kUseSyntheticData) + Console.WriteLine("WARNING: Forcing synthetic data due to kSmallData"); LoadSyntheticData(distribution, seed, out i_keys, out t_keys); return; } @@ -643,7 +623,7 @@ public static void LoadData(string distribution, uint seed, out KeySpanByte[] i_ private static void LoadSyntheticData(string distribution, uint seed, out KeySpanByte[] i_keys, out KeySpanByte[] t_keys) { - Console.WriteLine("Loading synthetic data (uniform distribution)"); + Console.WriteLine($"Loading synthetic data ({distribution} distribution), seed = {seed}"); i_keys = new KeySpanByte[kInitCount]; long val = 0; @@ -655,12 +635,14 @@ private static void LoadSyntheticData(string distribution, uint seed, out KeySpa Console.WriteLine("loaded " + kInitCount + " keys."); RandomGenerator generator = new RandomGenerator(seed); + var zipf = new ZipfGenerator(generator, (int)kInitCount, theta: 0.99); t_keys = new KeySpanByte[kTxnCount]; for (int idx = 0; idx < kTxnCount; idx++) { - t_keys[idx] = new KeySpanByte { length = kKeySize - 4, value = (long)generator.Generate64(kInitCount) }; + var rand = distribution == YcsbGlobals.UniformDist ? (long)generator.Generate64(kInitCount) : zipf.Next(); + t_keys[idx] = new KeySpanByte { length = kKeySize - 4, value = rand }; } Console.WriteLine("loaded " + kTxnCount + " txns."); diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index a6051c685..0d92b5a30 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -17,31 +17,6 @@ namespace FASTER.benchmark { internal class FASTER_YcsbBenchmark { - public enum Op : ulong - { - Upsert = 0, - Read = 1, - ReadModifyWrite = 2 - } - -#if DEBUG - const bool kDumpDistribution = false; - const bool kUseSmallData = true; - const bool kUseSyntheticData = true; - const bool kSmallMemoryLog = false; - const bool kAffinitizedSession = true; - const int kRunSeconds = 30; - const int kPeriodicCheckpointMilliseconds = 0; -#else - const bool kDumpDistribution = false; - const bool kUseSmallData = true; // false; - const bool kUseSyntheticData = false; - const bool kSmallMemoryLog = false; - const bool kAffinitizedSession = true; - const int kRunSeconds = 30; - const int kPeriodicCheckpointMilliseconds = 0; -#endif - // *** Use these to backup and recover database for fast benchmark repeat runs // Use BackupMode.Backup to create the backup, unless it was recovered during BackupMode.Recover // Use BackupMode.Restore for fast subsequent runs @@ -49,14 +24,19 @@ public enum Op : ulong readonly BackupMode backupMode; // *** - const long kInitCount_ = kUseSmallData ? 2500480 : 250000000; - const long kTxnCount_ = kUseSmallData ? 10000000 : 1000000000; +#if DEBUG + internal const bool kDumpDistribution = false; + internal const bool kAffinitizedSession = true; + internal const int kPeriodicCheckpointMilliseconds = 0; +#else + internal const bool kDumpDistribution = false; + internal const bool kAffinitizedSession = true; + internal const int kPeriodicCheckpointMilliseconds = 0; +#endif // Ensure sizes are aligned to chunk sizes - const long kInitCount = kChunkSize * (kInitCount_ / kChunkSize); - const long kTxnCount = kChunkSize * (kTxnCount_ / kChunkSize); - - const int kMaxKey = kUseSmallData ? 1 << 22 : 1 << 28; + const long kInitCount = kChunkSize * (YcsbGlobals.kInitCount / kChunkSize); + const long kTxnCount = kChunkSize * (YcsbGlobals.kTxnCount / kChunkSize); const int kFileChunkSize = 4096; const long kChunkSize = 640; @@ -92,7 +72,7 @@ internal FASTER_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, int threadCount_, in numaStyle = numaStyle_; distribution = distribution_; readPercent = readPercent_; - this.backupMode = backupMode_; + backupMode = backupMode_; var lockImpl = lockImpl_; functions = new Functions(lockImpl != LockImpl.None); secondaryIndexType = secondaryIndexType_; @@ -117,13 +97,13 @@ internal FASTER_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, int threadCount_, in var path = "D:\\data\\FasterYcsbBenchmark\\"; device = Devices.CreateLogDevice(path + "hlog", preallocateFile: true); - if (kSmallMemoryLog) + if (YcsbGlobals.kSmallMemoryLog) store = new FasterKV - (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, + (YcsbGlobals.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); else store = new FasterKV - (kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true }, + (YcsbGlobals.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); if (secondaryIndexType.HasFlag(SecondaryIndexType.Key)) @@ -301,11 +281,9 @@ public unsafe (double, double) Run() } sw.Stop(); } - double initsPerSecond = storeWasRecovered ? 0 : ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; - Console.WriteLine($"Loading time: {sw.ElapsedMilliseconds}ms ({initsPerSecond:N3} inserts per sec)"); - - long startTailAddress = store.Log.TailAddress; - Console.WriteLine("Start tail address = " + startTailAddress); + double insertsPerSecond = storeWasRecovered ? 0 : ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; + Console.WriteLine(YcsbGlobals.LoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); + Console.WriteLine(YcsbGlobals.AddressesLine(AddressLine.Before, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); if (!storeWasRecovered && this.backupMode.HasFlag(BackupMode.Backup) && kPeriodicCheckpointMilliseconds <= 0) { @@ -346,12 +324,12 @@ public unsafe (double, double) Run() if (kPeriodicCheckpointMilliseconds <= 0) { - Thread.Sleep(TimeSpan.FromSeconds(kRunSeconds)); + Thread.Sleep(TimeSpan.FromSeconds(YcsbGlobals.kRunSeconds)); } else { var checkpointTaken = 0; - while (swatch.ElapsedMilliseconds < 1000 * kRunSeconds) + while (swatch.ElapsedMilliseconds < 1000 * YcsbGlobals.kRunSeconds) { if (checkpointTaken < swatch.ElapsedMilliseconds / kPeriodicCheckpointMilliseconds) { @@ -378,15 +356,12 @@ public unsafe (double, double) Run() #endif double seconds = swatch.ElapsedMilliseconds / 1000.0; - long endTailAddress = store.Log.TailAddress; - Console.WriteLine("End tail address = " + endTailAddress); + Console.WriteLine(YcsbGlobals.AddressesLine(AddressLine.After, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); double opsPerSecond = total_ops_done / seconds; - Console.WriteLine("Total " + total_ops_done + " ops done " + " in " + seconds + " secs."); - Console.WriteLine("##, " + distribution + ", " + numaStyle + ", " + readPercent + ", " - + threadCount + ", " + opsPerSecond + ", " - + (endTailAddress - startTailAddress)); - return (initsPerSecond, opsPerSecond); + Console.WriteLine(YcsbGlobals.TotalOpsString(total_ops_done, seconds)); + Console.WriteLine(YcsbGlobals.StatsLine(StatsLine.Iteration, YcsbGlobals.OpsPerSec, opsPerSecond)); + return (insertsPerSecond, opsPerSecond); } private void SetupYcsb(int thread_idx) @@ -509,7 +484,7 @@ void DoContinuousMeasurements() } #endif - #region Load Data +#region Load Data private static unsafe void LoadDataFromFile(string filePath, string distribution, out Key[] i_keys, out Key[] t_keys) { @@ -606,8 +581,10 @@ private static unsafe void LoadDataFromFile(string filePath, string distribution public static void LoadData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) { - if (kUseSyntheticData) + if (YcsbGlobals.kUseSyntheticData || YcsbGlobals.kUseSmallData) { + if (!YcsbGlobals.kUseSyntheticData) + Console.WriteLine("WARNING: Forcing synthetic data due to kSmallData"); LoadSyntheticData(distribution, seed, out i_keys, out t_keys); return; } @@ -636,7 +613,7 @@ public static void LoadData(string distribution, uint seed, out Key[] i_keys, ou private static void LoadSyntheticData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) { - Console.WriteLine("Loading synthetic data (uniform distribution)"); + Console.WriteLine($"Loading synthetic data ({distribution} distribution), seed = {seed}"); i_keys = new Key[kInitCount]; long val = 0; @@ -648,19 +625,17 @@ private static void LoadSyntheticData(string distribution, uint seed, out Key[] Console.WriteLine("loaded " + kInitCount + " keys."); RandomGenerator generator = new RandomGenerator(seed); + var zipf = new ZipfGenerator(generator, (int)kInitCount, theta:0.99); t_keys = new Key[kTxnCount]; - for (int idx = 0; idx < kTxnCount; idx++) { - t_keys[idx] = new Key { value = (long)generator.Generate64(kInitCount) }; + var rand = distribution == YcsbGlobals.UniformDist ? (long)generator.Generate64(kInitCount) : zipf.Next(); + t_keys[idx] = new Key { value = rand}; } Console.WriteLine("loaded " + kTxnCount + " txns."); - } - #endregion - - +#endregion } } \ No newline at end of file diff --git a/cs/benchmark/Options.cs b/cs/benchmark/Options.cs new file mode 100644 index 000000000..41fd56b43 --- /dev/null +++ b/cs/benchmark/Options.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using CommandLine; + +namespace FASTER.benchmark +{ + class Options + { + [Option('b', "benchmark", Required = false, Default = 0, + HelpText = "Benchmark to run:" + + "\n 0 = YCSB" + + "\n 1 = YCSB with SpanByte" + + "\n 2 = ConcurrentDictionary")] + public int Benchmark { get; set; } + + [Option('t', "threads", Required = false, Default = 8, + HelpText = "Number of threads to run the workload on")] + public int ThreadCount { get; set; } + + [Option('n', "numa", Required = false, Default = 0, + HelpText = "NUMA options:" + + "\n 0 = No sharding across NUMA sockets" + + "\n 1 = Sharding across NUMA sockets")] + public int NumaStyle { get; set; } + + [Option('k', "backup", Required = false, Default = 0, + HelpText = "Enable Backup and Restore of FasterKV for fast test startup:" + + "\n 0 = None; Populate FasterKV from data" + + "\n 1 = Recover FasterKV from Checkpoint; if this fails, populate FasterKV from data" + + "\n 2 = Checkpoint FasterKV (unless it was Recovered by option 1; if option 1 is not specified, this will overwrite an existing Checkpoint)" + + "\n 3 = Both (Recover FasterKV if a Checkpoint is available, else populate FasterKV from data and Checkpoint it so it can be Restored in a subsequent run)")] + public int Backup { get; set; } + + [Option('z', "locking", Required = false, Default = 0, + HelpText = "Locking Implementation:" + + "\n 0 = None (default)" + + "\n 1 = RecordInfo.SpinLock()")] + public int LockImpl { get; set; } + + [Option('x', "index", Required = false, Default = 0, + HelpText = "Secondary index type(s); these implement a no-op index to test the overhead on FasterKV operations:" + + "\n 0 = None (default)" + + "\n 1 = Key-based index" + + "\n 2 = Value-based index" + + "\n 3 = Both index types")] + public int SecondaryIndexType { get; set; } + + [Option('i', "iterations", Required = false, Default = 1, + HelpText = "Number of iterations of the test to run")] + public int IterationCount { get; set; } + + [Option('r', "read_percent", Required = false, Default = 50, + HelpText = "Percentage of reads (-1 for 100% read-modify-write")] + public int ReadPercent { get; set; } + + [Option('d', "distribution", Required = false, Default = YcsbGlobals.UniformDist, + HelpText = "Distribution of keys in workload")] + public string DistributionName { get; set; } + + [Option('s', "seed", Required = false, Default = 211, + HelpText = "Seed for synthetic data distribution")] + public int RandomSeed { get; set; } + + public string GetOptionsString() + => $"d: {DistributionName.ToLower()}; n: {NumaStyle}; r: {ReadPercent}; t: {ThreadCount}; x: {SecondaryIndexType}; z: {LockImpl}"; + } +} diff --git a/cs/benchmark/Program.cs b/cs/benchmark/Program.cs index 9a5c85c5d..03a921e23 100644 --- a/cs/benchmark/Program.cs +++ b/cs/benchmark/Program.cs @@ -9,88 +9,27 @@ namespace FASTER.benchmark { - class Options + enum AggregateType { - [Option('b', "benchmark", Required = false, Default = 0, - HelpText = "Benchmark to run:" + - "\n 0 = YCSB" + - "\n 1 = YCSB with SpanByte" + - "\n 2 = ConcurrentDictionary")] - public int Benchmark { get; set; } - - [Option('t', "threads", Required = false, Default = 8, - HelpText = "Number of threads to run the workload on")] - public int ThreadCount { get; set; } - - [Option('n', "numa", Required = false, Default = 0, - HelpText = "NUMA options:" + - "\n 0 = No sharding across NUMA sockets" + - "\n 1 = Sharding across NUMA sockets")] - public int NumaStyle { get; set; } - - [Option('k', "backup", Required = false, Default = 0, - HelpText = "Enable Backup and Restore of FasterKV for fast test startup:" + - "\n 0 = None; Populate FasterKV from data" + - "\n 1 = Recover FasterKV from Checkpoint; if this fails, populate FasterKV from data" + - "\n 2 = Checkpoint FasterKV (unless it was Recovered by option 1; if option 1 is not specified, this will overwrite an existing Checkpoint)" + - "\n 3 = Both (Recover FasterKV if a Checkpoint is available, else populate FasterKV from data and Checkpoint it so it can be Restored in a subsequent run)")] - public int Backup { get; set; } - - [Option('l', "locking", Required = false, Default = 0, - HelpText = "Locking Implementation:" + - "\n 0 = None (default)" + - "\n 1 = RecordInfo.SpinLock()")] - public int LockImpl { get; set; } - - [Option('x', "index", Required = false, Default = 0, - HelpText = "Secondary index type(s); these implement a no-op index to test the overhead on FasterKV operations:" + - "\n 0 = None (default)" + - "\n 1 = Key-based index" + - "\n 2 = Value-based index" + - "\n 3 = Both index types")] - public int SecondaryIndexType { get; set; } - - [Option('i', "iterations", Required = false, Default = 1, - HelpText = "Number of iterations of the test to run")] - public int IterationCount { get; set; } - - [Option('r', "read_percent", Required = false, Default = 50, - HelpText = "Percentage of reads (-1 for 100% read-modify-write")] - public int ReadPercent { get; set; } - - [Option('d', "distribution", Required = false, Default = "uniform", - HelpText = "Distribution of keys in workload")] - public string Distribution { get; set; } - - [Option('s', "seed", Required = false, Default = 211, - HelpText = "Seed for synthetic data distribution")] - public int RandomSeed { get; set; } + Running, + FinalFull, + FinalTrimmed } - enum BenchmarkType : int + enum StatsLine : int { - Ycsb, SpanByte, ConcurrentDictionaryYcsb - }; - - [Flags] enum BackupMode : int - { - None, Restore, Backup, Both - }; - - enum LockImpl : int - { - None, RecordInfo - }; - - [Flags] - enum SecondaryIndexType : int - { - None, Key, Value, Both - }; + Iteration = 3, + RunningIns = 4, + RunningOps = 5, + FinalFullIns = 10, + FinalFullOps = 11, + FinalTrimmedIns = 20, + FinalTrimmedOps = 21 + } public class Program { - const int kTrimResultCount = 3;// int.MaxValue; // Use some high value like int.MaxValue to bypass + const int kTrimResultCount = 3; // Use some high value like int.MaxValue to disable public static void Main(string[] args) { @@ -119,11 +58,13 @@ bool verifyOption(bool isValid, string name) if (!verifyOption(Enum.IsDefined(typeof(SecondaryIndexType), secondaryIndexType), "Secondary Index Type")) return; if (!verifyOption(options.IterationCount > 0, "Iteration Count")) return; if (!verifyOption(options.ReadPercent >= -1 && options.ReadPercent <= 100, "Read Percent")) return; - var distribution = options.Distribution.ToLower(); - if (!verifyOption(distribution == "uniform" || distribution == "zipf", "Distribution")) return; + var distribution = options.DistributionName.ToLower(); + if (!verifyOption(distribution == YcsbGlobals.UniformDist || distribution == YcsbGlobals.ZipfDist, "Distribution")) return; Console.WriteLine($"Scenario: {b}, Locking: {(LockImpl)options.LockImpl}, Indexing: {(SecondaryIndexType)options.SecondaryIndexType}"); + YcsbGlobals.OptionsString = options.GetOptionsString(); + var initsPerRun = new List(); var opsPerRun = new List(); @@ -153,30 +94,41 @@ void addResult((double ips, double ops) result) throw new ApplicationException("Unknown benchmark type"); } - string delim = "###"; - - void showStats(string tag, List vec, string discardMessage = "") + static void showStats(StatsLine lineNum, string tag, List vec, string discardMessage = "") { var mean = vec.Sum() / vec.Count; var stddev = Math.Sqrt(vec.Sum(n => Math.Pow(n - mean, 2)) / vec.Count); var stddevpct = (stddev / mean) * 100; - Console.WriteLine($"{delim}; {tag}; {mean:N3}; sd; {stddev:N1}; {stddevpct:N1}%"); + Console.WriteLine(YcsbGlobals.StatsLine(lineNum, tag, mean, stddev, stddevpct)); } - void showAllStats(string discardMessage = "") + void showAllStats(AggregateType aggregateType, string discardMessage = "") { - Console.WriteLine($"Averages per second over {initsPerRun.Count} iteration(s){discardMessage}:"); - showStats("ins/sec", initsPerRun); - showStats("ops/sec", opsPerRun); + var aggTypeString = aggregateType == AggregateType.Running ? "Running" : "Final"; + Console.WriteLine($"{aggTypeString} averages per second over {initsPerRun.Count} iteration(s){discardMessage}:"); + var statsLineNum = aggregateType switch + { + AggregateType.Running => StatsLine.RunningIns, + AggregateType.FinalFull => StatsLine.FinalFullIns, + AggregateType.FinalTrimmed => StatsLine.FinalTrimmedIns, + _ => throw new InvalidOperationException("Unknown AggregateType") + }; + showStats(statsLineNum, "ins/sec", initsPerRun); + statsLineNum = aggregateType switch + { + AggregateType.Running => StatsLine.RunningOps, + AggregateType.FinalFull => StatsLine.FinalFullOps, + AggregateType.FinalTrimmed => StatsLine.FinalTrimmedOps, + _ => throw new InvalidOperationException("Unknown AggregateType") + }; + showStats(statsLineNum, "ops/sec", opsPerRun); } for (var iter = 0; iter < options.IterationCount; ++iter) { + Console.WriteLine(); if (options.IterationCount > 1) - { - Console.WriteLine(); Console.WriteLine($"Iteration {iter + 1} of {options.IterationCount}"); - } switch (b) { @@ -201,21 +153,19 @@ void showAllStats(string discardMessage = "") if (options.IterationCount > 1) { + showAllStats(AggregateType.Running); if (iter < options.IterationCount - 1) { - showAllStats(); GC.Collect(); GC.WaitForFullGCComplete(); Thread.Sleep(1000); } - else - { - delim = "#+#"; - showAllStats(); - } } } + Console.WriteLine(); + showAllStats(AggregateType.FinalFull); + if (options.IterationCount >= kTrimResultCount) { static void discardHiLo(List vec) @@ -230,8 +180,7 @@ static void discardHiLo(List vec) discardHiLo(opsPerRun); Console.WriteLine(); - delim = "#-#"; - showAllStats($" ({options.IterationCount} iterations specified, with high and low discarded)"); + showAllStats(AggregateType.FinalTrimmed, $" ({options.IterationCount} iterations specified, with high and low discarded)"); } } } diff --git a/cs/benchmark/YcsbGlobals.cs b/cs/benchmark/YcsbGlobals.cs new file mode 100644 index 000000000..beec65b19 --- /dev/null +++ b/cs/benchmark/YcsbGlobals.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace FASTER.benchmark +{ + enum BenchmarkType : int + { + Ycsb, SpanByte, ConcurrentDictionaryYcsb + }; + + [Flags] + enum BackupMode : int + { + None, Restore, Backup, Both + }; + + enum LockImpl : int + { + None, RecordInfo + }; + + [Flags] + enum SecondaryIndexType : int + { + None, Key, Value, Both + }; + + enum AddressLine : int + { + Before = 1, + After = 2 + } + + public enum Op : ulong + { + Upsert = 0, + Read = 1, + ReadModifyWrite = 2 + } + + public static class YcsbGlobals + { + internal const string UniformDist = "uniform"; // Uniformly random distribution of keys + internal const string ZipfDist = "zipf"; // Smooth zipf curve (most localized keys) + + internal const string InsPerSec = "ins/sec"; + internal const string OpsPerSec = "ops/sec"; + + internal static string TotalOpsString(long totalOps, double seconds) => $"Total {totalOps:N0} ops done in {seconds:N3} seconds"; + + internal static string OptionsString; + + internal static string LoadingTimeLine(double insertsPerSec, long elapsedMs) + => $"##0; {InsPerSec}: {insertsPerSec:N2}; ms: {elapsedMs:N0}"; + + internal static string AddressesLine(AddressLine lineNum, long begin, long head, long rdonly, long tail) + => $"##{(int)lineNum}; begin: {begin:N0}; head: {head:N0}; readonly: {rdonly:N0}; tail: {tail}"; + + private static string BoolStr(bool value) => value ? "y" : "n"; + + internal static string StatsLine(StatsLine lineNum, string opsPerSecTag, double opsPerSec) + => $"##{(int)lineNum}; {opsPerSecTag}: {opsPerSec:N2}; {OptionsString}; sd: {BoolStr(kUseSmallData)}; sm: {BoolStr(kSmallMemoryLog)}"; + + internal static string StatsLine(StatsLine lineNum, string meanTag, double mean, double stdev, double stdevpct) + => $"##{(int)lineNum}; {meanTag}: {mean:N2}; stdev: {stdev:N1}; stdev%: {stdevpct:N1}; {OptionsString}; sd: {BoolStr(kUseSmallData)}; sm: {BoolStr(kSmallMemoryLog)}"; + +#if DEBUG + internal const bool kUseSmallData = true; + internal const bool kUseSyntheticData = true; + internal const bool kSmallMemoryLog = false; + internal const int kRunSeconds = 30; +#else + internal const bool kUseSmallData = true;//false; + internal const bool kUseSyntheticData = false; + internal const bool kSmallMemoryLog = false; + internal const int kRunSeconds = 30; +#endif + internal const long kInitCount = kUseSmallData ? 2500480 : 250000000; + internal const long kTxnCount = kUseSmallData ? 10000000 : 1000000000; + internal const int kMaxKey = kUseSmallData ? 1 << 22 : 1 << 28; + } +} diff --git a/cs/benchmark/ZipfGenerator.cs b/cs/benchmark/ZipfGenerator.cs new file mode 100644 index 000000000..274bbe466 --- /dev/null +++ b/cs/benchmark/ZipfGenerator.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace FASTER.benchmark +{ + public class ZipfGenerator + { + // Based on "Quickly Generating Billion-Record Synthetic Databases", Jim Gray et al., SIGMOD 1994. + readonly RandomGenerator rng; + readonly private int size; + readonly double theta; + readonly double zetaN, alpha, cutoff2, eta; + + public ZipfGenerator(RandomGenerator rng, int size, double theta = 0.99) + { + this.rng = rng; + this.size = size; + this.theta = theta; + + zetaN = Zeta(size, this.theta); + alpha = 1.0 / (1.0 - this.theta); + cutoff2 = Math.Pow(0.5, this.theta); + var zeta2 = Zeta(2, this.theta); + eta = (1.0 - Math.Pow(2.0 / size, 1.0 - this.theta)) / (1.0 - zeta2 / zetaN); + } + + private static double Zeta(int count, double theta) + { + double zetaN = 0.0; + for (var ii = 1; ii <= count; ++ii) + zetaN += 1.0 / Math.Pow(ii, theta); + return zetaN; + } + + public int Next() + { + double u = (double)rng.Generate64(int.MaxValue) / int.MaxValue; + double uz = u * zetaN; + if (uz < 1) + return 0; + if (uz < 1 + cutoff2) + return 1; + return (int)(size * Math.Pow(eta * u - eta + 1, alpha)); + } + } +} diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index 6ce9355f4..d0e4cff8b 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -164,29 +164,30 @@ internal OperationStatus InternalRead( { ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); pendingContext.recordInfo = recordInfo; - if (pendingContext.recordInfo.Tombstone) - return OperationStatus.NOTFOUND; - - fasterSession.ConcurrentReader(ref key, ref input, ref hlog.GetValue(physicalAddress), ref output, ref recordInfo, logicalAddress); - return OperationStatus.SUCCESS; + if (!pendingContext.recordInfo.Tombstone) + { + fasterSession.ConcurrentReader(ref key, ref input, ref hlog.GetValue(physicalAddress), ref output, ref recordInfo, logicalAddress); + return OperationStatus.SUCCESS; + } + return OperationStatus.NOTFOUND; } // Immutable region else if (logicalAddress >= hlog.HeadAddress) { pendingContext.recordInfo = hlog.GetInfo(physicalAddress); - if (pendingContext.recordInfo.Tombstone) - return OperationStatus.NOTFOUND; - - fasterSession.SingleReader(ref key, ref input, ref hlog.GetValue(physicalAddress), ref output, logicalAddress); - - if (CopyReadsToTail == CopyReadsToTail.FromReadOnly) + if (!pendingContext.recordInfo.Tombstone) { - var container = hlog.GetValueContainer(ref hlog.GetValue(physicalAddress)); - InternalUpsert(ref key, ref container.Get(), ref userContext, ref pendingContext, fasterSession, sessionCtx, lsn); - container.Dispose(); + fasterSession.SingleReader(ref key, ref input, ref hlog.GetValue(physicalAddress), ref output, logicalAddress); + if (CopyReadsToTail == CopyReadsToTail.FromReadOnly) + { + var container = hlog.GetValueContainer(ref hlog.GetValue(physicalAddress)); + InternalUpsert(ref key, ref container.Get(), ref userContext, ref pendingContext, fasterSession, sessionCtx, lsn); + container.Dispose(); + } + return OperationStatus.SUCCESS; } - return OperationStatus.SUCCESS; + return OperationStatus.NOTFOUND; } // On-Disk Region @@ -259,6 +260,13 @@ internal OperationStatus InternalRead( #region Upsert Operation + private enum LatchDestination + { + CreateNewRecord, + CreatePendingContext, + NormalProcessing + } + /// /// Upsert operation. Replaces the value corresponding to 'key' with provided 'value', if one exists /// else inserts a new record with 'key' and 'value'. @@ -300,12 +308,11 @@ internal OperationStatus InternalUpsert( long lsn) where FasterSession : IFasterSession { - var status = default(OperationStatus); var bucket = default(HashBucket*); var slot = default(int); - var logicalAddress = Constants.kInvalidAddress; - var physicalAddress = default(long); - var latchOperation = default(LatchOperation); + var status = default(OperationStatus); + var latchOperation = LatchOperation.None; + var latchDestination = LatchDestination.NormalProcessing; var hash = comparer.GetHashCode64(ref key); var tag = (ushort)((ulong)hash >> Constants.kHashTagShift); @@ -316,7 +323,8 @@ internal OperationStatus InternalUpsert( #region Trace back for record in in-memory HybridLog var entry = default(HashBucketEntry); FindOrCreateTag(hash, tag, ref bucket, ref slot, ref entry, hlog.BeginAddress); - logicalAddress = entry.Address; + var logicalAddress = entry.Address; + var physicalAddress = default(long); if (UseReadCache) SkipAndInvalidateReadCache(ref logicalAddress, ref key); @@ -337,101 +345,39 @@ internal OperationStatus InternalUpsert( } #endregion - // Optimization for most common case + // Optimization for the most common case if (sessionCtx.phase == Phase.REST && logicalAddress >= hlog.ReadOnlyAddress) { ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); - if (!recordInfo.Tombstone) - { - if (fasterSession.ConcurrentWriter(ref key, ref value, ref hlog.GetValue(physicalAddress), ref recordInfo, logicalAddress)) - return OperationStatus.SUCCESS; - goto CreateNewRecord; - } + if (!recordInfo.Tombstone + && fasterSession.ConcurrentWriter(ref key, ref value, ref hlog.GetValue(physicalAddress), ref recordInfo, logicalAddress)) + return OperationStatus.SUCCESS; + goto CreateNewRecord; } -#region Entry latch operation + #region Entry latch operation if (sessionCtx.phase != Phase.REST) { - switch (sessionCtx.phase) - { - case Phase.PREPARE: - { - if (HashBucket.TryAcquireSharedLatch(bucket)) - { - // Set to release shared latch (default) - latchOperation = LatchOperation.Shared; - if (GetLatestRecordVersion(ref entry, sessionCtx.version) > sessionCtx.version) - { - status = OperationStatus.CPR_SHIFT_DETECTED; - goto CreatePendingContext; // Pivot Thread - } - break; // Normal Processing - } - else - { - status = OperationStatus.CPR_SHIFT_DETECTED; - goto CreatePendingContext; // Pivot Thread - } - } - case Phase.IN_PROGRESS: - { - if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) - { - if (HashBucket.TryAcquireExclusiveLatch(bucket)) - { - // Set to release exclusive latch (default) - latchOperation = LatchOperation.Exclusive; - goto CreateNewRecord; // Create a (v+1) record - } - else - { - status = OperationStatus.RETRY_LATER; - goto CreatePendingContext; // Go Pending - } - } - break; // Normal Processing - } - case Phase.WAIT_PENDING: - { - if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) - { - if (HashBucket.NoSharedLatches(bucket)) - { - goto CreateNewRecord; // Create a (v+1) record - } - else - { - status = OperationStatus.RETRY_LATER; - goto CreatePendingContext; // Go Pending - } - } - break; // Normal Processing - } - case Phase.WAIT_FLUSH: - { - if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) - { - goto CreateNewRecord; // Create a (v+1) record - } - break; // Normal Processing - } - default: - break; - } + latchDestination = AcquireLatchUpsert(sessionCtx, bucket, ref status, ref latchOperation, ref entry); } -#endregion + #endregion Debug.Assert(GetLatestRecordVersion(ref entry, sessionCtx.version) <= sessionCtx.version); -#region Normal processing + #region Normal processing // Mutable Region: Update the record in-place - if (logicalAddress >= hlog.ReadOnlyAddress && !hlog.GetInfo(physicalAddress).Tombstone) + if (latchDestination == LatchDestination.NormalProcessing) { - if (fasterSession.ConcurrentWriter(ref key, ref value, ref hlog.GetValue(physicalAddress), ref hlog.GetInfo(physicalAddress), logicalAddress)) + if (logicalAddress >= hlog.ReadOnlyAddress) { - status = OperationStatus.SUCCESS; - goto LatchRelease; // Release shared latch (if acquired) + ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); + if (!recordInfo.Tombstone + && fasterSession.ConcurrentWriter(ref key, ref value, ref hlog.GetValue(physicalAddress), ref recordInfo, logicalAddress)) + { + status = OperationStatus.SUCCESS; + goto LatchRelease; // Release shared latch (if acquired) + } } } @@ -440,48 +386,16 @@ internal OperationStatus InternalUpsert( #region Create new record in the mutable region CreateNewRecord: + if (latchDestination != LatchDestination.CreatePendingContext) { // Immutable region or new record - var (actualSize, allocateSize) = hlog.GetRecordSize(ref key, ref value); - BlockAllocate(allocateSize, out long newLogicalAddress, sessionCtx, fasterSession); - var newPhysicalAddress = hlog.GetPhysicalAddress(newLogicalAddress); - RecordInfo.WriteInfo(ref hlog.GetInfo(newPhysicalAddress), - sessionCtx.version, - tombstone:false, invalidBit:false, - latestLogicalAddress); - hlog.Serialize(ref key, newPhysicalAddress); - fasterSession.SingleWriter(ref key, ref value, - ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); - - var updatedEntry = default(HashBucketEntry); - updatedEntry.Tag = tag; - updatedEntry.Address = newLogicalAddress & Constants.kAddressMask; - updatedEntry.Pending = entry.Pending; - updatedEntry.Tentative = false; - - var foundEntry = default(HashBucketEntry); - foundEntry.word = Interlocked.CompareExchange( - ref bucket->bucket_entries[slot], - updatedEntry.word, entry.word); - - if (foundEntry.word == entry.word) - { - pendingContext.logicalAddress = newLogicalAddress; - pendingContext.IsNewRecord = this.SecondaryIndexBroker.HasMutableIndexes; - status = OperationStatus.SUCCESS; - goto LatchRelease; - } - else - { - hlog.GetInfo(newPhysicalAddress).Invalid = true; - status = OperationStatus.RETRY_NOW; - goto LatchRelease; - } + status = CreateNewRecordUpsert(ref key, ref value, ref pendingContext, fasterSession, sessionCtx, bucket, slot, tag, entry, latestLogicalAddress); + goto LatchRelease; } -#endregion + #endregion -#region Create pending context - CreatePendingContext: + #region Create pending context + Debug.Assert(latchDestination == LatchDestination.CreatePendingContext); { pendingContext.type = OperationType.UPSERT; pendingContext.key = hlog.GetKeyContainer(ref key); @@ -514,9 +428,114 @@ internal OperationStatus InternalUpsert( return status; } - #endregion + private LatchDestination AcquireLatchUpsert(FasterExecutionContext sessionCtx, HashBucket* bucket, ref OperationStatus status, ref LatchOperation latchOperation, ref HashBucketEntry entry) + { + switch (sessionCtx.phase) + { + case Phase.PREPARE: + { + if (HashBucket.TryAcquireSharedLatch(bucket)) + { + // Set to release shared latch (default) + latchOperation = LatchOperation.Shared; + if (GetLatestRecordVersion(ref entry, sessionCtx.version) > sessionCtx.version) + { + status = OperationStatus.CPR_SHIFT_DETECTED; + return LatchDestination.CreatePendingContext; // Pivot Thread + } + break; // Normal Processing + } + else + { + status = OperationStatus.CPR_SHIFT_DETECTED; + return LatchDestination.CreatePendingContext; // Pivot Thread + } + } + case Phase.IN_PROGRESS: + { + if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) + { + if (HashBucket.TryAcquireExclusiveLatch(bucket)) + { + // Set to release exclusive latch (default) + latchOperation = LatchOperation.Exclusive; + return LatchDestination.CreateNewRecord; // Create a (v+1) record + } + else + { + status = OperationStatus.RETRY_LATER; + return LatchDestination.CreatePendingContext; // Go Pending + } + } + break; // Normal Processing + } + case Phase.WAIT_PENDING: + { + if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) + { + if (HashBucket.NoSharedLatches(bucket)) + { + return LatchDestination.CreateNewRecord; // Create a (v+1) record + } + else + { + status = OperationStatus.RETRY_LATER; + return LatchDestination.CreatePendingContext; // Go Pending + } + } + break; // Normal Processing + } + case Phase.WAIT_FLUSH: + { + if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) + { + return LatchDestination.CreateNewRecord; // Create a (v+1) record + } + break; // Normal Processing + } + default: + break; + } + return LatchDestination.NormalProcessing; + } + + private OperationStatus CreateNewRecordUpsert(ref Key key, ref Value value, ref PendingContext pendingContext, FasterSession fasterSession, FasterExecutionContext sessionCtx, HashBucket* bucket, int slot, ushort tag, HashBucketEntry entry, long latestLogicalAddress) where FasterSession : IFasterSession + { + var (actualSize, allocateSize) = hlog.GetRecordSize(ref key, ref value); + BlockAllocate(allocateSize, out long newLogicalAddress, sessionCtx, fasterSession); + var newPhysicalAddress = hlog.GetPhysicalAddress(newLogicalAddress); + RecordInfo.WriteInfo(ref hlog.GetInfo(newPhysicalAddress), + sessionCtx.version, + tombstone: false, invalidBit: false, + latestLogicalAddress); + hlog.Serialize(ref key, newPhysicalAddress); + fasterSession.SingleWriter(ref key, ref value, + ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); + + var updatedEntry = default(HashBucketEntry); + updatedEntry.Tag = tag; + updatedEntry.Address = newLogicalAddress & Constants.kAddressMask; + updatedEntry.Pending = entry.Pending; + updatedEntry.Tentative = false; + + var foundEntry = default(HashBucketEntry); + foundEntry.word = Interlocked.CompareExchange(ref bucket->bucket_entries[slot], updatedEntry.word, entry.word); + + if (foundEntry.word == entry.word) + { + pendingContext.logicalAddress = newLogicalAddress; + pendingContext.IsNewRecord = this.SecondaryIndexBroker.HasMutableIndexes; + return OperationStatus.SUCCESS; + } + + // CAS failed + hlog.GetInfo(newPhysicalAddress).Invalid = true; + return OperationStatus.RETRY_NOW; + } - #region RMW Operation +#endregion + +#region RMW Operation /// /// Read-Modify-Write Operation. Updates value of 'key' using 'input' and current value. @@ -566,11 +585,11 @@ internal OperationStatus InternalRMW( { var bucket = default(HashBucket*); var slot = default(int); - var logicalAddress = Constants.kInvalidAddress; var physicalAddress = default(long); var status = default(OperationStatus); var latchOperation = LatchOperation.None; var heldOperation = LatchOperation.None; + var latchDestination = LatchDestination.NormalProcessing; var hash = comparer.GetHashCode64(ref key); var tag = (ushort)((ulong)hash >> Constants.kHashTagShift); @@ -581,7 +600,7 @@ internal OperationStatus InternalRMW( #region Trace back for record in in-memory HybridLog var entry = default(HashBucketEntry); FindOrCreateTag(hash, tag, ref bucket, ref slot, ref entry, hlog.BeginAddress); - logicalAddress = entry.Address; + var logicalAddress = entry.Address; // For simplicity, we don't let RMW operations use read cache if (UseReadCache) @@ -595,247 +614,118 @@ internal OperationStatus InternalRMW( if (!comparer.Equals(ref key, ref hlog.GetKey(physicalAddress))) { logicalAddress = hlog.GetInfo(physicalAddress).PreviousAddress; - TraceBackForKeyMatch(ref key, logicalAddress, - hlog.HeadAddress, - out logicalAddress, - out physicalAddress); + TraceBackForKeyMatch(ref key, + logicalAddress, + hlog.HeadAddress, + out logicalAddress, + out physicalAddress); } } - #endregion +#endregion // Optimization for the most common case - if (sessionCtx.phase == Phase.REST && logicalAddress >= hlog.ReadOnlyAddress && !hlog.GetInfo(physicalAddress).Tombstone) + if (sessionCtx.phase == Phase.REST && logicalAddress >= hlog.ReadOnlyAddress) { - if (fasterSession.InPlaceUpdater(ref key, ref input, ref hlog.GetValue(physicalAddress), ref hlog.GetInfo(physicalAddress), logicalAddress)) - { + ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); + if (!recordInfo.Tombstone + && fasterSession.InPlaceUpdater(ref key, ref input, ref hlog.GetValue(physicalAddress), ref recordInfo, logicalAddress)) return OperationStatus.SUCCESS; - } goto CreateNewRecord; } #region Entry latch operation if (sessionCtx.phase != Phase.REST) { - switch (sessionCtx.phase) - { - case Phase.PREPARE: - { - Debug.Assert(pendingContext.heldLatch != LatchOperation.Exclusive); - if (pendingContext.heldLatch == LatchOperation.Shared || HashBucket.TryAcquireSharedLatch(bucket)) - { - // Set to release shared latch (default) - latchOperation = LatchOperation.Shared; - if (GetLatestRecordVersion(ref entry, sessionCtx.version) > sessionCtx.version) - { - status = OperationStatus.CPR_SHIFT_DETECTED; - goto CreateFailureContext; // Pivot Thread - } - break; // Normal Processing - } - else - { - status = OperationStatus.CPR_SHIFT_DETECTED; - goto CreateFailureContext; // Pivot Thread - } - } - case Phase.IN_PROGRESS: - { - if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) - { - Debug.Assert(pendingContext.heldLatch != LatchOperation.Shared); - if (pendingContext.heldLatch == LatchOperation.Exclusive || HashBucket.TryAcquireExclusiveLatch(bucket)) - { - // Set to release exclusive latch (default) - latchOperation = LatchOperation.Exclusive; - if (logicalAddress >= hlog.HeadAddress) - goto CreateNewRecord; // Create a (v+1) record - } - else - { - status = OperationStatus.RETRY_LATER; - goto CreateFailureContext; // Go Pending - } - } - break; // Normal Processing - } - case Phase.WAIT_PENDING: - { - if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) - { - if (HashBucket.NoSharedLatches(bucket)) - { - if (logicalAddress >= hlog.HeadAddress) - goto CreateNewRecord; // Create a (v+1) record - } - else - { - status = OperationStatus.RETRY_LATER; - goto CreateFailureContext; // Go Pending - } - } - break; // Normal Processing - } - case Phase.WAIT_FLUSH: - { - if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) - { - if (logicalAddress >= hlog.HeadAddress) - goto CreateNewRecord; // Create a (v+1) record - } - break; // Normal Processing - } - default: - break; - } + latchDestination = AcquireLatchRMW(pendingContext, sessionCtx, bucket, ref status, ref latchOperation, ref entry, logicalAddress); } -#endregion + #endregion Debug.Assert(GetLatestRecordVersion(ref entry, sessionCtx.version) <= sessionCtx.version); -#region Normal processing + #region Normal processing // Mutable Region: Update the record in-place - if (logicalAddress >= hlog.ReadOnlyAddress && !hlog.GetInfo(physicalAddress).Tombstone) + if (latchDestination == LatchDestination.NormalProcessing) { - if (FoldOverSnapshot) - { - Debug.Assert(hlog.GetInfo(physicalAddress).Version == sessionCtx.version); - } - - if (fasterSession.InPlaceUpdater(ref key, ref input, ref hlog.GetValue(physicalAddress), ref hlog.GetInfo(physicalAddress), logicalAddress)) + if (logicalAddress >= hlog.ReadOnlyAddress) { - status = OperationStatus.SUCCESS; - goto LatchRelease; // Release shared latch (if acquired) - } - } - - // Fuzzy Region: Must go pending due to lost-update anomaly - else if (logicalAddress >= hlog.SafeReadOnlyAddress && !hlog.GetInfo(physicalAddress).Tombstone) - { - status = OperationStatus.RETRY_LATER; - // Do not retain latch for pendings ops in relaxed CPR - if (!RelaxedCPR) - { - // Retain the shared latch (if acquired) - if (latchOperation == LatchOperation.Shared) + ref RecordInfo recordInfo = ref hlog.GetInfo(physicalAddress); + if (!recordInfo.Tombstone) { - heldOperation = latchOperation; - latchOperation = LatchOperation.None; - } - } - goto CreateFailureContext; // Go pending - } - - // Safe Read-Only Region: Create a record in the mutable region - else if (logicalAddress >= hlog.HeadAddress) - { - goto CreateNewRecord; - } + if (FoldOverSnapshot) + { + Debug.Assert(recordInfo.Version == sessionCtx.version); + } - // Disk Region: Need to issue async io requests - else if (logicalAddress >= hlog.BeginAddress) - { - status = OperationStatus.RECORD_ON_DISK; - // Do not retain latch for pendings ops in relaxed CPR - if (!RelaxedCPR) - { - // Retain the shared latch (if acquired) - if (latchOperation == LatchOperation.Shared) - { - heldOperation = latchOperation; - latchOperation = LatchOperation.None; + if (fasterSession.InPlaceUpdater(ref key, ref input, ref hlog.GetValue(physicalAddress), ref recordInfo, logicalAddress)) + { + status = OperationStatus.SUCCESS; + goto LatchRelease; // Release shared latch (if acquired) + } } } - goto CreateFailureContext; // Go pending - } - - // No record exists - create new - else - { - goto CreateNewRecord; - } - -#endregion -#region Create new record - CreateNewRecord: - { - if (logicalAddress >= hlog.HeadAddress && !hlog.GetInfo(physicalAddress).Tombstone) + // Fuzzy Region: Must go pending due to lost-update anomaly + else if (logicalAddress >= hlog.SafeReadOnlyAddress && !hlog.GetInfo(physicalAddress).Tombstone) { - if (!fasterSession.NeedCopyUpdate(ref key, ref input, ref hlog.GetValue(physicalAddress))) + status = OperationStatus.RETRY_LATER; + // Do not retain latch for pendings ops in relaxed CPR + if (!RelaxedCPR) { - status = OperationStatus.SUCCESS; - goto LatchRelease; + // Retain the shared latch (if acquired) + if (latchOperation == LatchOperation.Shared) + { + heldOperation = latchOperation; + latchOperation = LatchOperation.None; + } } + goto CreatePendingContext; // Go pending } - var (actualSize, allocatedSize) = (logicalAddress < hlog.BeginAddress) ? - hlog.GetInitialRecordSize(ref key, ref input, fasterSession) : - hlog.GetRecordSize(physicalAddress, ref input, fasterSession); - BlockAllocate(allocatedSize, out long newLogicalAddress, sessionCtx, fasterSession); - var newPhysicalAddress = hlog.GetPhysicalAddress(newLogicalAddress); - RecordInfo.WriteInfo(ref hlog.GetInfo(newPhysicalAddress), sessionCtx.version, - tombstone:false, invalidBit:false, - latestLogicalAddress); - hlog.Serialize(ref key, newPhysicalAddress); - - if (logicalAddress < hlog.BeginAddress) + // Safe Read-Only Region: Create a record in the mutable region + else if (logicalAddress >= hlog.HeadAddress) { - fasterSession.InitialUpdater(ref key, ref input, ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); - status = OperationStatus.NOTFOUND; + goto CreateNewRecord; } - else if (logicalAddress >= hlog.HeadAddress) + + // Disk Region: Need to issue async io requests + else if (logicalAddress >= hlog.BeginAddress) { - if (hlog.GetInfo(physicalAddress).Tombstone) - { - fasterSession.InitialUpdater(ref key, ref input, ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); - status = OperationStatus.NOTFOUND; - } - else + status = OperationStatus.RECORD_ON_DISK; + // Do not retain latch for pendings ops in relaxed CPR + if (!RelaxedCPR) { - fasterSession.CopyUpdater(ref key, ref input, - ref hlog.GetValue(physicalAddress), - ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); - status = OperationStatus.SUCCESS; + // Retain the shared latch (if acquired) + if (latchOperation == LatchOperation.Shared) + { + heldOperation = latchOperation; + latchOperation = LatchOperation.None; + } } + goto CreatePendingContext; // Go pending } + + // No record exists - create new else { - // ah, old record slipped onto disk - hlog.GetInfo(newPhysicalAddress).Invalid = true; - status = OperationStatus.RETRY_NOW; - goto LatchRelease; + goto CreateNewRecord; } + } - var updatedEntry = default(HashBucketEntry); - updatedEntry.Tag = tag; - updatedEntry.Address = newLogicalAddress & Constants.kAddressMask; - updatedEntry.Pending = entry.Pending; - updatedEntry.Tentative = false; - - var foundEntry = default(HashBucketEntry); - foundEntry.word = Interlocked.CompareExchange( - ref bucket->bucket_entries[slot], - updatedEntry.word, entry.word); + #endregion - if (foundEntry.word == entry.word) - { - pendingContext.logicalAddress = newLogicalAddress; - pendingContext.IsNewRecord = this.SecondaryIndexBroker.HasMutableIndexes; - goto LatchRelease; - } - else - { - // CAS failed - hlog.GetInfo(newPhysicalAddress).Invalid = true; - status = OperationStatus.RETRY_NOW; - goto LatchRelease; - } + #region Create new record + CreateNewRecord: + if (latchDestination != LatchDestination.CreatePendingContext) + { + status = CreateNewRecordRMW(ref key, ref input, ref pendingContext, fasterSession, sessionCtx, bucket, slot, logicalAddress, physicalAddress, tag, entry, latestLogicalAddress); + goto LatchRelease; } -#endregion + #endregion -#region Create failure context - CreateFailureContext: + #region Create failure context + CreatePendingContext: + Debug.Assert(latchDestination == LatchDestination.CreatePendingContext); { pendingContext.type = OperationType.RMW; pendingContext.key = hlog.GetKeyContainer(ref key); @@ -869,6 +759,152 @@ ref hlog.GetValue(physicalAddress), return status; } + private LatchDestination AcquireLatchRMW(PendingContext pendingContext, FasterExecutionContext sessionCtx, + HashBucket* bucket, ref OperationStatus status, ref LatchOperation latchOperation, ref HashBucketEntry entry, long logicalAddress) + { + switch (sessionCtx.phase) + { + case Phase.PREPARE: + { + Debug.Assert(pendingContext.heldLatch != LatchOperation.Exclusive); + if (pendingContext.heldLatch == LatchOperation.Shared || HashBucket.TryAcquireSharedLatch(bucket)) + { + // Set to release shared latch (default) + latchOperation = LatchOperation.Shared; + if (GetLatestRecordVersion(ref entry, sessionCtx.version) > sessionCtx.version) + { + status = OperationStatus.CPR_SHIFT_DETECTED; + return LatchDestination.CreatePendingContext; // Pivot Thread + } + break; // Normal Processing + } + else + { + status = OperationStatus.CPR_SHIFT_DETECTED; + return LatchDestination.CreatePendingContext; // Pivot Thread + } + } + case Phase.IN_PROGRESS: + { + if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) + { + Debug.Assert(pendingContext.heldLatch != LatchOperation.Shared); + if (pendingContext.heldLatch == LatchOperation.Exclusive || HashBucket.TryAcquireExclusiveLatch(bucket)) + { + // Set to release exclusive latch (default) + latchOperation = LatchOperation.Exclusive; + if (logicalAddress >= hlog.HeadAddress) + return LatchDestination.CreateNewRecord; // Create a (v+1) record + } + else + { + status = OperationStatus.RETRY_LATER; + return LatchDestination.CreatePendingContext; // Go Pending + } + } + break; // Normal Processing + } + case Phase.WAIT_PENDING: + { + if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) + { + if (HashBucket.NoSharedLatches(bucket)) + { + if (logicalAddress >= hlog.HeadAddress) + return LatchDestination.CreateNewRecord; // Create a (v+1) record + } + else + { + status = OperationStatus.RETRY_LATER; + return LatchDestination.CreatePendingContext; // Go Pending + } + } + break; // Normal Processing + } + case Phase.WAIT_FLUSH: + { + if (GetLatestRecordVersion(ref entry, sessionCtx.version) < sessionCtx.version) + { + if (logicalAddress >= hlog.HeadAddress) + return LatchDestination.CreateNewRecord; // Create a (v+1) record + } + break; // Normal Processing + } + default: + break; + } + return LatchDestination.NormalProcessing; + } + + private OperationStatus CreateNewRecordRMW(ref Key key, ref Input input, ref PendingContext pendingContext, FasterSession fasterSession, FasterExecutionContext sessionCtx, HashBucket* bucket, int slot, long logicalAddress, long physicalAddress, ushort tag, HashBucketEntry entry, long latestLogicalAddress) where FasterSession : IFasterSession + { + if (logicalAddress >= hlog.HeadAddress && !hlog.GetInfo(physicalAddress).Tombstone) + { + if (!fasterSession.NeedCopyUpdate(ref key, ref input, ref hlog.GetValue(physicalAddress))) + return OperationStatus.SUCCESS; + } + + var (actualSize, allocatedSize) = (logicalAddress < hlog.BeginAddress) ? + hlog.GetInitialRecordSize(ref key, ref input, fasterSession) : + hlog.GetRecordSize(physicalAddress, ref input, fasterSession); + BlockAllocate(allocatedSize, out long newLogicalAddress, sessionCtx, fasterSession); + var newPhysicalAddress = hlog.GetPhysicalAddress(newLogicalAddress); + RecordInfo.WriteInfo(ref hlog.GetInfo(newPhysicalAddress), sessionCtx.version, + tombstone: false, invalidBit: false, + latestLogicalAddress); + hlog.Serialize(ref key, newPhysicalAddress); + + OperationStatus status; + if (logicalAddress < hlog.BeginAddress) + { + fasterSession.InitialUpdater(ref key, ref input, ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); + status = OperationStatus.NOTFOUND; + } + else if (logicalAddress >= hlog.HeadAddress) + { + if (hlog.GetInfo(physicalAddress).Tombstone) + { + fasterSession.InitialUpdater(ref key, ref input, ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); + status = OperationStatus.NOTFOUND; + } + else + { + fasterSession.CopyUpdater(ref key, ref input, + ref hlog.GetValue(physicalAddress), + ref hlog.GetValue(newPhysicalAddress, newPhysicalAddress + actualSize)); + status = OperationStatus.SUCCESS; + } + } + else + { + // ah, old record slipped onto disk + hlog.GetInfo(newPhysicalAddress).Invalid = true; + return OperationStatus.RETRY_NOW; + } + + var updatedEntry = default(HashBucketEntry); + updatedEntry.Tag = tag; + updatedEntry.Address = newLogicalAddress & Constants.kAddressMask; + updatedEntry.Pending = entry.Pending; + updatedEntry.Tentative = false; + + var foundEntry = default(HashBucketEntry); + foundEntry.word = Interlocked.CompareExchange(ref bucket->bucket_entries[slot], updatedEntry.word, entry.word); + if (foundEntry.word == entry.word) + { + pendingContext.logicalAddress = newLogicalAddress; + pendingContext.IsNewRecord = this.SecondaryIndexBroker.HasMutableIndexes; + } + else + { + // CAS failed + hlog.GetInfo(newPhysicalAddress).Invalid = true; + status = OperationStatus.RETRY_NOW; + } + + return status; + } + #endregion #region Delete Operation From ab625acc2c6aad510eea05bb2ce9c0465dde3c8c Mon Sep 17 00:00:00 2001 From: Badrish Chandramouli Date: Tue, 2 Mar 2021 16:42:48 -0800 Subject: [PATCH 13/37] Update benchmark to start in unison. --- cs/benchmark/FasterYcsbBenchmark.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index 0d92b5a30..064adb14a 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -41,6 +41,7 @@ internal class FASTER_YcsbBenchmark const int kFileChunkSize = 4096; const long kChunkSize = 640; + readonly ManualResetEventSlim waiter = new ManualResetEventSlim(); readonly int threadCount; readonly int numaStyle; readonly string distribution; @@ -127,6 +128,8 @@ private void RunYcsb(int thread_idx) else Native32.AffinitizeThreadShardedNuma((uint)thread_idx, 2); // assuming two NUMA sockets + waiter.Wait(); + Stopwatch sw = new Stopwatch(); sw.Start(); @@ -269,17 +272,20 @@ public unsafe (double, double) Run() workers[idx] = new Thread(() => SetupYcsb(x)); } - sw.Start(); // Start threads. foreach (Thread worker in workers) { worker.Start(); } + + waiter.Set(); + sw.Start(); foreach (Thread worker in workers) { worker.Join(); } sw.Stop(); + waiter.Reset(); } double insertsPerSecond = storeWasRecovered ? 0 : ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; Console.WriteLine(YcsbGlobals.LoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); @@ -319,6 +325,7 @@ public unsafe (double, double) Run() worker.Start(); } + waiter.Set(); Stopwatch swatch = new Stopwatch(); swatch.Start(); @@ -350,6 +357,7 @@ public unsafe (double, double) Run() { worker.Join(); } + waiter.Reset(); #if DASHBOARD dash.Join(); @@ -371,6 +379,8 @@ private void SetupYcsb(int thread_idx) else Native32.AffinitizeThreadShardedNuma((uint)thread_idx, 2); // assuming two NUMA sockets + waiter.Wait(); + var session = store.For(functions).NewSession(null, kAffinitizedSession); #if DASHBOARD From 5ac4a8cdbfbb1c4196595c0740616cea2dc93925 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Wed, 3 Mar 2021 14:06:08 -0800 Subject: [PATCH 14/37] Refactor YCSB to clean up program.cs and duplicate LoadData and improve output --- cs/benchmark/ConcurrentDictionaryBenchmark.cs | 198 ++--------- cs/benchmark/FasterSpanByteYcsbBenchmark.cs | 256 ++++---------- cs/benchmark/FasterYcsbBenchmark.cs | 240 +++---------- cs/benchmark/Options.cs | 12 +- cs/benchmark/Program.cs | 149 +------- cs/benchmark/TestLoader.cs | 324 ++++++++++++++++++ cs/benchmark/TestStats.cs | 92 +++++ cs/benchmark/YcsbConstants.cs | 94 +++++ cs/benchmark/YcsbGlobals.cs | 84 ----- 9 files changed, 679 insertions(+), 770 deletions(-) create mode 100644 cs/benchmark/TestLoader.cs create mode 100644 cs/benchmark/TestStats.cs create mode 100644 cs/benchmark/YcsbConstants.cs delete mode 100644 cs/benchmark/YcsbGlobals.cs diff --git a/cs/benchmark/ConcurrentDictionaryBenchmark.cs b/cs/benchmark/ConcurrentDictionaryBenchmark.cs index 7e5d9442c..6cb0b398c 100644 --- a/cs/benchmark/ConcurrentDictionaryBenchmark.cs +++ b/cs/benchmark/ConcurrentDictionaryBenchmark.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -#pragma warning disable 0162 +#pragma warning disable CS0162 // Unreachable code detected -- when switching on YcsbConstants //#define DASHBOARD @@ -10,7 +10,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; @@ -32,12 +31,8 @@ public int GetHashCode(Key obj) } } - public unsafe class ConcurrentDictionary_YcsbBenchmark + internal unsafe class ConcurrentDictionary_YcsbBenchmark { - const int kFileChunkSize = 4096; - const long kChunkSize = 640; - - const int kRunSeconds = 30; const int kCheckpointSeconds = -1; readonly int threadCount; @@ -56,14 +51,14 @@ public unsafe class ConcurrentDictionary_YcsbBenchmark volatile bool done = false; Input* input_ptr; - public ConcurrentDictionary_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, int threadCount_, int numaStyle_, string distribution_, int readPercent_) + internal ConcurrentDictionary_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, TestLoader testLoader) { init_keys_ = i_keys_; txn_keys_ = t_keys_; - threadCount = threadCount_; - numaStyle = numaStyle_; - distribution = distribution_; - readPercent = readPercent_; + threadCount = testLoader.Options.ThreadCount; + numaStyle = testLoader.Options.NumaStyle; + distribution = testLoader.Distribution; + readPercent = testLoader.Options.ReadPercent; #if DASHBOARD statsWritten = new AutoResetEvent[threadCount]; @@ -82,7 +77,7 @@ public ConcurrentDictionary_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, int thre for (int i = 0; i < 8; i++) input_[i].value = i; - store = new ConcurrentDictionary(threadCount, YcsbGlobals.kMaxKey, new KeyComparer()); + store = new ConcurrentDictionary(threadCount, YcsbConstants.kMaxKey, new KeyComparer()); } public void Dispose() @@ -115,15 +110,15 @@ private void RunYcsb(int thread_idx) while (!done) { - long chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; - while (chunk_idx >= YcsbGlobals.kTxnCount) + long chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize; + while (chunk_idx >= YcsbConstants.kTxnCount) { - if (chunk_idx == YcsbGlobals.kTxnCount) + if (chunk_idx == YcsbConstants.kTxnCount) idx_ = 0; - chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; + chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize; } - for (long idx = chunk_idx; idx < chunk_idx + kChunkSize && !done; ++idx) + for (long idx = chunk_idx; idx < chunk_idx + YcsbConstants.kChunkSize && !done; ++idx) { Op op; int r = (int)rng.Generate(100); @@ -220,8 +215,8 @@ public unsafe (double, double) Run() } sw.Stop(); - double insertsPerSecond = ((double)YcsbGlobals.kInitCount / sw.ElapsedMilliseconds) * 1000; - Console.WriteLine(YcsbGlobals.LoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); + double insertsPerSecond = ((double)YcsbConstants.kInitCount / sw.ElapsedMilliseconds) * 1000; + Console.WriteLine(TestStats.GetLoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); idx_ = 0; @@ -244,12 +239,12 @@ public unsafe (double, double) Run() if (kCheckpointSeconds <= 0) { - Thread.Sleep(TimeSpan.FromSeconds(kRunSeconds)); + Thread.Sleep(TimeSpan.FromSeconds(YcsbConstants.kRunSeconds)); } else { int runSeconds = 0; - while (runSeconds < kRunSeconds) + while (runSeconds < YcsbConstants.kRunSeconds) { Thread.Sleep(TimeSpan.FromSeconds(kCheckpointSeconds)); runSeconds += kCheckpointSeconds; @@ -275,8 +270,8 @@ public unsafe (double, double) Run() double seconds = swatch.ElapsedMilliseconds / 1000.0; double opsPerSecond = total_ops_done / seconds; - Console.WriteLine(YcsbGlobals.TotalOpsString(total_ops_done, seconds)); - Console.WriteLine(YcsbGlobals.StatsLine(StatsLine.Iteration, YcsbGlobals.OpsPerSec, opsPerSecond)); + Console.WriteLine(TestStats.GetTotalOpsString(total_ops_done, seconds)); + Console.WriteLine(TestStats.GetStatsLine(StatsLineNum.Iteration, YcsbConstants.OpsPerSec, opsPerSecond)); return (insertsPerSecond, opsPerSecond); } @@ -296,11 +291,11 @@ private void SetupYcsb(int thread_idx) Value value = default; - for (long chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; - chunk_idx < YcsbGlobals.kInitCount; - chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize) + for (long chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize; + chunk_idx < YcsbConstants.kInitCount; + chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize) { - for (long idx = chunk_idx; idx < chunk_idx + kChunkSize; ++idx) + for (long idx = chunk_idx; idx < chunk_idx + YcsbConstants.kChunkSize; ++idx) { Key key = init_keys_[idx]; @@ -396,153 +391,16 @@ void DoContinuousMeasurements() #region Load Data - private static void LoadDataFromFile(string filePath, string distribution, out Key[] i_keys, out Key[] t_keys) + internal static void CreateKeyVectors(out Key[] i_keys, out Key[] t_keys) { - string init_filename = filePath + "/load_" + distribution + "_250M_raw.dat"; - string txn_filename = filePath + "/run_" + distribution + "_250M_1000M_raw.dat"; - - long count = 0; - using (FileStream stream = File.Open(init_filename, FileMode.Open, FileAccess.Read, - FileShare.Read)) - { - Console.WriteLine("loading keys from " + init_filename + " into memory..."); - i_keys = new Key[YcsbGlobals.kInitCount]; - - byte[] chunk = new byte[kFileChunkSize]; - GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); - byte* chunk_ptr = (byte*)chunk_handle.AddrOfPinnedObject(); - - long offset = 0; - - while (true) - { - stream.Position = offset; - int size = stream.Read(chunk, 0, kFileChunkSize); - for (int idx = 0; idx < size; idx += 8) - { - i_keys[count].value = *(long*)(chunk_ptr + idx); - ++count; - } - if (size == kFileChunkSize) - offset += kFileChunkSize; - else - break; - - if (count == YcsbGlobals.kInitCount) - break; - } - - if (count != YcsbGlobals.kInitCount) - { - throw new InvalidDataException("Init file load fail!"); - } - } - - Console.WriteLine("loaded " + YcsbGlobals.kInitCount + " keys."); - - - using (FileStream stream = File.Open(txn_filename, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - byte[] chunk = new byte[kFileChunkSize]; - GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); - byte* chunk_ptr = (byte*)chunk_handle.AddrOfPinnedObject(); - - Console.WriteLine("loading txns from " + txn_filename + " into memory..."); - - t_keys = new Key[YcsbGlobals.kTxnCount]; - - count = 0; - long offset = 0; - - while (true) - { - stream.Position = offset; - int size = stream.Read(chunk, 0, kFileChunkSize); - for (int idx = 0; idx < size; idx += 8) - { - t_keys[count] = *(Key*)(chunk_ptr + idx); - ++count; - } - if (size == kFileChunkSize) - offset += kFileChunkSize; - else - break; - - if (count == YcsbGlobals.kTxnCount) - break; - } - - if (count != YcsbGlobals.kTxnCount) - { - throw new InvalidDataException("Txn file load fail!" + count + ":" + YcsbGlobals.kTxnCount); - } - } - - Console.WriteLine("loaded " + YcsbGlobals.kTxnCount + " txns."); + i_keys = new Key[YcsbConstants.kInitCount]; + t_keys = new Key[YcsbConstants.kTxnCount]; } - - public static void LoadData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) + internal class KeySetter : IKeySetter { - if (YcsbGlobals.kUseSyntheticData || YcsbGlobals.kUseSmallData) - { - if (!YcsbGlobals.kUseSyntheticData) - Console.WriteLine("WARNING: Forcing synthetic data due to kSmallData"); - LoadSyntheticData(distribution, seed, out i_keys, out t_keys); - return; - } - - string filePath = "C:\\ycsb_files"; - - if (!Directory.Exists(filePath)) - { - filePath = "D:\\ycsb_files"; - } - if (!Directory.Exists(filePath)) - { - filePath = "E:\\ycsb_files"; - } - - if (Directory.Exists(filePath)) - { - LoadDataFromFile(filePath, distribution, out i_keys, out t_keys); - return; - } - else - { - Console.WriteLine("WARNING: Could not find YCSB directory, loading synthetic data instead"); - LoadSyntheticData(distribution, seed, out i_keys, out t_keys); - } + public void Set(Key[] vector, long idx, long value) => vector[idx].value = value; } - private static void LoadSyntheticData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) - { - Console.WriteLine($"Loading synthetic data ({distribution} distribution), seed = {seed}"); - - i_keys = new Key[YcsbGlobals.kInitCount]; - long val = 0; - for (int idx = 0; idx < YcsbGlobals.kInitCount; idx++) - { - i_keys[idx] = new Key { value = val++ }; - } - - Console.WriteLine("loaded " + YcsbGlobals.kInitCount + " keys."); - - RandomGenerator generator = new RandomGenerator(seed); - var zipf = new ZipfGenerator(generator, (int)YcsbGlobals.kInitCount, theta: 0.99); - - t_keys = new Key[YcsbGlobals.kTxnCount]; - - for (int idx = 0; idx < YcsbGlobals.kTxnCount; idx++) - { - var rand = distribution == YcsbGlobals.UniformDist ? (long)generator.Generate64(YcsbGlobals.kInitCount) : zipf.Next(); - t_keys[idx] = new Key { value = rand }; - } - - Console.WriteLine("loaded " + YcsbGlobals.kTxnCount + " txns."); - - } #endregion - - } } diff --git a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs index 0220fee22..7b3d8fc50 100644 --- a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs +++ b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -#pragma warning disable 0162 +#pragma warning disable CS0162 // Unreachable code detected -- when switching on YcsbConstants // Define below to enable continuous performance report for dashboard // #define DASHBOARD @@ -9,8 +9,6 @@ using FASTER.core; using System; using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; using System.Threading; namespace FASTER.benchmark @@ -35,15 +33,13 @@ public class FasterSpanByteYcsbBenchmark // *** // Ensure sizes are aligned to chunk sizes - const long kInitCount = kChunkSize * (YcsbGlobals.kInitCount / kChunkSize); - const long kTxnCount = kChunkSize * (YcsbGlobals.kTxnCount / kChunkSize); + const long kInitCount = YcsbConstants.kChunkSize * (YcsbConstants.kInitCount / YcsbConstants.kChunkSize); + const long kTxnCount = YcsbConstants.kChunkSize * (YcsbConstants.kTxnCount / YcsbConstants.kChunkSize); public const int kKeySize = 16; public const int kValueSize = 100; - const int kFileChunkSize = 4096; - const long kChunkSize = 640; - + readonly ManualResetEventSlim waiter = new ManualResetEventSlim(); readonly int threadCount; readonly int numaStyle; readonly string distribution; @@ -62,19 +58,22 @@ public class FasterSpanByteYcsbBenchmark long total_ops_done = 0; volatile bool done = false; - internal FasterSpanByteYcsbBenchmark(KeySpanByte[] i_keys_, KeySpanByte[] t_keys_, int threadCount_, int numaStyle_, string distribution_, int readPercent_, - BackupMode backupMode_, LockImpl lockImpl_, SecondaryIndexType secondaryIndexType_) + internal FasterSpanByteYcsbBenchmark(KeySpanByte[] i_keys_, KeySpanByte[] t_keys_, TestLoader testLoader) { + // Pin loading thread if it is not used for checkpointing + if (kPeriodicCheckpointMilliseconds <= 0) + Native32.AffinitizeThreadShardedNuma(0, 2); + init_keys_ = i_keys_; txn_keys_ = t_keys_; - threadCount = threadCount_; - numaStyle = numaStyle_; - distribution = distribution_; - readPercent = readPercent_; - backupMode = backupMode_; - var lockImpl = lockImpl_; + threadCount = testLoader.Options.ThreadCount; + numaStyle = testLoader.Options.NumaStyle; + distribution = testLoader.Distribution; + readPercent = testLoader.Options.ReadPercent; + backupMode = testLoader.BackupMode; + var lockImpl = testLoader.LockImpl; functions = new FunctionsSB(lockImpl != LockImpl.None); - secondaryIndexType = secondaryIndexType_; + secondaryIndexType = testLoader.SecondaryIndexType; #if DASHBOARD statsWritten = new AutoResetEvent[threadCount]; @@ -96,13 +95,13 @@ internal FasterSpanByteYcsbBenchmark(KeySpanByte[] i_keys_, KeySpanByte[] t_keys var path = "D:\\data\\FasterYcsbBenchmark\\"; device = Devices.CreateLogDevice(path + "hlog", preallocateFile: true); - if (YcsbGlobals.kSmallMemoryLog) + if (YcsbConstants.kSmallMemoryLog) store = new FasterKV - (YcsbGlobals.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, + (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); else store = new FasterKV - (YcsbGlobals.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, MemorySizeBits = 35 }, + (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, MemorySizeBits = 35 }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); if (secondaryIndexType.HasFlag(SecondaryIndexType.Key)) @@ -126,10 +125,11 @@ private void RunYcsb(int thread_idx) else Native32.AffinitizeThreadShardedNuma((uint)thread_idx, 2); // assuming two NUMA sockets + waiter.Wait(); + Stopwatch sw = new Stopwatch(); sw.Start(); - Span value = stackalloc byte[kValueSize]; Span input = stackalloc byte[kValueSize]; Span output = stackalloc byte[kValueSize]; @@ -152,15 +152,15 @@ private void RunYcsb(int thread_idx) while (!done) { - long chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; + long chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize; while (chunk_idx >= kTxnCount) { if (chunk_idx == kTxnCount) idx_ = 0; - chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; + chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize; } - for (long idx = chunk_idx; idx < chunk_idx + kChunkSize && !done; ++idx) + for (long idx = chunk_idx; idx < chunk_idx + YcsbConstants.kChunkSize && !done; ++idx) { Op op; int r = (int)rng.Generate(100); @@ -251,17 +251,25 @@ public unsafe (double, double) Run() var storeWasRecovered = false; if (this.backupMode.HasFlag(BackupMode.Restore) && kPeriodicCheckpointMilliseconds <= 0) { - sw.Start(); - try + if (!YcsbConstants.kUseSmallData) { - Console.WriteLine("Recovering FasterKV for fast restart"); - store.Recover(); - storeWasRecovered = true; - } catch (Exception) + Console.WriteLine("Skipping Recover() for kSmallData"); + } + else { - Console.WriteLine("Unable to recover prior store"); + sw.Start(); + try + { + Console.WriteLine("Recovering FasterKV for fast restart"); + store.Recover(); + storeWasRecovered = true; + } + catch (Exception) + { + Console.WriteLine("Unable to recover prior store"); + } + sw.Stop(); } - sw.Stop(); } if (!storeWasRecovered) { @@ -273,21 +281,24 @@ public unsafe (double, double) Run() workers[idx] = new Thread(() => SetupYcsb(x)); } - sw.Start(); // Start threads. foreach (Thread worker in workers) { worker.Start(); } + + waiter.Set(); + sw.Start(); foreach (Thread worker in workers) { worker.Join(); } sw.Stop(); + waiter.Reset(); } double insertsPerSecond = storeWasRecovered ? 0 : ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; - Console.WriteLine(YcsbGlobals.LoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); - Console.WriteLine(YcsbGlobals.AddressesLine(AddressLine.Before, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); + Console.WriteLine(TestStats.GetLoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); + Console.WriteLine(TestStats.GetAddressesLine(AddressLineNum.Before, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); long startTailAddress = store.Log.TailAddress; Console.WriteLine("Start tail address = " + startTailAddress); @@ -326,17 +337,18 @@ public unsafe (double, double) Run() worker.Start(); } + waiter.Set(); Stopwatch swatch = new Stopwatch(); swatch.Start(); if (kPeriodicCheckpointMilliseconds <= 0) { - Thread.Sleep(TimeSpan.FromSeconds(YcsbGlobals.kRunSeconds)); + Thread.Sleep(TimeSpan.FromSeconds(YcsbConstants.kRunSeconds)); } else { var checkpointTaken = 0; - while (swatch.ElapsedMilliseconds < 1000 * YcsbGlobals.kRunSeconds) + while (swatch.ElapsedMilliseconds < 1000 * YcsbConstants.kRunSeconds) { if (checkpointTaken < swatch.ElapsedMilliseconds / kPeriodicCheckpointMilliseconds) { @@ -357,17 +369,18 @@ public unsafe (double, double) Run() { worker.Join(); } + waiter.Reset(); #if DASHBOARD dash.Join(); #endif double seconds = swatch.ElapsedMilliseconds / 1000.0; - Console.WriteLine(YcsbGlobals.AddressesLine(AddressLine.After, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); + Console.WriteLine(TestStats.GetAddressesLine(AddressLineNum.After, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); double opsPerSecond = total_ops_done / seconds; - Console.WriteLine(YcsbGlobals.TotalOpsString(total_ops_done, seconds)); - Console.WriteLine(YcsbGlobals.StatsLine(StatsLine.Iteration, YcsbGlobals.OpsPerSec, opsPerSecond)); + Console.WriteLine(TestStats.GetTotalOpsString(total_ops_done, seconds)); + Console.WriteLine(TestStats.GetStatsLine(StatsLineNum.Iteration, YcsbConstants.OpsPerSec, opsPerSecond)); return (insertsPerSecond, opsPerSecond); } @@ -378,6 +391,8 @@ private void SetupYcsb(int thread_idx) else Native32.AffinitizeThreadShardedNuma((uint)thread_idx, 2); // assuming two NUMA sockets + waiter.Wait(); + var session = store.For(functions).NewSession(null, kAffinitizedSession); #if DASHBOARD @@ -390,11 +405,11 @@ private void SetupYcsb(int thread_idx) Span value = stackalloc byte[kValueSize]; ref SpanByte _value = ref SpanByte.Reinterpret(value); - for (long chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; + for (long chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize; chunk_idx < kInitCount; - chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize) + chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize) { - for (long idx = chunk_idx; idx < chunk_idx + kChunkSize; ++idx) + for (long idx = chunk_idx; idx < chunk_idx + YcsbConstants.kChunkSize; ++idx) { if (idx % 256 == 0) { @@ -494,162 +509,21 @@ void DoContinuousMeasurements() #region Load Data - private static unsafe void LoadDataFromFile(string filePath, string distribution, out KeySpanByte[] i_keys, out KeySpanByte[] t_keys) + internal static void CreateKeyVectors(out KeySpanByte[] i_keys, out KeySpanByte[] t_keys) { - string init_filename = filePath + "/load_" + distribution + "_250M_raw.dat"; - string txn_filename = filePath + "/run_" + distribution + "_250M_1000M_raw.dat"; - - long count = 0; - using (FileStream stream = File.Open(init_filename, FileMode.Open, FileAccess.Read, - FileShare.Read)) - { - Console.WriteLine("loading keys from " + init_filename + " into memory..."); - i_keys = new KeySpanByte[kInitCount]; - - byte[] chunk = new byte[kFileChunkSize]; - GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); - byte* chunk_ptr = (byte*)chunk_handle.AddrOfPinnedObject(); - - long offset = 0; - - while (true) - { - stream.Position = offset; - int size = stream.Read(chunk, 0, kFileChunkSize); - for (int idx = 0; idx < size; idx += 8) - { - i_keys[count].length = kKeySize - 4; - i_keys[count].value = *(long*)(chunk_ptr + idx); - ++count; - if (count == kInitCount) - break; - } - if (size == kFileChunkSize) - offset += kFileChunkSize; - else - break; - - if (count == kInitCount) - break; - } - - if (count != kInitCount) - { - throw new InvalidDataException("Init file load fail!"); - } - - chunk_handle.Free(); - } - - Console.WriteLine("loaded " + kInitCount + " keys."); - - - using (FileStream stream = File.Open(txn_filename, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - byte[] chunk = new byte[kFileChunkSize]; - GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); - byte* chunk_ptr = (byte*)chunk_handle.AddrOfPinnedObject(); - - Console.WriteLine("loading txns from " + txn_filename + " into memory..."); - - t_keys = new KeySpanByte[kTxnCount]; - - count = 0; - long offset = 0; - - while (true) - { - stream.Position = offset; - int size = stream.Read(chunk, 0, kFileChunkSize); - for (int idx = 0; idx < size; idx += 8) - { - t_keys[count].length = kKeySize - 4; - t_keys[count].value = *(long*)(chunk_ptr + idx); - ++count; - if (count == kTxnCount) - break; - } - if (size == kFileChunkSize) - offset += kFileChunkSize; - else - break; - - if (count == kTxnCount) - break; - } - - if (count != kTxnCount) - { - throw new InvalidDataException("Txn file load fail!" + count + ":" + kTxnCount); - } - - chunk_handle.Free(); - } - - Console.WriteLine("loaded " + kTxnCount + " txns."); + i_keys = new KeySpanByte[kInitCount]; + t_keys = new KeySpanByte[kTxnCount]; } - public static void LoadData(string distribution, uint seed, out KeySpanByte[] i_keys, out KeySpanByte[] t_keys) + internal class KeySetter : IKeySetter { - if (YcsbGlobals.kUseSyntheticData || YcsbGlobals.kUseSmallData) - { - if (!YcsbGlobals.kUseSyntheticData) - Console.WriteLine("WARNING: Forcing synthetic data due to kSmallData"); - LoadSyntheticData(distribution, seed, out i_keys, out t_keys); - return; - } - - string filePath = "C:\\ycsb_files"; - - if (!Directory.Exists(filePath)) - { - filePath = "D:\\ycsb_files"; - } - if (!Directory.Exists(filePath)) - { - filePath = "E:\\ycsb_files"; - } - - if (Directory.Exists(filePath)) + public unsafe void Set(KeySpanByte[] vector, long idx, long value) { - LoadDataFromFile(filePath, distribution, out i_keys, out t_keys); - } - else - { - Console.WriteLine("WARNING: Could not find YCSB directory, loading synthetic data instead"); - LoadSyntheticData(distribution, seed, out i_keys, out t_keys); + vector[idx].length = kKeySize - 4; + vector[idx].value = value; } } - private static void LoadSyntheticData(string distribution, uint seed, out KeySpanByte[] i_keys, out KeySpanByte[] t_keys) - { - Console.WriteLine($"Loading synthetic data ({distribution} distribution), seed = {seed}"); - - i_keys = new KeySpanByte[kInitCount]; - long val = 0; - for (int idx = 0; idx < kInitCount; idx++) - { - i_keys[idx] = new KeySpanByte { length = kKeySize - 4, value = val++ }; - } - - Console.WriteLine("loaded " + kInitCount + " keys."); - - RandomGenerator generator = new RandomGenerator(seed); - var zipf = new ZipfGenerator(generator, (int)kInitCount, theta: 0.99); - - t_keys = new KeySpanByte[kTxnCount]; - - for (int idx = 0; idx < kTxnCount; idx++) - { - var rand = distribution == YcsbGlobals.UniformDist ? (long)generator.Generate64(kInitCount) : zipf.Next(); - t_keys[idx] = new KeySpanByte { length = kKeySize - 4, value = rand }; - } - - Console.WriteLine("loaded " + kTxnCount + " txns."); - - } #endregion - - } } \ No newline at end of file diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index 064adb14a..0457c1dff 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -#pragma warning disable 0162 +#pragma warning disable CS0162 // Unreachable code detected -- when switching on YcsbConstants // Define below to enable continuous performance report for dashboard // #define DASHBOARD @@ -9,8 +9,6 @@ using FASTER.core; using System; using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; using System.Threading; namespace FASTER.benchmark @@ -20,7 +18,7 @@ internal class FASTER_YcsbBenchmark // *** Use these to backup and recover database for fast benchmark repeat runs // Use BackupMode.Backup to create the backup, unless it was recovered during BackupMode.Recover // Use BackupMode.Restore for fast subsequent runs - // Does NOT work when periodic checkpointing is turned on + // Does NOT work when periodic checkpointing or kUseSmallData is turned on readonly BackupMode backupMode; // *** @@ -35,11 +33,8 @@ internal class FASTER_YcsbBenchmark #endif // Ensure sizes are aligned to chunk sizes - const long kInitCount = kChunkSize * (YcsbGlobals.kInitCount / kChunkSize); - const long kTxnCount = kChunkSize * (YcsbGlobals.kTxnCount / kChunkSize); - - const int kFileChunkSize = 4096; - const long kChunkSize = 640; + const long kInitCount = YcsbConstants.kChunkSize * (YcsbConstants.kInitCount / YcsbConstants.kChunkSize); + const long kTxnCount = YcsbConstants.kChunkSize * (YcsbConstants.kTxnCount / YcsbConstants.kChunkSize); readonly ManualResetEventSlim waiter = new ManualResetEventSlim(); readonly int threadCount; @@ -60,8 +55,7 @@ internal class FASTER_YcsbBenchmark long total_ops_done = 0; volatile bool done = false; - internal FASTER_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, int threadCount_, int numaStyle_, string distribution_, int readPercent_, - BackupMode backupMode_, LockImpl lockImpl_, SecondaryIndexType secondaryIndexType_) + internal FASTER_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, TestLoader testLoader) { // Pin loading thread if it is not used for checkpointing if (kPeriodicCheckpointMilliseconds <= 0) @@ -69,14 +63,14 @@ internal FASTER_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, int threadCount_, in init_keys_ = i_keys_; txn_keys_ = t_keys_; - threadCount = threadCount_; - numaStyle = numaStyle_; - distribution = distribution_; - readPercent = readPercent_; - backupMode = backupMode_; - var lockImpl = lockImpl_; + threadCount = testLoader.Options.ThreadCount; + numaStyle = testLoader.Options.NumaStyle; + distribution = testLoader.Distribution; + readPercent = testLoader.Options.ReadPercent; + backupMode = testLoader.BackupMode; + var lockImpl = testLoader.LockImpl; functions = new Functions(lockImpl != LockImpl.None); - secondaryIndexType = secondaryIndexType_; + secondaryIndexType = testLoader.SecondaryIndexType; #if DASHBOARD statsWritten = new AutoResetEvent[threadCount]; @@ -98,13 +92,13 @@ internal FASTER_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, int threadCount_, in var path = "D:\\data\\FasterYcsbBenchmark\\"; device = Devices.CreateLogDevice(path + "hlog", preallocateFile: true); - if (YcsbGlobals.kSmallMemoryLog) + if (YcsbConstants.kSmallMemoryLog) store = new FasterKV - (YcsbGlobals.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, + (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); else store = new FasterKV - (YcsbGlobals.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true }, + (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true }, new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); if (secondaryIndexType.HasFlag(SecondaryIndexType.Key)) @@ -133,7 +127,6 @@ private void RunYcsb(int thread_idx) Stopwatch sw = new Stopwatch(); sw.Start(); - Value value = default; Input input = default; Output output = default; @@ -152,15 +145,15 @@ private void RunYcsb(int thread_idx) while (!done) { - long chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; + long chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize; while (chunk_idx >= kTxnCount) { if (chunk_idx == kTxnCount) idx_ = 0; - chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; + chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize; } - for (long idx = chunk_idx; idx < chunk_idx + kChunkSize && !done; ++idx) + for (long idx = chunk_idx; idx < chunk_idx + YcsbConstants.kChunkSize && !done; ++idx) { Op op; int r = (int)rng.Generate(100); @@ -249,18 +242,26 @@ public unsafe (double, double) Run() var storeWasRecovered = false; if (this.backupMode.HasFlag(BackupMode.Restore) && kPeriodicCheckpointMilliseconds <= 0) { - Console.WriteLine("Recovering store for fast restart"); - sw.Start(); - try + if (!YcsbConstants.kUseSmallData) { - Console.WriteLine("Recovering FasterKV for fast restart"); - store.Recover(); - storeWasRecovered = true; - } catch (Exception) + Console.WriteLine("Skipping Recover() for kSmallData"); + } + else { - Console.WriteLine("Unable to recover prior store"); + Console.WriteLine("Recovering store for fast restart"); + sw.Start(); + try + { + Console.WriteLine("Recovering FasterKV for fast restart"); + store.Recover(); + storeWasRecovered = true; + } + catch (Exception) + { + Console.WriteLine("Unable to recover prior store"); + } + sw.Stop(); } - sw.Stop(); } if (!storeWasRecovered) { @@ -288,8 +289,8 @@ public unsafe (double, double) Run() waiter.Reset(); } double insertsPerSecond = storeWasRecovered ? 0 : ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; - Console.WriteLine(YcsbGlobals.LoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); - Console.WriteLine(YcsbGlobals.AddressesLine(AddressLine.Before, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); + Console.WriteLine(TestStats.GetLoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); + Console.WriteLine(TestStats.GetAddressesLine(AddressLineNum.Before, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); if (!storeWasRecovered && this.backupMode.HasFlag(BackupMode.Backup) && kPeriodicCheckpointMilliseconds <= 0) { @@ -331,12 +332,12 @@ public unsafe (double, double) Run() if (kPeriodicCheckpointMilliseconds <= 0) { - Thread.Sleep(TimeSpan.FromSeconds(YcsbGlobals.kRunSeconds)); + Thread.Sleep(TimeSpan.FromSeconds(YcsbConstants.kRunSeconds)); } else { var checkpointTaken = 0; - while (swatch.ElapsedMilliseconds < 1000 * YcsbGlobals.kRunSeconds) + while (swatch.ElapsedMilliseconds < 1000 * YcsbConstants.kRunSeconds) { if (checkpointTaken < swatch.ElapsedMilliseconds / kPeriodicCheckpointMilliseconds) { @@ -364,11 +365,11 @@ public unsafe (double, double) Run() #endif double seconds = swatch.ElapsedMilliseconds / 1000.0; - Console.WriteLine(YcsbGlobals.AddressesLine(AddressLine.After, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); + Console.WriteLine(TestStats.GetAddressesLine(AddressLineNum.After, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); double opsPerSecond = total_ops_done / seconds; - Console.WriteLine(YcsbGlobals.TotalOpsString(total_ops_done, seconds)); - Console.WriteLine(YcsbGlobals.StatsLine(StatsLine.Iteration, YcsbGlobals.OpsPerSec, opsPerSecond)); + Console.WriteLine(TestStats.GetTotalOpsString(total_ops_done, seconds)); + Console.WriteLine(TestStats.GetStatsLine(StatsLineNum.Iteration, YcsbConstants.OpsPerSec, opsPerSecond)); return (insertsPerSecond, opsPerSecond); } @@ -392,11 +393,11 @@ private void SetupYcsb(int thread_idx) Value value = default; - for (long chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize; + for (long chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize; chunk_idx < kInitCount; - chunk_idx = Interlocked.Add(ref idx_, kChunkSize) - kChunkSize) + chunk_idx = Interlocked.Add(ref idx_, YcsbConstants.kChunkSize) - YcsbConstants.kChunkSize) { - for (long idx = chunk_idx; idx < chunk_idx + kChunkSize; ++idx) + for (long idx = chunk_idx; idx < chunk_idx + YcsbConstants.kChunkSize; ++idx) { if (idx % 256 == 0) { @@ -496,156 +497,17 @@ void DoContinuousMeasurements() #region Load Data - private static unsafe void LoadDataFromFile(string filePath, string distribution, out Key[] i_keys, out Key[] t_keys) + internal static void CreateKeyVectors(out Key[] i_keys, out Key[] t_keys) { - string init_filename = filePath + "/load_" + distribution + "_250M_raw.dat"; - string txn_filename = filePath + "/run_" + distribution + "_250M_1000M_raw.dat"; - - long count = 0; - using (FileStream stream = File.Open(init_filename, FileMode.Open, FileAccess.Read, - FileShare.Read)) - { - Console.WriteLine("loading keys from " + init_filename + " into memory..."); - i_keys = new Key[kInitCount]; - - byte[] chunk = new byte[kFileChunkSize]; - GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); - byte* chunk_ptr = (byte*)chunk_handle.AddrOfPinnedObject(); - - long offset = 0; - - while (true) - { - stream.Position = offset; - int size = stream.Read(chunk, 0, kFileChunkSize); - for (int idx = 0; idx < size; idx += 8) - { - i_keys[count].value = *(long*)(chunk_ptr + idx); - ++count; - if (count == kInitCount) - break; - } - if (size == kFileChunkSize) - offset += kFileChunkSize; - else - break; - - if (count == kInitCount) - break; - } - - if (count != kInitCount) - { - throw new InvalidDataException("Init file load fail!"); - } - - chunk_handle.Free(); - } - - Console.WriteLine("loaded " + kInitCount + " keys."); - - - using (FileStream stream = File.Open(txn_filename, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - byte[] chunk = new byte[kFileChunkSize]; - GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); - byte* chunk_ptr = (byte*)chunk_handle.AddrOfPinnedObject(); - - Console.WriteLine("loading txns from " + txn_filename + " into memory..."); - - t_keys = new Key[kTxnCount]; - - count = 0; - long offset = 0; - - while (true) - { - stream.Position = offset; - int size = stream.Read(chunk, 0, kFileChunkSize); - for (int idx = 0; idx < size; idx += 8) - { - t_keys[count].value = *(long*)(chunk_ptr + idx); - ++count; - if (count == kTxnCount) - break; - } - if (size == kFileChunkSize) - offset += kFileChunkSize; - else - break; - - if (count == kTxnCount) - break; - } - - if (count != kTxnCount) - { - throw new InvalidDataException("Txn file load fail!" + count + ":" + kTxnCount); - } - - chunk_handle.Free(); - } - - Console.WriteLine("loaded " + kTxnCount + " txns."); + i_keys = new Key[kInitCount]; + t_keys = new Key[kTxnCount]; } - public static void LoadData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) + internal class KeySetter : IKeySetter { - if (YcsbGlobals.kUseSyntheticData || YcsbGlobals.kUseSmallData) - { - if (!YcsbGlobals.kUseSyntheticData) - Console.WriteLine("WARNING: Forcing synthetic data due to kSmallData"); - LoadSyntheticData(distribution, seed, out i_keys, out t_keys); - return; - } - - string filePath = "C:\\ycsb_files"; - - if (!Directory.Exists(filePath)) - { - filePath = "D:\\ycsb_files"; - } - if (!Directory.Exists(filePath)) - { - filePath = "E:\\ycsb_files"; - } - - if (Directory.Exists(filePath)) - { - LoadDataFromFile(filePath, distribution, out i_keys, out t_keys); - } - else - { - Console.WriteLine("WARNING: Could not find YCSB directory, loading synthetic data instead"); - LoadSyntheticData(distribution, seed, out i_keys, out t_keys); - } + public void Set(Key[] vector, long idx, long value) => vector[idx].value = value; } - private static void LoadSyntheticData(string distribution, uint seed, out Key[] i_keys, out Key[] t_keys) - { - Console.WriteLine($"Loading synthetic data ({distribution} distribution), seed = {seed}"); - - i_keys = new Key[kInitCount]; - long val = 0; - for (int idx = 0; idx < kInitCount; idx++) - { - i_keys[idx] = new Key { value = val++ }; - } - - Console.WriteLine("loaded " + kInitCount + " keys."); - - RandomGenerator generator = new RandomGenerator(seed); - var zipf = new ZipfGenerator(generator, (int)kInitCount, theta:0.99); - - t_keys = new Key[kTxnCount]; - for (int idx = 0; idx < kTxnCount; idx++) - { - var rand = distribution == YcsbGlobals.UniformDist ? (long)generator.Generate64(kInitCount) : zipf.Next(); - t_keys[idx] = new Key { value = rand}; - } - - Console.WriteLine("loaded " + kTxnCount + " txns."); - } #endregion } -} \ No newline at end of file +} diff --git a/cs/benchmark/Options.cs b/cs/benchmark/Options.cs index 41fd56b43..2ba53eceb 100644 --- a/cs/benchmark/Options.cs +++ b/cs/benchmark/Options.cs @@ -54,7 +54,7 @@ class Options HelpText = "Percentage of reads (-1 for 100% read-modify-write")] public int ReadPercent { get; set; } - [Option('d', "distribution", Required = false, Default = YcsbGlobals.UniformDist, + [Option('d', "distribution", Required = false, Default = YcsbConstants.UniformDist, HelpText = "Distribution of keys in workload")] public string DistributionName { get; set; } @@ -62,7 +62,15 @@ class Options HelpText = "Seed for synthetic data distribution")] public int RandomSeed { get; set; } + [Option((char)0, "sy", Required = false, Default = false, + HelpText = "Use synthetic data")] + public bool UseSyntheticData { get; set; } + public string GetOptionsString() - => $"d: {DistributionName.ToLower()}; n: {NumaStyle}; r: {ReadPercent}; t: {ThreadCount}; x: {SecondaryIndexType}; z: {LockImpl}"; + { + static string boolStr(bool value) => value ? "y" : "n"; + return $"d: {DistributionName.ToLower()}; n: {NumaStyle}; r: {ReadPercent}; t: {ThreadCount}; x: {SecondaryIndexType}; z: {LockImpl}; i: {IterationCount};" + + $" sd: {boolStr(YcsbConstants.kUseSmallData)}; sm: {boolStr(YcsbConstants.kSmallMemoryLog)}; sy: {boolStr(this.UseSyntheticData)}"; + } } } diff --git a/cs/benchmark/Program.cs b/cs/benchmark/Program.cs index 03a921e23..ffa3ba85d 100644 --- a/cs/benchmark/Program.cs +++ b/cs/benchmark/Program.cs @@ -1,128 +1,24 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using CommandLine; using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; namespace FASTER.benchmark { - enum AggregateType - { - Running, - FinalFull, - FinalTrimmed - } - - enum StatsLine : int - { - Iteration = 3, - RunningIns = 4, - RunningOps = 5, - FinalFullIns = 10, - FinalFullOps = 11, - FinalTrimmedIns = 20, - FinalTrimmedOps = 21 - } - public class Program { const int kTrimResultCount = 3; // Use some high value like int.MaxValue to disable public static void Main(string[] args) { - ParserResult result = Parser.Default.ParseArguments(args); - if (result.Tag == ParserResultType.NotParsed) - { + var testLoader = new TestLoader(); + if (!testLoader.Parse(args)) return; - } - - bool verifyOption(bool isValid, string name) - { - if (!isValid) - Console.WriteLine($"Invalid {name} argument"); - return isValid; - } - var options = result.MapResult(o => o, xs => new Options()); - - var b = (BenchmarkType)options.Benchmark; - if (!verifyOption(Enum.IsDefined(typeof(BenchmarkType), b), "Benchmark")) return; - if (!verifyOption(options.NumaStyle >= 0 && options.NumaStyle <= 1, "NumaStyle")) return; - var backupMode = (BackupMode)options.Backup; - if (!verifyOption(Enum.IsDefined(typeof(BackupMode), backupMode), "BackupMode")) return; - var lockImpl = (LockImpl)options.LockImpl; - if (!verifyOption(Enum.IsDefined(typeof(LockImpl), lockImpl), "Lock Implementation")) return; - var secondaryIndexType = (SecondaryIndexType)options.SecondaryIndexType; - if (!verifyOption(Enum.IsDefined(typeof(SecondaryIndexType), secondaryIndexType), "Secondary Index Type")) return; - if (!verifyOption(options.IterationCount > 0, "Iteration Count")) return; - if (!verifyOption(options.ReadPercent >= -1 && options.ReadPercent <= 100, "Read Percent")) return; - var distribution = options.DistributionName.ToLower(); - if (!verifyOption(distribution == YcsbGlobals.UniformDist || distribution == YcsbGlobals.ZipfDist, "Distribution")) return; - - Console.WriteLine($"Scenario: {b}, Locking: {(LockImpl)options.LockImpl}, Indexing: {(SecondaryIndexType)options.SecondaryIndexType}"); - - YcsbGlobals.OptionsString = options.GetOptionsString(); - - var initsPerRun = new List(); - var opsPerRun = new List(); - - void addResult((double ips, double ops) result) - { - initsPerRun.Add(result.ips); - opsPerRun.Add(result.ops); - } - Key[] init_keys_ = default; - Key[] txn_keys_ = default; - KeySpanByte[] init_span_keys_ = default; - KeySpanByte[] txn_span_keys_ = default; - - switch (b) - { - case BenchmarkType.Ycsb: - FASTER_YcsbBenchmark.LoadData(distribution, (uint)options.RandomSeed, out init_keys_, out txn_keys_); - break; - case BenchmarkType.SpanByte: - FasterSpanByteYcsbBenchmark.LoadData(distribution, (uint)options.RandomSeed, out init_span_keys_, out txn_span_keys_); - break; - case BenchmarkType.ConcurrentDictionaryYcsb: - ConcurrentDictionary_YcsbBenchmark.LoadData(distribution, (uint)options.RandomSeed, out init_keys_, out txn_keys_); - break; - default: - throw new ApplicationException("Unknown benchmark type"); - } - - static void showStats(StatsLine lineNum, string tag, List vec, string discardMessage = "") - { - var mean = vec.Sum() / vec.Count; - var stddev = Math.Sqrt(vec.Sum(n => Math.Pow(n - mean, 2)) / vec.Count); - var stddevpct = (stddev / mean) * 100; - Console.WriteLine(YcsbGlobals.StatsLine(lineNum, tag, mean, stddev, stddevpct)); - } - - void showAllStats(AggregateType aggregateType, string discardMessage = "") - { - var aggTypeString = aggregateType == AggregateType.Running ? "Running" : "Final"; - Console.WriteLine($"{aggTypeString} averages per second over {initsPerRun.Count} iteration(s){discardMessage}:"); - var statsLineNum = aggregateType switch - { - AggregateType.Running => StatsLine.RunningIns, - AggregateType.FinalFull => StatsLine.FinalFullIns, - AggregateType.FinalTrimmed => StatsLine.FinalTrimmedIns, - _ => throw new InvalidOperationException("Unknown AggregateType") - }; - showStats(statsLineNum, "ins/sec", initsPerRun); - statsLineNum = aggregateType switch - { - AggregateType.Running => StatsLine.RunningOps, - AggregateType.FinalFull => StatsLine.FinalFullOps, - AggregateType.FinalTrimmed => StatsLine.FinalTrimmedOps, - _ => throw new InvalidOperationException("Unknown AggregateType") - }; - showStats(statsLineNum, "ops/sec", opsPerRun); - } + var testStats = new TestStats(testLoader.Options); + testLoader.LoadData(); + var options = testLoader.Options; // shortcut for (var iter = 0; iter < options.IterationCount; ++iter) { @@ -130,21 +26,21 @@ void showAllStats(AggregateType aggregateType, string discardMessage = "") if (options.IterationCount > 1) Console.WriteLine($"Iteration {iter + 1} of {options.IterationCount}"); - switch (b) + switch (testLoader.BenchmarkType) { case BenchmarkType.Ycsb: - var yTest = new FASTER_YcsbBenchmark(init_keys_, txn_keys_, options.ThreadCount, options.NumaStyle, distribution, options.ReadPercent, backupMode, lockImpl, secondaryIndexType); - addResult(yTest.Run()); + var yTest = new FASTER_YcsbBenchmark(testLoader.init_keys, testLoader.txn_keys, testLoader); + testStats.AddResult(yTest.Run()); yTest.Dispose(); break; case BenchmarkType.SpanByte: - var sTest = new FasterSpanByteYcsbBenchmark(init_span_keys_, txn_span_keys_, options.ThreadCount, options.NumaStyle, distribution, options.ReadPercent, backupMode, lockImpl, secondaryIndexType); - addResult(sTest.Run()); + var sTest = new FasterSpanByteYcsbBenchmark(testLoader.init_span_keys, testLoader.txn_span_keys, testLoader); + testStats.AddResult(sTest.Run()); sTest.Dispose(); break; case BenchmarkType.ConcurrentDictionaryYcsb: - var cTest = new ConcurrentDictionary_YcsbBenchmark(init_keys_, txn_keys_, options.ThreadCount, options.NumaStyle, distribution, options.ReadPercent); - addResult(cTest.Run()); + var cTest = new ConcurrentDictionary_YcsbBenchmark(testLoader.init_keys, testLoader.txn_keys, testLoader); + testStats.AddResult(cTest.Run()); cTest.Dispose(); break; default: @@ -153,7 +49,7 @@ void showAllStats(AggregateType aggregateType, string discardMessage = "") if (options.IterationCount > 1) { - showAllStats(AggregateType.Running); + testStats.ShowAllStats(AggregateType.Running); if (iter < options.IterationCount - 1) { GC.Collect(); @@ -164,24 +60,9 @@ void showAllStats(AggregateType aggregateType, string discardMessage = "") } Console.WriteLine(); - showAllStats(AggregateType.FinalFull); - + testStats.ShowAllStats(AggregateType.FinalFull); if (options.IterationCount >= kTrimResultCount) - { - static void discardHiLo(List vec) - { - vec.Sort(); -#pragma warning disable IDE0056 // Use index operator (^ is not supported on .NET Framework or NETCORE pre-3.0) - vec[0] = vec[vec.Count - 2]; // overwrite lowest with second-highest -#pragma warning restore IDE0056 // Use index operator - vec.RemoveRange(vec.Count - 2, 2); // remove highest and (now-duplicated) second-highest - } - discardHiLo(initsPerRun); - discardHiLo(opsPerRun); - - Console.WriteLine(); - showAllStats(AggregateType.FinalTrimmed, $" ({options.IterationCount} iterations specified, with high and low discarded)"); - } + testStats.ShowTrimmedStats(); } } } diff --git a/cs/benchmark/TestLoader.cs b/cs/benchmark/TestLoader.cs new file mode 100644 index 000000000..82cb3903f --- /dev/null +++ b/cs/benchmark/TestLoader.cs @@ -0,0 +1,324 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using CommandLine; +using FASTER.core; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; + +#pragma warning disable CS0162 // Unreachable code detected -- when switching on YcsbConstants + +namespace FASTER.benchmark +{ + internal interface IKeySetter + { + void Set(TKey[] vector, long idx, long value); + } + + class TestLoader + { + internal Options Options; + + internal BenchmarkType BenchmarkType; + internal BackupMode BackupMode; + internal LockImpl LockImpl; + internal SecondaryIndexType SecondaryIndexType; + internal string Distribution; + + internal Key[] init_keys = default; + internal Key[] txn_keys = default; + internal KeySpanByte[] init_span_keys = default; + internal KeySpanByte[] txn_span_keys = default; + + internal bool Parse(string[] args) + { + ParserResult result = Parser.Default.ParseArguments(args); + if (result.Tag == ParserResultType.NotParsed) + { + return false; + } + Options = result.MapResult(o => o, xs => new Options()); + + static bool verifyOption(bool isValid, string name) + { + if (!isValid) + Console.WriteLine($"Invalid {name} argument"); + return isValid; + } + + this.BenchmarkType = (BenchmarkType)Options.Benchmark; + if (!verifyOption(Enum.IsDefined(typeof(BenchmarkType), this.BenchmarkType), "Benchmark")) + return false; + + if (!verifyOption(Options.NumaStyle >= 0 && Options.NumaStyle <= 1, "NumaStyle")) + return false; + + this.BackupMode = (BackupMode)Options.Backup; + if (!verifyOption(Enum.IsDefined(typeof(BackupMode), this.BackupMode), "BackupMode")) + return false; + + this.LockImpl = (LockImpl)Options.LockImpl; + if (!verifyOption(Enum.IsDefined(typeof(LockImpl), this.LockImpl), "Lock Implementation")) + return false; + + this.SecondaryIndexType = (SecondaryIndexType)Options.SecondaryIndexType; + if (!verifyOption(Enum.IsDefined(typeof(SecondaryIndexType), this.SecondaryIndexType), "Secondary Index Type")) + return false; + + if (!verifyOption(Options.IterationCount > 0, "Iteration Count")) + return false; + + if (!verifyOption(Options.ReadPercent >= -1 && Options.ReadPercent <= 100, "Read Percent")) + return false; + + this.Distribution = Options.DistributionName.ToLower(); + if (!verifyOption(this.Distribution == YcsbConstants.UniformDist || this.Distribution == YcsbConstants.ZipfDist, "Distribution")) + return false; + + Console.WriteLine($"Scenario: {this.BenchmarkType}, Locking: {(LockImpl)Options.LockImpl}, Indexing: {(SecondaryIndexType)Options.SecondaryIndexType}"); + return true; + } + + internal void LoadData() + { + var worker = new Thread(LoadDataThreadProc); + worker.Start(); + worker.Join(); + } + + private void LoadDataThreadProc() + { + Native32.AffinitizeThreadShardedNuma(0, 2); + + switch (this.BenchmarkType) + { + case BenchmarkType.Ycsb: + FASTER_YcsbBenchmark.CreateKeyVectors(out this.init_keys, out this.txn_keys); + LoadData(this, this.init_keys, this.txn_keys, new FASTER_YcsbBenchmark.KeySetter()); + break; + case BenchmarkType.SpanByte: + FasterSpanByteYcsbBenchmark.CreateKeyVectors(out this.init_span_keys, out this.txn_span_keys); + LoadData(this, this.init_span_keys, this.txn_span_keys, new FasterSpanByteYcsbBenchmark.KeySetter()); + break; + case BenchmarkType.ConcurrentDictionaryYcsb: + ConcurrentDictionary_YcsbBenchmark.CreateKeyVectors(out this.init_keys, out this.txn_keys); + LoadData(this, this.init_keys, this.txn_keys, new ConcurrentDictionary_YcsbBenchmark.KeySetter()); + break; + default: + throw new ApplicationException("Unknown benchmark type"); + } + } + + private static void LoadData(TestLoader testLoader, TKey[] init_keys, TKey[] txn_keys, TKeySetter keySetter) + where TKeySetter : IKeySetter + { + if (testLoader.Options.UseSyntheticData) + { + LoadSyntheticData(testLoader.Distribution, (uint)testLoader.Options.RandomSeed, init_keys, txn_keys, keySetter); + return; + } + + string filePath = "C:/ycsb_files"; + + if (!Directory.Exists(filePath)) + { + filePath = "D:/ycsb_files"; + } + if (!Directory.Exists(filePath)) + { + filePath = "E:/ycsb_files"; + } + + if (Directory.Exists(filePath)) + { + LoadDataFromFile(filePath, testLoader.Distribution, init_keys, txn_keys, keySetter); + } + else + { + Console.WriteLine("WARNING: Could not find YCSB directory, loading synthetic data instead"); + LoadSyntheticData(testLoader.Distribution, (uint)testLoader.Options.RandomSeed, init_keys, txn_keys, keySetter); + } + } + + private static unsafe void LoadDataFromFile(string filePath, string distribution, TKey[] init_keys, TKey[] txn_keys, TKeySetter keySetter) + where TKeySetter : IKeySetter + { + string init_filename = filePath + "/load_" + distribution + "_250M_raw.dat"; + string txn_filename = filePath + "/run_" + distribution + "_250M_1000M_raw.dat"; + + Console.WriteLine($"loading all keys from {init_filename} into memory..."); + var sw = Stopwatch.StartNew(); + + if (YcsbConstants.kUseSmallData) + { + Console.WriteLine($"loading subset of keys and txns from {txn_filename} into memory..."); + using FileStream stream = File.Open(txn_filename, FileMode.Open, FileAccess.Read, FileShare.Read); + byte[] chunk = new byte[YcsbConstants.kFileChunkSize]; + GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); + byte* chunk_ptr = (byte*)chunk_handle.AddrOfPinnedObject(); + + var initValueSet = new HashSet(); + + long init_count = 0; + long txn_count = 0; + + long offset = 0; + + while (true) + { + stream.Position = offset; + int size = stream.Read(chunk, 0, YcsbConstants.kFileChunkSize); + for (int idx = 0; idx < size && txn_count < txn_keys.Length; idx += 8) + { + var value = *(long*)(chunk_ptr + idx); + if (!initValueSet.Contains(value)) + { + if (init_count >= init_keys.Length) + continue; + + initValueSet.Add(value); + keySetter.Set(init_keys, init_count, value); + ++init_count; + } + keySetter.Set(txn_keys, txn_count, value); + ++txn_count; + } + if (size == YcsbConstants.kFileChunkSize) + offset += YcsbConstants.kFileChunkSize; + else + break; + + if (txn_count == txn_keys.Length) + break; + } + + sw.Stop(); + chunk_handle.Free(); + + if (init_count != init_keys.Length) + throw new InvalidDataException($"Init file subset load fail! Expected {init_keys.Length} keys; found {init_count}"); + if (txn_count != txn_keys.Length) + throw new InvalidDataException($"Txn file subset load fail! Expected {txn_keys.Length} keys; found {txn_count}"); + + Console.WriteLine($"loaded {init_keys.Length:N0} keys and {txn_keys.Length:N0} txns in {(double)sw.ElapsedMilliseconds / 1000:N3} seconds"); + return; + } + +#pragma warning disable CS0162 // Unreachable code detected -- when !kUseSmallData + long count = 0; +#pragma warning restore CS0162 // Unreachable code detected + + using (FileStream stream = File.Open(init_filename, FileMode.Open, FileAccess.Read, + FileShare.Read)) + { + byte[] chunk = new byte[YcsbConstants.kFileChunkSize]; + GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); + byte* chunk_ptr = (byte*)chunk_handle.AddrOfPinnedObject(); + + long offset = 0; + + while (true) + { + stream.Position = offset; + int size = stream.Read(chunk, 0, YcsbConstants.kFileChunkSize); + for (int idx = 0; idx < size; idx += 8) + { + keySetter.Set(init_keys, count, *(long*)(chunk_ptr + idx)); + ++count; + if (count == init_keys.Length) + break; + } + if (size == YcsbConstants.kFileChunkSize) + offset += YcsbConstants.kFileChunkSize; + else + break; + + if (count == init_keys.Length) + break; + } + + chunk_handle.Free(); + + if (count != init_keys.Length) + throw new InvalidDataException($"Init file load fail! Expected {init_keys.Length} keys; found {count}"); + } + + sw.Stop(); + Console.WriteLine($"loaded {init_keys.Length:N0} keys in {(double)sw.ElapsedMilliseconds / 1000:N3} seconds"); + + Console.WriteLine($"loading all txns from {txn_filename} into memory..."); + sw.Restart(); + + using (FileStream stream = File.Open(txn_filename, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + byte[] chunk = new byte[YcsbConstants.kFileChunkSize]; + GCHandle chunk_handle = GCHandle.Alloc(chunk, GCHandleType.Pinned); + byte* chunk_ptr = (byte*)chunk_handle.AddrOfPinnedObject(); + + count = 0; + long offset = 0; + + while (true) + { + stream.Position = offset; + int size = stream.Read(chunk, 0, YcsbConstants.kFileChunkSize); + for (int idx = 0; idx < size; idx += 8) + { + keySetter.Set(txn_keys, count, *(long*)(chunk_ptr + idx)); + ++count; + if (count == txn_keys.Length) + break; + } + if (size == YcsbConstants.kFileChunkSize) + offset += YcsbConstants.kFileChunkSize; + else + break; + + if (count == txn_keys.Length) + break; + } + + chunk_handle.Free(); + + if (count != txn_keys.Length) + throw new InvalidDataException($"Txn file load fail! Expected {txn_keys.Length} keys; found {count}"); + } + + sw.Stop(); + Console.WriteLine($"loaded {txn_keys.Length:N0} txns in {(double)sw.ElapsedMilliseconds / 1000:N3} seconds"); + } + + private static void LoadSyntheticData(string distribution, uint seed, TKey[] init_keys, TKey[] txn_keys, TKeySetter keySetter) + where TKeySetter : IKeySetter + { + Console.WriteLine($"Loading synthetic data ({distribution} distribution), seed = {seed}"); + var sw = Stopwatch.StartNew(); + + long val = 0; + for (int idx = 0; idx < init_keys.Length; idx++) + { + keySetter.Set(init_keys, idx, val++); + } + + sw.Stop(); + Console.WriteLine($"loaded {init_keys.Length:N0} keys in {(double)sw.ElapsedMilliseconds / 1000:N3} seconds"); + + RandomGenerator generator = new RandomGenerator(seed); + var zipf = new ZipfGenerator(generator, (int)init_keys.Length, theta: 0.99); + + sw.Restart(); + for (int idx = 0; idx < txn_keys.Length; idx++) + { + var rand = distribution == YcsbConstants.UniformDist ? (long)generator.Generate64((ulong)init_keys.Length) : zipf.Next(); + keySetter.Set(txn_keys, idx, rand); + } + + sw.Stop(); + Console.WriteLine($"loaded {txn_keys.Length:N0} txns in {(double)sw.ElapsedMilliseconds / 1000:N3} seconds"); + } + } +} diff --git a/cs/benchmark/TestStats.cs b/cs/benchmark/TestStats.cs new file mode 100644 index 000000000..1bf806dea --- /dev/null +++ b/cs/benchmark/TestStats.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using static FASTER.benchmark.YcsbConstants; + +namespace FASTER.benchmark +{ + class TestStats + { + private readonly List initsPerRun = new List(); + private readonly List opsPerRun = new List(); + + private Options options; + internal static string OptionsString; + + internal TestStats(Options options) + { + this.options = options; + OptionsString = options.GetOptionsString(); + } + + internal void AddResult((double ips, double ops) result) + { + initsPerRun.Add(result.ips); + opsPerRun.Add(result.ops); + } + + internal void ShowAllStats(AggregateType aggregateType, string discardMessage = "") + { + var aggTypeString = aggregateType == AggregateType.Running ? "Running" : "Final"; + Console.WriteLine($"{aggTypeString} averages per second over {initsPerRun.Count} iteration(s){discardMessage}:"); + var statsLineNum = aggregateType switch + { + AggregateType.Running => StatsLineNum.RunningIns, + AggregateType.FinalFull => StatsLineNum.FinalFullIns, + AggregateType.FinalTrimmed => StatsLineNum.FinalTrimmedIns, + _ => throw new InvalidOperationException("Unknown AggregateType") + }; + ShowStats(statsLineNum, "ins/sec", initsPerRun); + statsLineNum = aggregateType switch + { + AggregateType.Running => StatsLineNum.RunningOps, + AggregateType.FinalFull => StatsLineNum.FinalFullOps, + AggregateType.FinalTrimmed => StatsLineNum.FinalTrimmedOps, + _ => throw new InvalidOperationException("Unknown AggregateType") + }; + ShowStats(statsLineNum, "ops/sec", opsPerRun); + } + + private void ShowStats(StatsLineNum lineNum, string tag, List vec) + { + var mean = vec.Sum() / vec.Count; + var stddev = Math.Sqrt(vec.Sum(n => Math.Pow(n - mean, 2)) / vec.Count); + var stddevpct = (stddev / mean) * 100; + Console.WriteLine(GetStatsLine(lineNum, tag, mean, stddev, stddevpct)); + } + + internal void ShowTrimmedStats() + { + static void discardHiLo(List vec) + { + vec.Sort(); +#pragma warning disable IDE0056 // Use index operator (^ is not supported on .NET Framework or NETCORE pre-3.0) + vec[0] = vec[vec.Count - 2]; // overwrite lowest with second-highest +#pragma warning restore IDE0056 // Use index operator + vec.RemoveRange(vec.Count - 2, 2); // remove highest and (now-duplicated) second-highest + } + discardHiLo(initsPerRun); + discardHiLo(opsPerRun); + + Console.WriteLine(); + ShowAllStats(AggregateType.FinalTrimmed, $" ({this.options.IterationCount} iterations specified, with high and low discarded)"); + } + + internal static string GetTotalOpsString(long totalOps, double seconds) => $"Total {totalOps:N0} ops done in {seconds:N3} seconds"; + + internal static string GetLoadingTimeLine(double insertsPerSec, long elapsedMs) + => $"##00; {InsPerSec}: {insertsPerSec:N2}; sec: {(double)elapsedMs / 1000:N3}"; + + internal static string GetAddressesLine(AddressLineNum lineNum, long begin, long head, long rdonly, long tail) + => $"##{(int)lineNum:00}; begin: {begin:N0}; head: {head:N0}; readonly: {rdonly:N0}; tail: {tail}"; + + internal static string GetStatsLine(StatsLineNum lineNum, string opsPerSecTag, double opsPerSec) + => $"##{(int)lineNum:00}; {opsPerSecTag}: {opsPerSec:N2}; {OptionsString}"; + + internal static string GetStatsLine(StatsLineNum lineNum, string meanTag, double mean, double stdev, double stdevpct) + => $"##{(int)lineNum:00}; {meanTag}: {mean:N2}; stdev: {stdev:N1}; stdev%: {stdevpct:N1}; {OptionsString}"; + } +} diff --git a/cs/benchmark/YcsbConstants.cs b/cs/benchmark/YcsbConstants.cs new file mode 100644 index 000000000..b9b8ae885 --- /dev/null +++ b/cs/benchmark/YcsbConstants.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace FASTER.benchmark +{ + enum BenchmarkType : int + { + Ycsb, + SpanByte, + ConcurrentDictionaryYcsb + }; + + [Flags] + enum BackupMode : int + { + None = 0, + Restore = 1, + Backup = 2, + Both = 3 + }; + + enum LockImpl : int + { + None, + RecordInfo + }; + + [Flags] + enum SecondaryIndexType : int + { + None = 0, + Key = 1, + Value = 2, + Both = 3 + }; + + enum AddressLineNum : int + { + Before = 1, + After = 2 + } + + enum AggregateType + { + Running = 0, + FinalFull = 1, + FinalTrimmed = 2 + } + + enum StatsLineNum : int + { + Iteration = 3, + RunningIns = 4, + RunningOps = 5, + FinalFullIns = 10, + FinalFullOps = 11, + FinalTrimmedIns = 20, + FinalTrimmedOps = 21 + } + + public enum Op : ulong + { + Upsert = 0, + Read = 1, + ReadModifyWrite = 2 + } + + public static class YcsbConstants + { + internal const string UniformDist = "uniform"; // Uniformly random distribution of keys + internal const string ZipfDist = "zipf"; // Smooth zipf curve (most localized keys) + + internal const string InsPerSec = "ins/sec"; + internal const string OpsPerSec = "ops/sec"; + +#if DEBUG + internal const bool kUseSmallData = true; + internal const bool kSmallMemoryLog = false; + internal const int kRunSeconds = 30; +#else + internal const bool kUseSmallData = true; //false; + internal const bool kSmallMemoryLog = false; + internal const int kRunSeconds = 30; +#endif + internal const long kInitCount = kUseSmallData ? 2500480 : 250000000; + internal const long kTxnCount = kUseSmallData ? 10000000 : 1000000000; + internal const int kMaxKey = kUseSmallData ? 1 << 22 : 1 << 28; + + internal const int kFileChunkSize = 4096; + internal const long kChunkSize = 640; + } +} diff --git a/cs/benchmark/YcsbGlobals.cs b/cs/benchmark/YcsbGlobals.cs deleted file mode 100644 index beec65b19..000000000 --- a/cs/benchmark/YcsbGlobals.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; - -namespace FASTER.benchmark -{ - enum BenchmarkType : int - { - Ycsb, SpanByte, ConcurrentDictionaryYcsb - }; - - [Flags] - enum BackupMode : int - { - None, Restore, Backup, Both - }; - - enum LockImpl : int - { - None, RecordInfo - }; - - [Flags] - enum SecondaryIndexType : int - { - None, Key, Value, Both - }; - - enum AddressLine : int - { - Before = 1, - After = 2 - } - - public enum Op : ulong - { - Upsert = 0, - Read = 1, - ReadModifyWrite = 2 - } - - public static class YcsbGlobals - { - internal const string UniformDist = "uniform"; // Uniformly random distribution of keys - internal const string ZipfDist = "zipf"; // Smooth zipf curve (most localized keys) - - internal const string InsPerSec = "ins/sec"; - internal const string OpsPerSec = "ops/sec"; - - internal static string TotalOpsString(long totalOps, double seconds) => $"Total {totalOps:N0} ops done in {seconds:N3} seconds"; - - internal static string OptionsString; - - internal static string LoadingTimeLine(double insertsPerSec, long elapsedMs) - => $"##0; {InsPerSec}: {insertsPerSec:N2}; ms: {elapsedMs:N0}"; - - internal static string AddressesLine(AddressLine lineNum, long begin, long head, long rdonly, long tail) - => $"##{(int)lineNum}; begin: {begin:N0}; head: {head:N0}; readonly: {rdonly:N0}; tail: {tail}"; - - private static string BoolStr(bool value) => value ? "y" : "n"; - - internal static string StatsLine(StatsLine lineNum, string opsPerSecTag, double opsPerSec) - => $"##{(int)lineNum}; {opsPerSecTag}: {opsPerSec:N2}; {OptionsString}; sd: {BoolStr(kUseSmallData)}; sm: {BoolStr(kSmallMemoryLog)}"; - - internal static string StatsLine(StatsLine lineNum, string meanTag, double mean, double stdev, double stdevpct) - => $"##{(int)lineNum}; {meanTag}: {mean:N2}; stdev: {stdev:N1}; stdev%: {stdevpct:N1}; {OptionsString}; sd: {BoolStr(kUseSmallData)}; sm: {BoolStr(kSmallMemoryLog)}"; - -#if DEBUG - internal const bool kUseSmallData = true; - internal const bool kUseSyntheticData = true; - internal const bool kSmallMemoryLog = false; - internal const int kRunSeconds = 30; -#else - internal const bool kUseSmallData = true;//false; - internal const bool kUseSyntheticData = false; - internal const bool kSmallMemoryLog = false; - internal const int kRunSeconds = 30; -#endif - internal const long kInitCount = kUseSmallData ? 2500480 : 250000000; - internal const long kTxnCount = kUseSmallData ? 10000000 : 1000000000; - internal const int kMaxKey = kUseSmallData ? 1 << 22 : 1 << 28; - } -} From e6baec2146a5bca84807ce80a730789065a994d6 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Wed, 3 Mar 2021 17:42:41 -0800 Subject: [PATCH 15/37] Fix UpdateSIForIPU return; fix latchDest in InternalRMW; tweak addresses StatsLine --- cs/benchmark/TestStats.cs | 2 +- cs/src/core/Index/FASTER/FASTER.cs | 7 +++---- cs/src/core/Index/FASTER/FASTERImpl.cs | 5 ++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/cs/benchmark/TestStats.cs b/cs/benchmark/TestStats.cs index 1bf806dea..a8e4de7d2 100644 --- a/cs/benchmark/TestStats.cs +++ b/cs/benchmark/TestStats.cs @@ -81,7 +81,7 @@ internal static string GetLoadingTimeLine(double insertsPerSec, long elapsedMs) => $"##00; {InsPerSec}: {insertsPerSec:N2}; sec: {(double)elapsedMs / 1000:N3}"; internal static string GetAddressesLine(AddressLineNum lineNum, long begin, long head, long rdonly, long tail) - => $"##{(int)lineNum:00}; begin: {begin:N0}; head: {head:N0}; readonly: {rdonly:N0}; tail: {tail}"; + => $"##{(int)lineNum:00}; begin: {begin}; head: {head}; readonly: {rdonly}; tail: {tail}"; internal static string GetStatsLine(StatsLineNum lineNum, string opsPerSecTag, double opsPerSec) => $"##{(int)lineNum:00}; {opsPerSecTag}: {opsPerSec:N2}; {OptionsString}"; diff --git a/cs/src/core/Index/FASTER/FASTER.cs b/cs/src/core/Index/FASTER/FASTER.cs index 12b0a49b3..7905715b4 100644 --- a/cs/src/core/Index/FASTER/FASTER.cs +++ b/cs/src/core/Index/FASTER/FASTER.cs @@ -607,10 +607,9 @@ internal Status ContextUpsert(ref Key key internal bool UpdateSIForIPU(ref Value value, long address) { // KeyIndexes do not need notification of in-place updates because the key does not change. - if (this.SecondaryIndexBroker.MutableValueIndexCount == 0) - return true; - this.SecondaryIndexBroker.Upsert(ref value, address); - return false; + if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) + this.SecondaryIndexBroker.Upsert(ref value, address); + return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index d0e4cff8b..f2513e5d7 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -679,7 +679,7 @@ internal OperationStatus InternalRMW( latchOperation = LatchOperation.None; } } - goto CreatePendingContext; // Go pending + latchDestination = LatchDestination.CreatePendingContext; // Go pending } // Safe Read-Only Region: Create a record in the mutable region @@ -702,7 +702,7 @@ internal OperationStatus InternalRMW( latchOperation = LatchOperation.None; } } - goto CreatePendingContext; // Go pending + latchDestination = LatchDestination.CreatePendingContext; // Go pending } // No record exists - create new @@ -724,7 +724,6 @@ internal OperationStatus InternalRMW( #endregion #region Create failure context - CreatePendingContext: Debug.Assert(latchDestination == LatchDestination.CreatePendingContext); { pendingContext.type = OperationType.RMW; From b922709cc398ad827c61cc3c083ec320aded73ef Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Wed, 3 Mar 2021 18:05:35 -0800 Subject: [PATCH 16/37] turn off smalldata for Release --- cs/benchmark/YcsbConstants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cs/benchmark/YcsbConstants.cs b/cs/benchmark/YcsbConstants.cs index b9b8ae885..e0d9b7b40 100644 --- a/cs/benchmark/YcsbConstants.cs +++ b/cs/benchmark/YcsbConstants.cs @@ -80,7 +80,7 @@ public static class YcsbConstants internal const bool kSmallMemoryLog = false; internal const int kRunSeconds = 30; #else - internal const bool kUseSmallData = true; //false; + internal const bool kUseSmallData = false; internal const bool kSmallMemoryLog = false; internal const int kRunSeconds = 30; #endif From 1874a7ced5499f01c49014db7a326848d160707a Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Thu, 4 Mar 2021 22:40:49 -0800 Subject: [PATCH 17/37] YCSB changes: - Move backup/restore to subdirs dependent upon distribution, ycsb vs. synthetic data, and sizes - Reduce -k (backup) parameter from bitmask to bool - Move remaining constants to YcsbConstants --- cs/benchmark/ConcurrentDictionaryBenchmark.cs | 36 ++---- cs/benchmark/FasterSpanByteYcsbBenchmark.cs | 117 +++++------------- cs/benchmark/FasterYcsbBenchmark.cs | 106 ++++------------ cs/benchmark/Options.cs | 14 +-- cs/benchmark/Program.cs | 6 +- cs/benchmark/TestLoader.cs | 56 +++++++-- cs/benchmark/TestStats.cs | 2 +- cs/benchmark/YcsbConstants.cs | 10 ++ 8 files changed, 142 insertions(+), 205 deletions(-) diff --git a/cs/benchmark/ConcurrentDictionaryBenchmark.cs b/cs/benchmark/ConcurrentDictionaryBenchmark.cs index 6cb0b398c..72b5e8d19 100644 --- a/cs/benchmark/ConcurrentDictionaryBenchmark.cs +++ b/cs/benchmark/ConcurrentDictionaryBenchmark.cs @@ -16,26 +16,17 @@ namespace FASTER.benchmark { - public class KeyComparer : IEqualityComparer + internal class KeyComparer : IEqualityComparer { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Key x, Key y) - { - return x.value == y.value; - } + public bool Equals(Key x, Key y) => x.value == y.value; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetHashCode(Key obj) - { - return (int)Utility.GetHashCode(obj.value); - } + public int GetHashCode(Key obj) => (int)Utility.GetHashCode(obj.value); } internal unsafe class ConcurrentDictionary_YcsbBenchmark { - const int kCheckpointSeconds = -1; - - readonly int threadCount; readonly int numaStyle; readonly string distribution; readonly int readPercent; @@ -55,7 +46,6 @@ internal ConcurrentDictionary_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, TestLo { init_keys_ = i_keys_; txn_keys_ = t_keys_; - threadCount = testLoader.Options.ThreadCount; numaStyle = testLoader.Options.NumaStyle; distribution = testLoader.Distribution; readPercent = testLoader.Options.ReadPercent; @@ -77,10 +67,10 @@ internal ConcurrentDictionary_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, TestLo for (int i = 0; i < 8; i++) input_[i].value = i; - store = new ConcurrentDictionary(threadCount, YcsbConstants.kMaxKey, new KeyComparer()); + store = new ConcurrentDictionary(testLoader.Options.ThreadCount, YcsbConstants.kMaxKey, new KeyComparer()); } - public void Dispose() + internal void Dispose() { store.Clear(); } @@ -180,7 +170,7 @@ private void RunYcsb(int thread_idx) Interlocked.Add(ref total_ops_done, reads_done + writes_done); } - public unsafe (double, double) Run() + internal unsafe (double, double) Run(TestLoader testLoader) { RandomGenerator rng = new RandomGenerator(); @@ -192,12 +182,12 @@ public unsafe (double, double) Run() dash.Start(); #endif - Thread[] workers = new Thread[threadCount]; + Thread[] workers = new Thread[testLoader.Options.ThreadCount]; Console.WriteLine("Executing setup."); // Setup the store for the YCSB benchmark. - for (int idx = 0; idx < threadCount; ++idx) + for (int idx = 0; idx < testLoader.Options.ThreadCount; ++idx) { int x = idx; workers[idx] = new Thread(() => SetupYcsb(x)); @@ -223,7 +213,7 @@ public unsafe (double, double) Run() Console.WriteLine("Executing experiment."); // Run the experiment. - for (int idx = 0; idx < threadCount; ++idx) + for (int idx = 0; idx < testLoader.Options.ThreadCount; ++idx) { int x = idx; workers[idx] = new Thread(() => RunYcsb(x)); @@ -237,17 +227,17 @@ public unsafe (double, double) Run() Stopwatch swatch = new Stopwatch(); swatch.Start(); - if (kCheckpointSeconds <= 0) + if (YcsbConstants.kPeriodicCheckpointMilliseconds <= 0) { Thread.Sleep(TimeSpan.FromSeconds(YcsbConstants.kRunSeconds)); } else { - int runSeconds = 0; + double runSeconds = 0; while (runSeconds < YcsbConstants.kRunSeconds) { - Thread.Sleep(TimeSpan.FromSeconds(kCheckpointSeconds)); - runSeconds += kCheckpointSeconds; + Thread.Sleep(TimeSpan.FromMilliseconds(YcsbConstants.kPeriodicCheckpointMilliseconds)); + runSeconds += YcsbConstants.kPeriodicCheckpointMilliseconds / 1000; } } diff --git a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs index 7b3d8fc50..af7da257d 100644 --- a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs +++ b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs @@ -13,39 +13,16 @@ namespace FASTER.benchmark { - public class FasterSpanByteYcsbBenchmark + internal class FasterSpanByteYcsbBenchmark { -#if DEBUG - const bool kDumpDistribution = false; - const bool kAffinitizedSession = true; - const int kPeriodicCheckpointMilliseconds = 0; -#else - const bool kDumpDistribution = false; - const bool kAffinitizedSession = true; - const int kPeriodicCheckpointMilliseconds = 0; -#endif - - // *** Use these to backup and recover database for fast benchmark repeat runs - // Use BackupMode.Backup to create the backup, unless it was recovered during BackupMode.Recover - // Use BackupMode.Restore for fast subsequent runs - // Does NOT work when periodic checkpointing is turned on - readonly BackupMode backupMode; - // *** - // Ensure sizes are aligned to chunk sizes const long kInitCount = YcsbConstants.kChunkSize * (YcsbConstants.kInitCount / YcsbConstants.kChunkSize); const long kTxnCount = YcsbConstants.kChunkSize * (YcsbConstants.kTxnCount / YcsbConstants.kChunkSize); - public const int kKeySize = 16; - public const int kValueSize = 100; - readonly ManualResetEventSlim waiter = new ManualResetEventSlim(); - readonly int threadCount; readonly int numaStyle; - readonly string distribution; readonly int readPercent; readonly FunctionsSB functions; - readonly SecondaryIndexType secondaryIndexType = SecondaryIndexType.None; readonly Input[] input_; readonly KeySpanByte[] init_keys_; @@ -58,22 +35,21 @@ public class FasterSpanByteYcsbBenchmark long total_ops_done = 0; volatile bool done = false; + internal const int kKeySize = 16; + internal const int kValueSize = 100; + internal FasterSpanByteYcsbBenchmark(KeySpanByte[] i_keys_, KeySpanByte[] t_keys_, TestLoader testLoader) { // Pin loading thread if it is not used for checkpointing - if (kPeriodicCheckpointMilliseconds <= 0) + if (YcsbConstants.kPeriodicCheckpointMilliseconds <= 0) Native32.AffinitizeThreadShardedNuma(0, 2); init_keys_ = i_keys_; txn_keys_ = t_keys_; - threadCount = testLoader.Options.ThreadCount; numaStyle = testLoader.Options.NumaStyle; - distribution = testLoader.Distribution; readPercent = testLoader.Options.ReadPercent; - backupMode = testLoader.BackupMode; var lockImpl = testLoader.LockImpl; functions = new FunctionsSB(lockImpl != LockImpl.None); - secondaryIndexType = testLoader.SecondaryIndexType; #if DASHBOARD statsWritten = new AutoResetEvent[threadCount]; @@ -88,29 +64,29 @@ internal FasterSpanByteYcsbBenchmark(KeySpanByte[] i_keys_, KeySpanByte[] t_keys writeStats = new bool[threadCount]; freq = Stopwatch.Frequency; #endif + input_ = new Input[8]; for (int i = 0; i < 8; i++) input_[i].value = i; - var path = "D:\\data\\FasterYcsbBenchmark\\"; - device = Devices.CreateLogDevice(path + "hlog", preallocateFile: true); + device = Devices.CreateLogDevice(TestLoader.DevicePath, preallocateFile: true); if (YcsbConstants.kSmallMemoryLog) store = new FasterKV (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, - new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); + new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = testLoader.BackupPath }); else store = new FasterKV (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, MemorySizeBits = 35 }, - new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); + new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = testLoader.BackupPath }); - if (secondaryIndexType.HasFlag(SecondaryIndexType.Key)) + if (testLoader.SecondaryIndexType.HasFlag(SecondaryIndexType.Key)) store.SecondaryIndexBroker.AddIndex(new NullKeyIndex()); - if (secondaryIndexType.HasFlag(SecondaryIndexType.Value)) + if (testLoader.SecondaryIndexType.HasFlag(SecondaryIndexType.Value)) store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); } - public void Dispose() + internal void Dispose() { store.Dispose(); device.Dispose(); @@ -148,7 +124,7 @@ private void RunYcsb(int thread_idx) int count = 0; #endif - var session = store.For(functions).NewSession(null, kAffinitizedSession); + var session = store.For(functions).NewSession(null, YcsbConstants.kAffinitizedSession); while (!done) { @@ -173,7 +149,7 @@ private void RunYcsb(int thread_idx) if (idx % 512 == 0) { - if (kAffinitizedSession) + if (YcsbConstants.kAffinitizedSession) session.Refresh(); session.CompletePending(false); } @@ -234,7 +210,7 @@ private void RunYcsb(int thread_idx) Interlocked.Add(ref total_ops_done, reads_done + writes_done); } - public unsafe (double, double) Run() + internal unsafe (double, double) Run(TestLoader testLoader) { //Native32.AffinitizeThreadShardedNuma(0, 2); @@ -243,90 +219,61 @@ public unsafe (double, double) Run() dash.Start(); #endif - Thread[] workers = new Thread[threadCount]; + Thread[] workers = new Thread[testLoader.Options.ThreadCount]; Console.WriteLine("Executing setup."); - Stopwatch sw = new Stopwatch(); - var storeWasRecovered = false; - if (this.backupMode.HasFlag(BackupMode.Restore) && kPeriodicCheckpointMilliseconds <= 0) - { - if (!YcsbConstants.kUseSmallData) - { - Console.WriteLine("Skipping Recover() for kSmallData"); - } - else - { - sw.Start(); - try - { - Console.WriteLine("Recovering FasterKV for fast restart"); - store.Recover(); - storeWasRecovered = true; - } - catch (Exception) - { - Console.WriteLine("Unable to recover prior store"); - } - sw.Stop(); - } - } + var storeWasRecovered = testLoader.MaybeRecoverStore(store); + long elapsedMs = 0; if (!storeWasRecovered) { // Setup the store for the YCSB benchmark. Console.WriteLine("Loading FasterKV from data"); - for (int idx = 0; idx < threadCount; ++idx) + for (int idx = 0; idx < testLoader.Options.ThreadCount; ++idx) { int x = idx; workers[idx] = new Thread(() => SetupYcsb(x)); } - // Start threads. foreach (Thread worker in workers) { worker.Start(); } waiter.Set(); - sw.Start(); + var sw = Stopwatch.StartNew(); foreach (Thread worker in workers) { worker.Join(); } sw.Stop(); waiter.Reset(); + + elapsedMs = sw.ElapsedMilliseconds; } - double insertsPerSecond = storeWasRecovered ? 0 : ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; - Console.WriteLine(TestStats.GetLoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); + double insertsPerSecond = elapsedMs == 0 ? 0 : ((double)kInitCount / elapsedMs) * 1000; + Console.WriteLine(TestStats.GetLoadingTimeLine(insertsPerSecond, elapsedMs)); Console.WriteLine(TestStats.GetAddressesLine(AddressLineNum.Before, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); - long startTailAddress = store.Log.TailAddress; - Console.WriteLine("Start tail address = " + startTailAddress); - - if (!storeWasRecovered && this.backupMode.HasFlag(BackupMode.Backup) && kPeriodicCheckpointMilliseconds <= 0) - { - Console.WriteLine("Checkpointing FasterKV for fast restart"); - store.TakeFullCheckpoint(out _); - store.CompleteCheckpointAsync().GetAwaiter().GetResult(); - Console.WriteLine("Completed checkpoint"); - } + if (!storeWasRecovered) + testLoader.MaybeCheckpointStore(store); // Uncomment below to dispose log from memory, use for 100% read workloads only // store.Log.DisposeFromMemory(); idx_ = 0; - if (kDumpDistribution) + if (YcsbConstants.kDumpDistribution) Console.WriteLine(store.DumpDistribution()); // Ensure first checkpoint is fast - if (kPeriodicCheckpointMilliseconds > 0) + if (YcsbConstants.kPeriodicCheckpointMilliseconds > 0) store.Log.ShiftReadOnlyAddress(store.Log.TailAddress, true); Console.WriteLine("Executing experiment."); // Run the experiment. - for (int idx = 0; idx < threadCount; ++idx) + for (int idx = 0; idx < testLoader.Options.ThreadCount; ++idx) { int x = idx; workers[idx] = new Thread(() => RunYcsb(x)); @@ -341,7 +288,7 @@ public unsafe (double, double) Run() Stopwatch swatch = new Stopwatch(); swatch.Start(); - if (kPeriodicCheckpointMilliseconds <= 0) + if (YcsbConstants.kPeriodicCheckpointMilliseconds <= 0) { Thread.Sleep(TimeSpan.FromSeconds(YcsbConstants.kRunSeconds)); } @@ -350,7 +297,7 @@ public unsafe (double, double) Run() var checkpointTaken = 0; while (swatch.ElapsedMilliseconds < 1000 * YcsbConstants.kRunSeconds) { - if (checkpointTaken < swatch.ElapsedMilliseconds / kPeriodicCheckpointMilliseconds) + if (checkpointTaken < swatch.ElapsedMilliseconds / YcsbConstants.kPeriodicCheckpointMilliseconds) { if (store.TakeHybridLogCheckpoint(out _)) { @@ -393,7 +340,7 @@ private void SetupYcsb(int thread_idx) waiter.Wait(); - var session = store.For(functions).NewSession(null, kAffinitizedSession); + var session = store.For(functions).NewSession(null, YcsbConstants.kAffinitizedSession); #if DASHBOARD var tstart = Stopwatch.GetTimestamp(); diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index 0457c1dff..dd099a16e 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -15,34 +15,14 @@ namespace FASTER.benchmark { internal class FASTER_YcsbBenchmark { - // *** Use these to backup and recover database for fast benchmark repeat runs - // Use BackupMode.Backup to create the backup, unless it was recovered during BackupMode.Recover - // Use BackupMode.Restore for fast subsequent runs - // Does NOT work when periodic checkpointing or kUseSmallData is turned on - readonly BackupMode backupMode; - // *** - -#if DEBUG - internal const bool kDumpDistribution = false; - internal const bool kAffinitizedSession = true; - internal const int kPeriodicCheckpointMilliseconds = 0; -#else - internal const bool kDumpDistribution = false; - internal const bool kAffinitizedSession = true; - internal const int kPeriodicCheckpointMilliseconds = 0; -#endif - // Ensure sizes are aligned to chunk sizes const long kInitCount = YcsbConstants.kChunkSize * (YcsbConstants.kInitCount / YcsbConstants.kChunkSize); const long kTxnCount = YcsbConstants.kChunkSize * (YcsbConstants.kTxnCount / YcsbConstants.kChunkSize); readonly ManualResetEventSlim waiter = new ManualResetEventSlim(); - readonly int threadCount; readonly int numaStyle; - readonly string distribution; readonly int readPercent; readonly Functions functions; - readonly SecondaryIndexType secondaryIndexType = SecondaryIndexType.None; readonly Input[] input_; readonly Key[] init_keys_; @@ -58,19 +38,15 @@ internal class FASTER_YcsbBenchmark internal FASTER_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, TestLoader testLoader) { // Pin loading thread if it is not used for checkpointing - if (kPeriodicCheckpointMilliseconds <= 0) + if (YcsbConstants.kPeriodicCheckpointMilliseconds <= 0) Native32.AffinitizeThreadShardedNuma(0, 2); init_keys_ = i_keys_; txn_keys_ = t_keys_; - threadCount = testLoader.Options.ThreadCount; numaStyle = testLoader.Options.NumaStyle; - distribution = testLoader.Distribution; readPercent = testLoader.Options.ReadPercent; - backupMode = testLoader.BackupMode; var lockImpl = testLoader.LockImpl; functions = new Functions(lockImpl != LockImpl.None); - secondaryIndexType = testLoader.SecondaryIndexType; #if DASHBOARD statsWritten = new AutoResetEvent[threadCount]; @@ -85,29 +61,29 @@ internal FASTER_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, TestLoader testLoade writeStats = new bool[threadCount]; freq = Stopwatch.Frequency; #endif + input_ = new Input[8]; for (int i = 0; i < 8; i++) input_[i].value = i; - var path = "D:\\data\\FasterYcsbBenchmark\\"; - device = Devices.CreateLogDevice(path + "hlog", preallocateFile: true); + device = Devices.CreateLogDevice(TestLoader.DevicePath, preallocateFile: true); if (YcsbConstants.kSmallMemoryLog) store = new FasterKV (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, - new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); + new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = testLoader.BackupPath }); else store = new FasterKV (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true }, - new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = path }); + new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = testLoader.BackupPath }); - if (secondaryIndexType.HasFlag(SecondaryIndexType.Key)) + if (testLoader.SecondaryIndexType.HasFlag(SecondaryIndexType.Key)) store.SecondaryIndexBroker.AddIndex(new NullKeyIndex()); - if (secondaryIndexType.HasFlag(SecondaryIndexType.Value)) + if (testLoader.SecondaryIndexType.HasFlag(SecondaryIndexType.Value)) store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); } - public void Dispose() + internal void Dispose() { store.Dispose(); device.Dispose(); @@ -141,7 +117,7 @@ private void RunYcsb(int thread_idx) int count = 0; #endif - var session = store.For(functions).NewSession(null, kAffinitizedSession); + var session = store.For(functions).NewSession(null, YcsbConstants.kAffinitizedSession); while (!done) { @@ -166,7 +142,7 @@ private void RunYcsb(int thread_idx) if (idx % 512 == 0) { - if (kAffinitizedSession) + if (YcsbConstants.kAffinitizedSession) session.Refresh(); session.CompletePending(false); } @@ -227,95 +203,67 @@ private void RunYcsb(int thread_idx) Interlocked.Add(ref total_ops_done, reads_done + writes_done); } - public unsafe (double, double) Run() + internal unsafe (double, double) Run(TestLoader testLoader) { #if DASHBOARD var dash = new Thread(() => DoContinuousMeasurements()); dash.Start(); #endif - Thread[] workers = new Thread[threadCount]; + Thread[] workers = new Thread[testLoader.Options.ThreadCount]; Console.WriteLine("Executing setup."); - Stopwatch sw = new Stopwatch(); - var storeWasRecovered = false; - if (this.backupMode.HasFlag(BackupMode.Restore) && kPeriodicCheckpointMilliseconds <= 0) - { - if (!YcsbConstants.kUseSmallData) - { - Console.WriteLine("Skipping Recover() for kSmallData"); - } - else - { - Console.WriteLine("Recovering store for fast restart"); - sw.Start(); - try - { - Console.WriteLine("Recovering FasterKV for fast restart"); - store.Recover(); - storeWasRecovered = true; - } - catch (Exception) - { - Console.WriteLine("Unable to recover prior store"); - } - sw.Stop(); - } - } + var storeWasRecovered = testLoader.MaybeRecoverStore(store); + long elapsedMs = 0; if (!storeWasRecovered) { // Setup the store for the YCSB benchmark. Console.WriteLine("Loading FasterKV from data"); - for (int idx = 0; idx < threadCount; ++idx) + for (int idx = 0; idx < testLoader.Options.ThreadCount; ++idx) { int x = idx; workers[idx] = new Thread(() => SetupYcsb(x)); } - // Start threads. foreach (Thread worker in workers) { worker.Start(); } waiter.Set(); - sw.Start(); + var sw = Stopwatch.StartNew(); foreach (Thread worker in workers) { worker.Join(); } sw.Stop(); + elapsedMs = sw.ElapsedMilliseconds; waiter.Reset(); } - double insertsPerSecond = storeWasRecovered ? 0 : ((double)kInitCount / sw.ElapsedMilliseconds) * 1000; - Console.WriteLine(TestStats.GetLoadingTimeLine(insertsPerSecond, sw.ElapsedMilliseconds)); + double insertsPerSecond = elapsedMs == 0 ? 0 : ((double)kInitCount / elapsedMs) * 1000; + Console.WriteLine(TestStats.GetLoadingTimeLine(insertsPerSecond, elapsedMs)); Console.WriteLine(TestStats.GetAddressesLine(AddressLineNum.Before, store.Log.BeginAddress, store.Log.HeadAddress, store.Log.ReadOnlyAddress, store.Log.TailAddress)); - if (!storeWasRecovered && this.backupMode.HasFlag(BackupMode.Backup) && kPeriodicCheckpointMilliseconds <= 0) - { - Console.WriteLine("Checkpointing FasterKV for fast restart"); - store.TakeFullCheckpoint(out _); - store.CompleteCheckpointAsync().GetAwaiter().GetResult(); - Console.WriteLine("Completed checkpoint"); - } + if (!storeWasRecovered) + testLoader.MaybeCheckpointStore(store); // Uncomment below to dispose log from memory, use for 100% read workloads only // store.Log.DisposeFromMemory(); idx_ = 0; - if (kDumpDistribution) + if (YcsbConstants.kDumpDistribution) Console.WriteLine(store.DumpDistribution()); // Ensure first checkpoint is fast - if (kPeriodicCheckpointMilliseconds > 0) + if (YcsbConstants.kPeriodicCheckpointMilliseconds > 0) store.Log.ShiftReadOnlyAddress(store.Log.TailAddress, true); Console.WriteLine("Executing experiment."); // Run the experiment. - for (int idx = 0; idx < threadCount; ++idx) + for (int idx = 0; idx < testLoader.Options.ThreadCount; ++idx) { int x = idx; workers[idx] = new Thread(() => RunYcsb(x)); @@ -330,7 +278,7 @@ public unsafe (double, double) Run() Stopwatch swatch = new Stopwatch(); swatch.Start(); - if (kPeriodicCheckpointMilliseconds <= 0) + if (YcsbConstants.kPeriodicCheckpointMilliseconds <= 0) { Thread.Sleep(TimeSpan.FromSeconds(YcsbConstants.kRunSeconds)); } @@ -339,7 +287,7 @@ public unsafe (double, double) Run() var checkpointTaken = 0; while (swatch.ElapsedMilliseconds < 1000 * YcsbConstants.kRunSeconds) { - if (checkpointTaken < swatch.ElapsedMilliseconds / kPeriodicCheckpointMilliseconds) + if (checkpointTaken < swatch.ElapsedMilliseconds / YcsbConstants.kPeriodicCheckpointMilliseconds) { if (store.TakeHybridLogCheckpoint(out _)) { @@ -382,7 +330,7 @@ private void SetupYcsb(int thread_idx) waiter.Wait(); - var session = store.For(functions).NewSession(null, kAffinitizedSession); + var session = store.For(functions).NewSession(null, YcsbConstants.kAffinitizedSession); #if DASHBOARD var tstart = Stopwatch.GetTimestamp(); diff --git a/cs/benchmark/Options.cs b/cs/benchmark/Options.cs index 2ba53eceb..d6b6844ed 100644 --- a/cs/benchmark/Options.cs +++ b/cs/benchmark/Options.cs @@ -24,13 +24,13 @@ class Options "\n 1 = Sharding across NUMA sockets")] public int NumaStyle { get; set; } - [Option('k', "backup", Required = false, Default = 0, - HelpText = "Enable Backup and Restore of FasterKV for fast test startup:" + - "\n 0 = None; Populate FasterKV from data" + - "\n 1 = Recover FasterKV from Checkpoint; if this fails, populate FasterKV from data" + - "\n 2 = Checkpoint FasterKV (unless it was Recovered by option 1; if option 1 is not specified, this will overwrite an existing Checkpoint)" + - "\n 3 = Both (Recover FasterKV if a Checkpoint is available, else populate FasterKV from data and Checkpoint it so it can be Restored in a subsequent run)")] - public int Backup { get; set; } + [Option('k', "backup", Required = false, Default = false, + HelpText = "Enable Backup and Restore of FasterKV for fast test startup." + + "\n True = Recover FasterKV if a Checkpoint is available, else populate FasterKV from data and Checkpoint it so it can be Restored in a subsequent run" + + "\n False = Populate FasterKV from data" + + "\n Checkpoints are stored in directories under " + TestLoader.DataPath + " in directories named by distribution, ycsb vs. synthetic data, and key counts;" + + "\n to force a new checkpoint, delete the existing folder")] + public bool BackupAndRestore { get; set; } [Option('z', "locking", Required = false, Default = 0, HelpText = "Locking Implementation:" + diff --git a/cs/benchmark/Program.cs b/cs/benchmark/Program.cs index ffa3ba85d..1bbee71b2 100644 --- a/cs/benchmark/Program.cs +++ b/cs/benchmark/Program.cs @@ -30,17 +30,17 @@ public static void Main(string[] args) { case BenchmarkType.Ycsb: var yTest = new FASTER_YcsbBenchmark(testLoader.init_keys, testLoader.txn_keys, testLoader); - testStats.AddResult(yTest.Run()); + testStats.AddResult(yTest.Run(testLoader)); yTest.Dispose(); break; case BenchmarkType.SpanByte: var sTest = new FasterSpanByteYcsbBenchmark(testLoader.init_span_keys, testLoader.txn_span_keys, testLoader); - testStats.AddResult(sTest.Run()); + testStats.AddResult(sTest.Run(testLoader)); sTest.Dispose(); break; case BenchmarkType.ConcurrentDictionaryYcsb: var cTest = new ConcurrentDictionary_YcsbBenchmark(testLoader.init_keys, testLoader.txn_keys, testLoader); - testStats.AddResult(cTest.Run()); + testStats.AddResult(cTest.Run(testLoader)); cTest.Dispose(); break; default: diff --git a/cs/benchmark/TestLoader.cs b/cs/benchmark/TestLoader.cs index 82cb3903f..fdb9cb307 100644 --- a/cs/benchmark/TestLoader.cs +++ b/cs/benchmark/TestLoader.cs @@ -24,7 +24,6 @@ class TestLoader internal Options Options; internal BenchmarkType BenchmarkType; - internal BackupMode BackupMode; internal LockImpl LockImpl; internal SecondaryIndexType SecondaryIndexType; internal string Distribution; @@ -57,10 +56,6 @@ static bool verifyOption(bool isValid, string name) if (!verifyOption(Options.NumaStyle >= 0 && Options.NumaStyle <= 1, "NumaStyle")) return false; - this.BackupMode = (BackupMode)Options.Backup; - if (!verifyOption(Enum.IsDefined(typeof(BackupMode), this.BackupMode), "BackupMode")) - return false; - this.LockImpl = (LockImpl)Options.LockImpl; if (!verifyOption(Enum.IsDefined(typeof(LockImpl), this.LockImpl), "Lock Implementation")) return false; @@ -208,9 +203,7 @@ private static unsafe void LoadDataFromFile(string filePath, s return; } -#pragma warning disable CS0162 // Unreachable code detected -- when !kUseSmallData long count = 0; -#pragma warning restore CS0162 // Unreachable code detected using (FileStream stream = File.Open(init_filename, FileMode.Open, FileAccess.Read, FileShare.Read)) @@ -320,5 +313,54 @@ private static void LoadSyntheticData(string distribution, uin sw.Stop(); Console.WriteLine($"loaded {txn_keys.Length:N0} txns in {(double)sw.ElapsedMilliseconds / 1000:N3} seconds"); } + + internal const string DataPath = "D:/data/FasterYcsbBenchmark"; + + internal static string DevicePath => $"{DataPath}/hlog"; + + internal string BackupPath => $"{DataPath}/{this.Distribution}_{(this.Options.UseSyntheticData ? "synthetic" : "ycsb")}_{(YcsbConstants.kUseSmallData ? "2.5M_10M" : "250M_1000M")}"; + + internal bool MaybeRecoverStore(FasterKV store) + { + // Recover database for fast benchmark repeat runs. + if (this.Options.BackupAndRestore && YcsbConstants.kPeriodicCheckpointMilliseconds <= 0) + { + if (YcsbConstants.kUseSmallData) + { + Console.WriteLine("Skipping Recover() for kSmallData"); + return false; + } + + Console.WriteLine($"Recovering FasterKV from {this.BackupPath} for fast restart"); + try + { + var sw = Stopwatch.StartNew(); + store.Recover(); + sw.Stop(); + Console.WriteLine($" Completed recovery in {(double)sw.ElapsedMilliseconds / 1000:N3} seconds"); + return true; + } + catch (Exception ex) + { + var suffix = Directory.Exists(this.BackupPath) ? "" : " (directory does not exist)"; + Console.WriteLine($"Unable to recover prior store: {ex.Message}{suffix}"); + } + } + return false; + } + + internal void MaybeCheckpointStore(FasterKV store) + { + // Checkpoint database for fast benchmark repeat runs. + if (this.Options.BackupAndRestore && YcsbConstants.kPeriodicCheckpointMilliseconds <= 0) + { + Console.WriteLine($"Checkpointing FasterKV to {this.BackupPath} for fast restart"); + Stopwatch sw = Stopwatch.StartNew(); + store.TakeFullCheckpoint(out _); + store.CompleteCheckpointAsync().GetAwaiter().GetResult(); + sw.Stop(); + Console.WriteLine($" Completed checkpoint in {(double)sw.ElapsedMilliseconds / 1000:N3} seconds"); + } + } } } diff --git a/cs/benchmark/TestStats.cs b/cs/benchmark/TestStats.cs index a8e4de7d2..3c814e40e 100644 --- a/cs/benchmark/TestStats.cs +++ b/cs/benchmark/TestStats.cs @@ -54,7 +54,7 @@ private void ShowStats(StatsLineNum lineNum, string tag, List vec) { var mean = vec.Sum() / vec.Count; var stddev = Math.Sqrt(vec.Sum(n => Math.Pow(n - mean, 2)) / vec.Count); - var stddevpct = (stddev / mean) * 100; + var stddevpct = mean == 0 ? 0 : (stddev / mean) * 100; Console.WriteLine(GetStatsLine(lineNum, tag, mean, stddev, stddevpct)); } diff --git a/cs/benchmark/YcsbConstants.cs b/cs/benchmark/YcsbConstants.cs index e0d9b7b40..e9bccbeaa 100644 --- a/cs/benchmark/YcsbConstants.cs +++ b/cs/benchmark/YcsbConstants.cs @@ -72,6 +72,9 @@ public static class YcsbConstants internal const string UniformDist = "uniform"; // Uniformly random distribution of keys internal const string ZipfDist = "zipf"; // Smooth zipf curve (most localized keys) + internal const string SyntheticData = "synthetic"; + internal const string YcsbData = "ycsb"; + internal const string InsPerSec = "ins/sec"; internal const string OpsPerSec = "ops/sec"; @@ -79,11 +82,18 @@ public static class YcsbConstants internal const bool kUseSmallData = true; internal const bool kSmallMemoryLog = false; internal const int kRunSeconds = 30; + internal const bool kDumpDistribution = false; + internal const bool kAffinitizedSession = true; + internal const int kPeriodicCheckpointMilliseconds = 0; #else internal const bool kUseSmallData = false; internal const bool kSmallMemoryLog = false; internal const int kRunSeconds = 30; + internal const bool kDumpDistribution = false; + internal const bool kAffinitizedSession = true; + internal const int kPeriodicCheckpointMilliseconds = 0; #endif + internal const long kInitCount = kUseSmallData ? 2500480 : 250000000; internal const long kTxnCount = kUseSmallData ? 10000000 : 1000000000; internal const int kMaxKey = kUseSmallData ? 1 << 22 : 1 << 28; From eb59000d5ce66867db250a428f7fe17cb8c8a9b7 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Mon, 8 Mar 2021 21:09:20 -0800 Subject: [PATCH 18/37] YCSB: change --backup to --recover and remove BackupMode; add --runsec --- cs/benchmark/ConcurrentDictionaryBenchmark.cs | 4 ++-- cs/benchmark/FasterSpanByteYcsbBenchmark.cs | 8 ++++---- cs/benchmark/FasterYcsbBenchmark.cs | 8 ++++---- cs/benchmark/Options.cs | 11 +++++++---- cs/benchmark/TestLoader.cs | 5 ++++- cs/benchmark/YcsbConstants.cs | 9 --------- 6 files changed, 21 insertions(+), 24 deletions(-) diff --git a/cs/benchmark/ConcurrentDictionaryBenchmark.cs b/cs/benchmark/ConcurrentDictionaryBenchmark.cs index 72b5e8d19..7dcafc6ad 100644 --- a/cs/benchmark/ConcurrentDictionaryBenchmark.cs +++ b/cs/benchmark/ConcurrentDictionaryBenchmark.cs @@ -229,12 +229,12 @@ internal unsafe (double, double) Run(TestLoader testLoader) if (YcsbConstants.kPeriodicCheckpointMilliseconds <= 0) { - Thread.Sleep(TimeSpan.FromSeconds(YcsbConstants.kRunSeconds)); + Thread.Sleep(TimeSpan.FromSeconds(testLoader.Options.RunSeconds)); } else { double runSeconds = 0; - while (runSeconds < YcsbConstants.kRunSeconds) + while (runSeconds < testLoader.Options.RunSeconds) { Thread.Sleep(TimeSpan.FromMilliseconds(YcsbConstants.kPeriodicCheckpointMilliseconds)); runSeconds += YcsbConstants.kPeriodicCheckpointMilliseconds / 1000; diff --git a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs index af7da257d..3f0c12531 100644 --- a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs +++ b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs @@ -74,11 +74,11 @@ internal FasterSpanByteYcsbBenchmark(KeySpanByte[] i_keys_, KeySpanByte[] t_keys if (YcsbConstants.kSmallMemoryLog) store = new FasterKV (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, - new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = testLoader.BackupPath }); + new CheckpointSettings { CheckPointType = CheckpointType.Snapshot, CheckpointDir = testLoader.BackupPath }); else store = new FasterKV (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, MemorySizeBits = 35 }, - new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = testLoader.BackupPath }); + new CheckpointSettings { CheckPointType = CheckpointType.Snapshot, CheckpointDir = testLoader.BackupPath }); if (testLoader.SecondaryIndexType.HasFlag(SecondaryIndexType.Key)) store.SecondaryIndexBroker.AddIndex(new NullKeyIndex()); @@ -290,12 +290,12 @@ internal unsafe (double, double) Run(TestLoader testLoader) if (YcsbConstants.kPeriodicCheckpointMilliseconds <= 0) { - Thread.Sleep(TimeSpan.FromSeconds(YcsbConstants.kRunSeconds)); + Thread.Sleep(TimeSpan.FromSeconds(testLoader.Options.RunSeconds)); } else { var checkpointTaken = 0; - while (swatch.ElapsedMilliseconds < 1000 * YcsbConstants.kRunSeconds) + while (swatch.ElapsedMilliseconds < 1000 * testLoader.Options.RunSeconds) { if (checkpointTaken < swatch.ElapsedMilliseconds / YcsbConstants.kPeriodicCheckpointMilliseconds) { diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index dd099a16e..b177b029b 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -71,11 +71,11 @@ internal FASTER_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, TestLoader testLoade if (YcsbConstants.kSmallMemoryLog) store = new FasterKV (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true, PageSizeBits = 22, SegmentSizeBits = 26, MemorySizeBits = 26 }, - new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = testLoader.BackupPath }); + new CheckpointSettings { CheckPointType = CheckpointType.Snapshot, CheckpointDir = testLoader.BackupPath }); else store = new FasterKV (YcsbConstants.kMaxKey / 2, new LogSettings { LogDevice = device, PreallocateLog = true }, - new CheckpointSettings { CheckPointType = CheckpointType.FoldOver, CheckpointDir = testLoader.BackupPath }); + new CheckpointSettings { CheckPointType = CheckpointType.Snapshot, CheckpointDir = testLoader.BackupPath }); if (testLoader.SecondaryIndexType.HasFlag(SecondaryIndexType.Key)) store.SecondaryIndexBroker.AddIndex(new NullKeyIndex()); @@ -280,12 +280,12 @@ internal unsafe (double, double) Run(TestLoader testLoader) if (YcsbConstants.kPeriodicCheckpointMilliseconds <= 0) { - Thread.Sleep(TimeSpan.FromSeconds(YcsbConstants.kRunSeconds)); + Thread.Sleep(TimeSpan.FromSeconds(testLoader.Options.RunSeconds)); } else { var checkpointTaken = 0; - while (swatch.ElapsedMilliseconds < 1000 * YcsbConstants.kRunSeconds) + while (swatch.ElapsedMilliseconds < 1000 * testLoader.Options.RunSeconds) { if (checkpointTaken < swatch.ElapsedMilliseconds / YcsbConstants.kPeriodicCheckpointMilliseconds) { diff --git a/cs/benchmark/Options.cs b/cs/benchmark/Options.cs index d6b6844ed..95f90e272 100644 --- a/cs/benchmark/Options.cs +++ b/cs/benchmark/Options.cs @@ -24,12 +24,11 @@ class Options "\n 1 = Sharding across NUMA sockets")] public int NumaStyle { get; set; } - [Option('k', "backup", Required = false, Default = false, + [Option('k', "recover", Required = false, Default = false, HelpText = "Enable Backup and Restore of FasterKV for fast test startup." + "\n True = Recover FasterKV if a Checkpoint is available, else populate FasterKV from data and Checkpoint it so it can be Restored in a subsequent run" + - "\n False = Populate FasterKV from data" + - "\n Checkpoints are stored in directories under " + TestLoader.DataPath + " in directories named by distribution, ycsb vs. synthetic data, and key counts;" + - "\n to force a new checkpoint, delete the existing folder")] + "\n False = Populate FasterKV from data and do not Checkpoint a backup" + + "\n (Checkpoints are stored in directories under " + TestLoader.DataPath + " in directories named by distribution, ycsb vs. synthetic data, and key counts)")] public bool BackupAndRestore { get; set; } [Option('z', "locking", Required = false, Default = 0, @@ -66,6 +65,10 @@ class Options HelpText = "Use synthetic data")] public bool UseSyntheticData { get; set; } + [Option((char)0, "runsec", Required = false, Default = YcsbConstants.kRunSeconds, + HelpText = "Number of seconds to execute experiment")] + public int RunSeconds { get; set; } + public string GetOptionsString() { static string boolStr(bool value) => value ? "y" : "n"; diff --git a/cs/benchmark/TestLoader.cs b/cs/benchmark/TestLoader.cs index fdb9cb307..ac8eea181 100644 --- a/cs/benchmark/TestLoader.cs +++ b/cs/benchmark/TestLoader.cs @@ -74,6 +74,9 @@ static bool verifyOption(bool isValid, string name) if (!verifyOption(this.Distribution == YcsbConstants.UniformDist || this.Distribution == YcsbConstants.ZipfDist, "Distribution")) return false; + if (!verifyOption(this.Options.RunSeconds >= 0, "RunSeconds")) + return false; + Console.WriteLine($"Scenario: {this.BenchmarkType}, Locking: {(LockImpl)Options.LockImpl}, Indexing: {(SecondaryIndexType)Options.SecondaryIndexType}"); return true; } @@ -343,7 +346,7 @@ internal bool MaybeRecoverStore(FasterKV store) catch (Exception ex) { var suffix = Directory.Exists(this.BackupPath) ? "" : " (directory does not exist)"; - Console.WriteLine($"Unable to recover prior store: {ex.Message}{suffix}"); + Console.WriteLine($" Unable to recover prior store: {ex.Message}{suffix}"); } } return false; diff --git a/cs/benchmark/YcsbConstants.cs b/cs/benchmark/YcsbConstants.cs index e9bccbeaa..d76c6283a 100644 --- a/cs/benchmark/YcsbConstants.cs +++ b/cs/benchmark/YcsbConstants.cs @@ -12,15 +12,6 @@ enum BenchmarkType : int ConcurrentDictionaryYcsb }; - [Flags] - enum BackupMode : int - { - None = 0, - Restore = 1, - Backup = 2, - Both = 3 - }; - enum LockImpl : int { None, From 664e7dc1be1a034e5733f604763aa8e4cee1235e Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Tue, 9 Mar 2021 21:40:08 -0800 Subject: [PATCH 19/37] a few minor tweaks made while doing PRs --- cs/benchmark/FunctionsSB.cs | 2 +- cs/benchmark/Options.cs | 4 +-- cs/src/core/Allocator/GenericAllocator.cs | 1 - .../ClientSession/AdvancedClientSession.cs | 30 +++++++++++-------- cs/src/core/ClientSession/ClientSession.cs | 12 ++++---- cs/src/core/Index/FASTER/FASTERImpl.cs | 8 ++--- cs/src/core/Index/FASTER/FASTERLegacy.cs | 10 +++---- cs/test/AsyncTests.cs | 3 -- cs/test/ObjectRecoveryTestTypes.cs | 8 ----- cs/test/ObjectTestTypes.cs | 4 --- cs/test/RecoverContinueTests.cs | 5 ---- 11 files changed, 36 insertions(+), 51 deletions(-) diff --git a/cs/benchmark/FunctionsSB.cs b/cs/benchmark/FunctionsSB.cs index 664e1638b..6cdb27eb0 100644 --- a/cs/benchmark/FunctionsSB.cs +++ b/cs/benchmark/FunctionsSB.cs @@ -7,6 +7,6 @@ namespace FASTER.benchmark { public sealed class FunctionsSB : SpanByteFunctions { - public FunctionsSB(bool locking) : base(locking:locking) { } + public FunctionsSB(bool locking) : base(locking: locking) { } } } diff --git a/cs/benchmark/Options.cs b/cs/benchmark/Options.cs index 95f90e272..e39aaa83e 100644 --- a/cs/benchmark/Options.cs +++ b/cs/benchmark/Options.cs @@ -61,11 +61,11 @@ class Options HelpText = "Seed for synthetic data distribution")] public int RandomSeed { get; set; } - [Option((char)0, "sy", Required = false, Default = false, + [Option("synth", Required = false, Default = false, HelpText = "Use synthetic data")] public bool UseSyntheticData { get; set; } - [Option((char)0, "runsec", Required = false, Default = YcsbConstants.kRunSeconds, + [Option("runsec", Required = false, Default = YcsbConstants.kRunSeconds, HelpText = "Number of seconds to execute experiment")] public int RunSeconds { get; set; } diff --git a/cs/src/core/Allocator/GenericAllocator.cs b/cs/src/core/Allocator/GenericAllocator.cs index ff68f9d5f..beaa509be 100644 --- a/cs/src/core/Allocator/GenericAllocator.cs +++ b/cs/src/core/Allocator/GenericAllocator.cs @@ -47,7 +47,6 @@ public GenericAllocator(LogSettings settings, SerializerSettings ser throw new FasterException("LogSettings.ObjectLogDevice needs to be specified (e.g., use Devices.CreateLogDevice, AzureStorageDevice, or NullDevice)"); } - SerializerSettings = serializerSettings; SerializerSettings = serializerSettings ?? new SerializerSettings(); if ((!keyBlittable) && (settings.LogDevice as NullDevice == null) && ((SerializerSettings == null) || (SerializerSettings.keySerializer == null))) diff --git a/cs/src/core/ClientSession/AdvancedClientSession.cs b/cs/src/core/ClientSession/AdvancedClientSession.cs index cfa3a6a7c..9af1fa5ea 100644 --- a/cs/src/core/ClientSession/AdvancedClientSession.cs +++ b/cs/src/core/ClientSession/AdvancedClientSession.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -#pragma warning disable 0162 - using System; using System.Collections.Generic; using System.Diagnostics; @@ -381,7 +379,7 @@ public ValueTask.ReadAsyncResult> R /// on the return value to complete the read operation and obtain the result status, the output that is populated by the /// implementation, and optionally a copy of the header for the retrieved record [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ValueTask.ReadAsyncResult> ReadAsync(ref Key key, ref Input input, long startAddress, ReadFlags readFlags, + public ValueTask.ReadAsyncResult> ReadAsync(ref Key key, ref Input input, long startAddress, ReadFlags readFlags = ReadFlags.None, Context userContext = default, long serialNo = 0, CancellationToken cancellationToken = default) { Debug.Assert(SupportAsync, NotAsyncSessionErr); @@ -403,7 +401,7 @@ public ValueTask.ReadAsyncResult> R /// on the return value to complete the read operation and obtain the result status, the output that is populated by the /// implementation, and optionally a copy of the header for the retrieved record [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ValueTask.ReadAsyncResult> ReadAtAddressAsync(long address, ref Input input, ReadFlags readFlags, + public ValueTask.ReadAsyncResult> ReadAtAddressAsync(long address, ref Input input, ReadFlags readFlags = ReadFlags.None, Context userContext = default, long serialNo = 0, CancellationToken cancellationToken = default) { Debug.Assert(SupportAsync, NotAsyncSessionErr); @@ -554,7 +552,15 @@ public Status Delete(Key key, Context userContext = default, long serialNo = 0) /// Status internal Status ContainsKeyInMemory(ref Key key, long fromAddress = -1) { - return fht.InternalContainsKeyInMemory(ref key, ctx, FasterSession, fromAddress); + if (SupportAsync) UnsafeResumeThread(); + try + { + return fht.InternalContainsKeyInMemory(ref key, ctx, FasterSession, fromAddress); + } + finally + { + if (SupportAsync) UnsafeSuspendThread(); + } } /// @@ -670,7 +676,7 @@ public async ValueTask WaitForCommitAsync(CancellationToken token = default) token.ThrowIfCancellationRequested(); // Complete all pending operations on session - await CompletePendingAsync(); + await CompletePendingAsync(token: token); var task = fht.CheckpointTask; CommitPoint localCommitPoint = LatestCommitPoint; @@ -899,6 +905,12 @@ public void UpsertCompletionCallback(ref Key key, ref Value value, Context ctx) _clientSession.functions.UpsertCompletionCallback(ref key, ref value, ctx); } + public bool SupportsLocking => _clientSession.functions.SupportsLocking; + + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value, lockType, ref context); + + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value, lockType, context); + public IHeapContainer GetHeapContainer(ref Input input) { if (_clientSession.inputVariableLengthStruct == default) @@ -906,12 +918,6 @@ public IHeapContainer GetHeapContainer(ref Input input) return new VarLenHeapContainer(ref input, _clientSession.inputVariableLengthStruct, _clientSession.fht.hlog.bufferPool); } - - public bool SupportsLocking => _clientSession.functions.SupportsLocking; - - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value, lockType, ref context); - - public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value, lockType, context); } } } diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index baa76641c..127a47d2c 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -908,6 +908,12 @@ public void UpsertCompletionCallback(ref Key key, ref Value value, Context ctx) _clientSession.functions.UpsertCompletionCallback(ref key, ref value, ctx); } + public bool SupportsLocking => _clientSession.functions.SupportsLocking; + + public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value, lockType, ref context); + + public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value, lockType, context); + public IHeapContainer GetHeapContainer(ref Input input) { if (_clientSession.inputVariableLengthStruct == default) @@ -915,12 +921,6 @@ public IHeapContainer GetHeapContainer(ref Input input) return new VarLenHeapContainer(ref input, _clientSession.inputVariableLengthStruct, _clientSession.fht.hlog.bufferPool); } - - public bool SupportsLocking => _clientSession.functions.SupportsLocking; - - public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) => _clientSession.functions.Lock(ref recordInfo, ref key, ref value, lockType, ref context); - - public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) => _clientSession.functions.Unlock(ref recordInfo, ref key, ref value, lockType, context); } } } diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index f2513e5d7..d5dcc7b49 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -395,7 +395,7 @@ internal OperationStatus InternalUpsert( #endregion #region Create pending context - Debug.Assert(latchDestination == LatchDestination.CreatePendingContext); + Debug.Assert(latchDestination == LatchDestination.CreatePendingContext, $"Upsert CreatePendingContext encountered latchDest == {latchDestination}"); { pendingContext.type = OperationType.UPSERT; pendingContext.key = hlog.GetKeyContainer(ref key); @@ -721,10 +721,10 @@ internal OperationStatus InternalRMW( status = CreateNewRecordRMW(ref key, ref input, ref pendingContext, fasterSession, sessionCtx, bucket, slot, logicalAddress, physicalAddress, tag, entry, latestLogicalAddress); goto LatchRelease; } - #endregion + #endregion - #region Create failure context - Debug.Assert(latchDestination == LatchDestination.CreatePendingContext); + #region Create failure context + Debug.Assert(latchDestination == LatchDestination.CreatePendingContext, $"RMW CreatePendingContext encountered latchDest == {latchDestination}"); { pendingContext.type = OperationType.RMW; pendingContext.key = hlog.GetKeyContainer(ref key); diff --git a/cs/src/core/Index/FASTER/FASTERLegacy.cs b/cs/src/core/Index/FASTER/FASTERLegacy.cs index 0ec1b64af..83bdcbd3b 100644 --- a/cs/src/core/Index/FASTER/FASTERLegacy.cs +++ b/cs/src/core/Index/FASTER/FASTERLegacy.cs @@ -408,16 +408,16 @@ public void UpsertCompletionCallback(ref Key key, ref Value value, Context ctx) _fasterKV._functions.UpsertCompletionCallback(ref key, ref value, ctx); } - public IHeapContainer GetHeapContainer(ref Input input) - { - return new StandardHeapContainer(ref input); - } - public bool SupportsLocking => false; public void Lock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, ref long context) { } public bool Unlock(ref RecordInfo recordInfo, ref Key key, ref Value value, LockType lockType, long context) => true; + + public IHeapContainer GetHeapContainer(ref Input input) + { + return new StandardHeapContainer(ref input); + } } } diff --git a/cs/test/AsyncTests.cs b/cs/test/AsyncTests.cs index 50123e548..843309130 100644 --- a/cs/test/AsyncTests.cs +++ b/cs/test/AsyncTests.cs @@ -2,11 +2,8 @@ // Licensed under the MIT license. using System; -using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Collections.Generic; -using System.Linq; using FASTER.core; using System.IO; using NUnit.Framework; diff --git a/cs/test/ObjectRecoveryTestTypes.cs b/cs/test/ObjectRecoveryTestTypes.cs index 78db146bb..642c87e80 100644 --- a/cs/test/ObjectRecoveryTestTypes.cs +++ b/cs/test/ObjectRecoveryTestTypes.cs @@ -3,16 +3,8 @@ #pragma warning disable 1591 -using System; -using System.Text; using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.Linq; using FASTER.core; -using System.Runtime.CompilerServices; -using System.IO; -using System.Diagnostics; namespace FASTER.test.recovery.objectstore { diff --git a/cs/test/ObjectTestTypes.cs b/cs/test/ObjectTestTypes.cs index cd62b0215..d220c227a 100644 --- a/cs/test/ObjectTestTypes.cs +++ b/cs/test/ObjectTestTypes.cs @@ -283,10 +283,6 @@ public class MyLargeOutput public class MyLargeFunctions : FunctionsBase { - public override void RMWCompletionCallback(ref MyKey key, ref MyInput input, Empty ctx, Status status) - { - } - public override void ReadCompletionCallback(ref MyKey key, ref MyInput input, ref MyLargeOutput output, Empty ctx, Status status) { Assert.IsTrue(status == Status.OK); diff --git a/cs/test/RecoverContinueTests.cs b/cs/test/RecoverContinueTests.cs index 4004df260..7fde42659 100644 --- a/cs/test/RecoverContinueTests.cs +++ b/cs/test/RecoverContinueTests.cs @@ -1,15 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; using FASTER.core; using System.IO; using NUnit.Framework; -using System.Diagnostics; using System.Threading.Tasks; namespace FASTER.test.recovery.sumstore.recover_continue From d15c6c63015ac160e4858b97b09e762a02ec285f Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Tue, 16 Mar 2021 17:39:54 -0700 Subject: [PATCH 20/37] Add Powershell scripts to run the benchmark performance suite and compare results --- cs/benchmark/scripts/compare_runs.ps1 | 204 +++++++++++++++++++++++++ cs/benchmark/scripts/run_benchmark.ps1 | 138 +++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 cs/benchmark/scripts/compare_runs.ps1 create mode 100644 cs/benchmark/scripts/run_benchmark.ps1 diff --git a/cs/benchmark/scripts/compare_runs.ps1 b/cs/benchmark/scripts/compare_runs.ps1 new file mode 100644 index 000000000..0e5b15ef7 --- /dev/null +++ b/cs/benchmark/scripts/compare_runs.ps1 @@ -0,0 +1,204 @@ +<# +.SYNOPSIS + Compares two directories of output from FASTER.benchmark.exe, usually run by run_benchmark.ps1. + +.DESCRIPTION + See run_benchmark.ps1 for instructions on setting up a perf directory and running the benchmark parameter permutations. + Once run_benchmark.ps1 has completed, you can either run this script from the perf directory if it was copied there, or from + another machine that has access to the perf directory on the perf machine. + + This script will: + 1. Compare result files of the same name in the two directories (the names are set by run_benchmark.ps1 to identify the parameter permutation used in that file). + 2. List any result files that were not matched. + 3. Display two Grids, one showing the results of the comparison for Loading times, and one for Experiment Run times. These differences are: + a. The difference in Mean throughput in inserts or transactions (operations) per second. + b. The percentage difference in throughput. The initial ordering of the grid is by this column, descending; thus the files for which the best performance + improvement was made are shown first. + c. The difference in Standard Deviation. + d. The difference in Standard Deviation as a percentage of Mean. + e. All other parameters of the run (these are the same between the two files). + +.PARAMETER OldDir + The directory containing the results of the baseline run; the result comparison is "NewDir throughput minus OldDir throughput". + +.PARAMETER RunSeconds + The directory containing the results of the new run, with the changes to be tested for impact; the result comparison is "NewDir throughput minus OldDir throughput". + +.EXAMPLE + ./compare_runs.ps1 './baseline' './refactor_FASTERImpl' +#> +param ( + [Parameter(Mandatory)] [String]$OldDir, + [Parameter(Mandatory)] [String]$NewDir +) + +class Result : System.IComparable, System.IEquatable[Object] { + # To make things work in one class, name the properties "Diff"--they aren't displayed until the Diff is calculated. + [double]$MeanDiff + [double]$MeanDiffPercent + [double]$StdDevDiff + [double]$StdDevDiffPercent + [uint]$Numa + [string]$Distribution + [int]$ReadPercent + [uint]$ThreadCount + [uint]$IndexMode + [uint]$LockMode + [uint]$Iterations + [bool]$SmallData + [bool]$SmallMemory + [bool]$SyntheticData + + Result([string]$line) { + $fields = $line.Split(';') + foreach($field in ($fields | Select-Object -skip 1)) { + $arg, $value = $field.Split(':') + $value = $value.Trim() + switch ($arg.Trim()) { + "ins/sec" { $this.MeanDiff = $value } + "ops/sec" { $this.MeanDiff = $value } + "stdev" { $this.StdDevDiff = $value } + "stdev%" { $this.StdDevDiffPercent = $value } + "n" { $this.Numa = $value } + "d" { $this.Distribution = $value } + "r" { $this.ReadPercent = $value } + "t" { $this.ThreadCount = $value } + "x" { $this.IndexMode = $value } + "z" { $this.LockMode = $value } + "i" { $this.Iterations = $value } + "sd" { $this.SmallData = $value -eq "y" } + "sm" { $this.SmallMemory = $value -eq "y" } + "sy" { $this.SyntheticData = $value -eq "y" } + } + } + } + + Result([Result]$other) { + $this.Numa = $other.Numa + $this.Distribution = $other.Distribution + $this.ReadPercent = $other.ReadPercent + $this.ThreadCount = $other.ThreadCount + $this.IndexMode = $other.IndexMode + $this.LockMode = $other.LockMode + $this.Iterations = $other.Iterations + $this.SmallData = $other.SmallData + $this.SmallMemory = $other.SmallMemory + $this.SyntheticData = $other.SyntheticData + } + + [Result] CalculateDifference([Result]$newResult) { + $result = [Result]::new($newResult) + $result.MeanDiff = $newResult.MeanDiff - $this.MeanDiff + $result.MeanDiffPercent = ($result.MeanDiff / $this.MeanDiff) * 100 + $result.StdDevDiff = $newResult.StdDevDiff - $this.StdDevDiff + $result.StdDevDiffPercent = $newResult.StdDevDiffPercent - $this.StdDevDiffPercent + return $result + } + + [int] CompareTo($other) + { + If (-Not($other -is [Result])) { + Throw "'other' is not Result" + } + + # Sort in descending order + return $other.MeanDiffPercent - $this.MeanDiffPercent + } + + [bool] Equals($other) + { + Write-Host "in Equals" + If (-Not($other -is [Result])) { + Throw "'other' is not Result" + } + return $this.Numa -eq $other.Numa + -and $this.Distribution -eq $other.Distribution + -and $this.ReadPercent -eq $other.ReadPercent + -and $this.ThreadCount -eq $other.ThreadCount + -and $this.IndexMode -eq $other.IndexMode + -and $this.LockMode -eq $other.LockMode + -and $this.Iterations -eq $other.Iterations + -and $this.SmallData -eq $other.SmallData + -and $this.SmallMemory -eq $other.SmallMemory + -and $this.SyntheticData -eq $other.SyntheticData + } + + [int] GetHashCode() { + return ($this.Numa, $this.Distribution, $this.ReadPercent, $this.ThreadCount, $this.IndexMode, $this.LockMode, + $this.Iterations, $this.SmallData, $this.SmallMemory, $this.SyntheticData).GetHashCode(); + } +} + +# These have the same name format in each directory, qualified by parameters. +$oldOnlyFileNames = New-Object Collections.Generic.List[String] +$newOnlyFileNames = New-Object Collections.Generic.List[String] + +$LoadResults = New-Object Collections.Generic.List[Result] +$RunResults = New-Object Collections.Generic.List[Result] + +function ParseResultFile([String]$fileName) { + $loadResult = $null + $runResult = $null + foreach($line in Get-Content($fileName)) { + if ($line.StartsWith("##20;")) { + $loadResult = [Result]::new($line) + continue + } + if ($line.StartsWith("##21;")) { + $runResult = [Result]::new($line) + continue + } + } + if ($null -eq $loadResult) { + Throw "$fileName has no Load Result" + } + if ($null -eq $runResult) { + Throw "$fileName has no Run Result" + } + return ($loadResult, $runResult) +} + +foreach($oldFile in Get-ChildItem "$OldDir/results_*") { + $newName = "$NewDir/$($oldFile.Name)"; + if (!(Test-Path $newName)) { + $oldOnlyFileNames.Add($oldFile) + continue + } + $newFile = Get-ChildItem $newName + + $oldLoadResult, $oldRunResult = ParseResultFile $oldFile.FullName + $newLoadResult, $newRunResult = ParseResultFile $newFile.FullName + + $LoadResults.Add($oldLoadResult.CalculateDifference($newLoadResult)) + $RunResults.Add($oldRunResult.CalculateDifference($newRunResult)) +} + +foreach($newFile in Get-ChildItem "$NewDir/results_*") { + $oldName = "$OldDir/$($newFile.Name)"; + if (!(Test-Path $oldName)) { + $newOnlyFileNames.Add($newFile) + continue + } +} + +if ($oldOnlyFileNames.Count -gt 0) { + Write-Host "The following files were found only in $OldDir" + foreach ($fileName in $oldOnlyFileNames) { + Write-Host " $fileName" + } +} +if ($newOnlyFileNames.Count -gt 0) { + Write-Host "The following files were found only in $NewDir" + foreach ($fileName in $newOnlyFileNames) { + Write-Host " $fileName" + } +} + +if ($oldOnlyFileNames.Count -gt 0 -or $newOnlyFileNames.Count -gt 0) { + Start-Sleep -Seconds 3 +} + +$LoadResults.Sort() +$RunResults.Sort() +$LoadResults | Out-GridView -Title "Loading Comparison (Inserts Per Second): $OldDir -vs- $NewDir" +$RunResults | Out-GridView -Title "Experiment Run Comparison(Operations Per Second): $OldDir -vs- $NewDir" diff --git a/cs/benchmark/scripts/run_benchmark.ps1 b/cs/benchmark/scripts/run_benchmark.ps1 new file mode 100644 index 000000000..4cee2e1ba --- /dev/null +++ b/cs/benchmark/scripts/run_benchmark.ps1 @@ -0,0 +1,138 @@ +<# +.SYNOPSIS + Runs one or more builds of FASTER.benchmark.exe with multiple parameter permutations and generates corresponding directories of result files named for those permutations. + +.DESCRIPTION + This is intended to run performance-testing parameter permutations on one or more builds of FASTER.benchmark.exe, to be compared by compare_runs.ps1. + The default execution of this script does a performance run on all FASTER.benchmark.exes identified in ExeDirs, and places their output into correspondingly-named + result directories, to be evaluated with compare_runs.ps1. + + This script functions best if you have a dedicated performance-testing machine that is not your build machine. Use the following steps: + 1. Create a directory on the perf machine for your test + 2. Xcopy the baseline build's Release directory to your perf folder. This script will start at the netcoreapp3.1 directory to traverse to FASTER.benchmark.exe. + Name this folder something that indicates its role, such as 'baseline'. + 3. Similarly, xcopy the new build's Release directory to your perf folder, naming it with some indication of what was changed, for example 'refactor_FASTERImpl". + 4. Copy this script and, if you will want to compare runs on the perf machine, compare_runs.ps1 to the perf folder. + 5. In a remote desktop on the perf machine, change to your folder, and run this file with those directory names. See .EXAMPLE for details. + +.PARAMETER ExeDirs + One or more directories from which to run FASTER.benchmark.exe builds. This is a Powershell array of strings; thus from the windows command line + the directory names should be joined by , (comma) with no spaces: + pwsh -c ./run_benchmark.ps1 './baseline','./refactor_FASTERImpl' + Single (or double) quotes are optional and may be omitted if the directory paths do not contain spaces. + +.PARAMETER RunSeconds + Used primarily to debug changes to this script or do a quick one-off run; the default is 30 seconds. + +.PARAMETER NumThreads + Used primarily to debug changes to this script or do a quick one-off run; the default is multiple counts as defined in the script. + +.PARAMETER UseRecover + Used primarily to debug changes to this script or do a quick one-off run; the default is false. + +.EXAMPLE + ./run_benchmark.ps1 './baseline','./refactor_FASTERImpl' + + If run from your perf directory using the setup from .DESCRIPTION, this will create and populate the following folders: + results_baseline + results_refactor_FASTERImpl + You can then run compare.ps1 on those two directories. + +.EXAMPLE + ./run_benchmark.ps1 './baseline','./refactor_FASTERImpl' -RunSeconds 3 -NumThreads 8 -UseRecover + + Does a quick run (e.g. test changes to this file). +#> +param ( + [Parameter(Mandatory=$true)] [String[]]$ExeDirs, + [Parameter(Mandatory=$false)] [uint]$RunSeconds = 30, + [Parameter(Mandatory=$false)] [uint]$NumThreads, + [Parameter(Mandatory=$false)] [switch]$UseRecover +) + +if (-not(Test-Path d:/data)) { + throw "Cannot find d:/data" +} + +$benchmarkExe = "netcoreapp3.1/win7-x64/FASTER.benchmark.exe" +$exeNames = [String[]]($ExeDirs | ForEach-Object{"$_/$benchmarkExe"}) + +Foreach ($exeName in $exeNames) { + if (Test-Path "$exeName") { + Write-Host "Found: $exeName" + continue + } + throw "Cannot find: $exeName" +} + +$resultDirs = [String[]]($ExeDirs | ForEach-Object{"./" + (Get-Item $_).Name}) +Foreach ($resultDir in $resultDirs) { + Write-Host $resultDir + if (Test-Path $resultDir) { + throw "$resultDir already exists (or possible duplication of leaf name in ExeDirs)" + } + New-Item "$resultDir" -ItemType Directory +} + +$iterations = 1 +$distributions = ("uniform", "zipf") +$readPercents = (0, 100) +$threadCounts = (1, 16, 32, 48, 64) +$indexModes = (0, 1, 2) #, 3) +$lockModes = (0, 1) +$smallDatas = (0) #, 1) +$smallMemories = (0) #, 1) +$syntheticDatas = (0) #, 1) +$k = "" + +if ($NumThreads) { + $threadCounts = ($NumThreads) +} +if ($UseRecover) { + $k = "-k" +} + +# Numa will always be either 0 or 1, so "Numas.Count" is 1 +$permutations = $distributions.Count * + $readPercents.Count * + $threadCounts.Count * + $indexModes.Count * + $lockModes.Count * + $smallDatas.Count * + $smallMemories.Count * + $syntheticDatas.Count + +$permutation = 1 +foreach ($d in $distributions) { + foreach ($r in $readPercents) { + foreach ($t in $threadCounts) { + foreach ($x in $indexModes) { + foreach ($z in $lockModes) { + foreach ($sd in $smallDatas) { + foreach ($sm in $smallMemories) { + foreach ($sy in $syntheticDatas) { + Write-Host + Write-Host "Permutation $permutation of $permutations" + ++$permutation + + # Only certain combinations of Numa/Threads are supported + $n = ($t -lt 64) ? 0 : 1; + + for($ii = 0; $ii -lt $exeNames.Count; ++$ii) { + $exeName = $exeNames[$ii] + $resultDir = $resultDirs[$ii] + + Write-Host + Write-Host "Generating $($ii + 1) of $($exeNames.Count) results to $resultDir for: -n $n -d $d -r $r -t $t -x $x -z $z -i $iterations --runsec $RunSeconds $k" + + # RunSec and Recover are for one-off operations and are not recorded in the filenames. + & "$exeName" -b 0 -n $n -d $d -r $r -t $t -x $x -z $z -i $iterations --runsec $RunSeconds $k | Tee-Object "$resultDir/results_n-$($n)_d-$($d)_r-$($r)_t-$($t)_x-$($x)_z-$($z).txt" + } + } + } + } + } + } + } + } +} From ef80838bc6d02cfbae8a935873123ad0dd0611ce Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Wed, 17 Mar 2021 12:19:49 -0700 Subject: [PATCH 21/37] updates to benchmark scripts --- cs/benchmark/scripts/compare_runs.ps1 | 3 ++- cs/benchmark/scripts/run_benchmark.ps1 | 37 +++++++++++++++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/cs/benchmark/scripts/compare_runs.ps1 b/cs/benchmark/scripts/compare_runs.ps1 index 0e5b15ef7..395e4d4f7 100644 --- a/cs/benchmark/scripts/compare_runs.ps1 +++ b/cs/benchmark/scripts/compare_runs.ps1 @@ -102,7 +102,8 @@ class Result : System.IComparable, System.IEquatable[Object] { } # Sort in descending order - return $other.MeanDiffPercent - $this.MeanDiffPercent + $cmp = $other.MeanDiffPercent.CompareTo($this.MeanDiffPercent) + return ($cmp -eq 0) ? $other.MeanDiff.CompareTo($this.MeanDiff) : $cmp } [bool] Equals($other) diff --git a/cs/benchmark/scripts/run_benchmark.ps1 b/cs/benchmark/scripts/run_benchmark.ps1 index 4cee2e1ba..db39ca940 100644 --- a/cs/benchmark/scripts/run_benchmark.ps1 +++ b/cs/benchmark/scripts/run_benchmark.ps1 @@ -22,12 +22,23 @@ Single (or double) quotes are optional and may be omitted if the directory paths do not contain spaces. .PARAMETER RunSeconds + Number of seconds to run the experiment. Used primarily to debug changes to this script or do a quick one-off run; the default is 30 seconds. .PARAMETER NumThreads + Number of threads to use. + Used primarily to debug changes to this script or do a quick one-off run; the default is multiple counts as defined in the script. + +.PARAMETER IndexMode + Indexing mode to use: 0 = None, 1 = Key, 2 = Value, 3 = both. + Used primarily to debug changes to this script or do a quick one-off run; the default is multiple counts as defined in the script. + +.PARAMETER LockMode + Locking mode to use: 0 = No locking, 1 = RecordInfo locking Used primarily to debug changes to this script or do a quick one-off run; the default is multiple counts as defined in the script. .PARAMETER UseRecover + Recover the FasterKV from a checkpoint of a previous run rather than loading it from data. Used primarily to debug changes to this script or do a quick one-off run; the default is false. .EXAMPLE @@ -44,9 +55,11 @@ Does a quick run (e.g. test changes to this file). #> param ( - [Parameter(Mandatory=$true)] [String[]]$ExeDirs, - [Parameter(Mandatory=$false)] [uint]$RunSeconds = 30, - [Parameter(Mandatory=$false)] [uint]$NumThreads, + [Parameter(Mandatory=$true)] [string[]]$ExeDirs, + [Parameter(Mandatory=$false)] [int]$RunSeconds = 30, + [Parameter(Mandatory=$false)] [int]$ThreadCount = -1, + [Parameter(Mandatory=$false)] [int]$IndexMode = -1, + [Parameter(Mandatory=$false)] [int]$lockMode = -1, [Parameter(Mandatory=$false)] [switch]$UseRecover ) @@ -65,7 +78,7 @@ Foreach ($exeName in $exeNames) { throw "Cannot find: $exeName" } -$resultDirs = [String[]]($ExeDirs | ForEach-Object{"./" + (Get-Item $_).Name}) +$resultDirs = [String[]]($ExeDirs | ForEach-Object{"./results_" + (Get-Item $_).Name}) Foreach ($resultDir in $resultDirs) { Write-Host $resultDir if (Test-Path $resultDir) { @@ -74,7 +87,7 @@ Foreach ($resultDir in $resultDirs) { New-Item "$resultDir" -ItemType Directory } -$iterations = 1 +$iterations = 7 $distributions = ("uniform", "zipf") $readPercents = (0, 100) $threadCounts = (1, 16, 32, 48, 64) @@ -85,14 +98,20 @@ $smallMemories = (0) #, 1) $syntheticDatas = (0) #, 1) $k = "" -if ($NumThreads) { - $threadCounts = ($NumThreads) +if ($ThreadCount -ge 0) { + $threadCounts = ($ThreadCount) +} +if ($IndexMode -ge 0) { + $indexModes = ($IndexMode) +} +if ($LockMode -ge 0) { + $lockModes = ($LockMode) } if ($UseRecover) { $k = "-k" } -# Numa will always be either 0 or 1, so "Numas.Count" is 1 +# Numa will always be set in the internal loop body to either 0 or 1, so "Numas.Count" is effectively 1 $permutations = $distributions.Count * $readPercents.Count * $threadCounts.Count * @@ -123,7 +142,7 @@ foreach ($d in $distributions) { $resultDir = $resultDirs[$ii] Write-Host - Write-Host "Generating $($ii + 1) of $($exeNames.Count) results to $resultDir for: -n $n -d $d -r $r -t $t -x $x -z $z -i $iterations --runsec $RunSeconds $k" + Write-Host "Iteration $permutation/$permutations generating results $($ii + 1)/$($exeNames.Count) to $resultDir for: -n $n -d $d -r $r -t $t -x $x -z $z -i $iterations --runsec $RunSeconds $k" # RunSec and Recover are for one-off operations and are not recorded in the filenames. & "$exeName" -b 0 -n $n -d $d -r $r -t $t -x $x -z $z -i $iterations --runsec $RunSeconds $k | Tee-Object "$resultDir/results_n-$($n)_d-$($d)_r-$($r)_t-$($t)_x-$($x)_z-$($z).txt" From 2ce51aaf3211c00f649e5d7126ea109c0dd7a37b Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Sun, 28 Mar 2021 01:17:16 -0700 Subject: [PATCH 22/37] Add SubsetIndexSessionBroker; add SimpleIndex tests --- cs/benchmark/SecondaryIndexes.cs | 16 +- .../ClientSession/AdvancedClientSession.cs | 8 +- cs/src/core/ClientSession/ClientSession.cs | 14 +- cs/src/core/Index/FASTER/FASTER.cs | 16 +- cs/src/core/Index/FASTER/FASTERImpl.cs | 6 +- cs/src/core/Index/FASTER/FASTERLegacy.cs | 2 + .../core/Index/Interfaces/IFasterSession.cs | 2 + cs/src/core/SecondaryIndex/ISecondaryIndex.cs | 34 ++- .../SecondaryIndex/SecondaryIndexBroker.cs | 94 ++++--- .../SecondaryIndexSessionBroker.cs | 54 +++++ cs/test/LockTests.cs | 5 - cs/test/ReadAddressTests.cs | 8 +- cs/test/SimpleIndexBase.cs | 229 ++++++++++++++++++ cs/test/SimpleKeyIndexTests.cs | 95 ++++++++ cs/test/SimpleValueIndexTests.cs | 95 ++++++++ 15 files changed, 596 insertions(+), 82 deletions(-) create mode 100644 cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs create mode 100644 cs/test/SimpleIndexBase.cs create mode 100644 cs/test/SimpleKeyIndexTests.cs create mode 100644 cs/test/SimpleValueIndexTests.cs diff --git a/cs/benchmark/SecondaryIndexes.cs b/cs/benchmark/SecondaryIndexes.cs index 574ee7b02..3922fc10d 100644 --- a/cs/benchmark/SecondaryIndexes.cs +++ b/cs/benchmark/SecondaryIndexes.cs @@ -11,11 +11,13 @@ class NullKeyIndex : ISecondaryKeyIndex public bool IsMutable => true; - public void Delete(ref Key key) { } + public void SetSessionSlot(long slot) { } - public void Insert(ref Key key) { } + public void Delete(ref Key key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Upsert(ref Key key, bool isMutable) { } + public void Insert(ref Key key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { } + + public void Upsert(ref Key key, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } } class NullValueIndex : ISecondaryValueIndex @@ -24,10 +26,12 @@ class NullValueIndex : ISecondaryValueIndex public bool IsMutable => true; - public void Delete(long recordId) { } + public void SetSessionSlot(long slot) { } + + public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Insert(ref Value value, long recordId) { } + public void Insert(ref Value value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Upsert(ref Value value, long recordId, bool isMutable) { } + public void Upsert(ref Value value, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } } } diff --git a/cs/src/core/ClientSession/AdvancedClientSession.cs b/cs/src/core/ClientSession/AdvancedClientSession.cs index 4c504200a..13eb25eb4 100644 --- a/cs/src/core/ClientSession/AdvancedClientSession.cs +++ b/cs/src/core/ClientSession/AdvancedClientSession.cs @@ -37,6 +37,8 @@ public sealed class AdvancedClientSession>.NotAsyncSessionErr; internal AdvancedClientSession( @@ -772,7 +774,7 @@ public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref Reco [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) => _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, ref recordInfo, address) - && _clientSession.fht.UpdateSIForIPU(ref dst, address); + && _clientSession.fht.UpdateSIForIPU(ref dst, address, this.SecondaryIndexSessionBroker); private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) { @@ -856,7 +858,7 @@ public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref Re [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool InPlaceUpdaterNoLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) => _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, ref recordInfo, address) - && _clientSession.fht.UpdateSIForIPU(ref value, address); + && _clientSession.fht.UpdateSIForIPU(ref value, address, this.SecondaryIndexSessionBroker); private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) { @@ -921,6 +923,8 @@ public IHeapContainer GetHeapContainer(ref Input input) return new VarLenHeapContainer(ref input, _clientSession.inputVariableLengthStruct, _clientSession.fht.hlog.bufferPool); } + + public SecondaryIndexSessionBroker SecondaryIndexSessionBroker => _clientSession.SecondaryIndexSessionBroker; } } } diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index 75c58aa75..b99cf6487 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -37,6 +37,8 @@ public sealed class ClientSession internal readonly InternalFasterSession FasterSession; + internal readonly SecondaryIndexSessionBroker SecondaryIndexSessionBroker = new SecondaryIndexSessionBroker(); + internal const string NotAsyncSessionErr = "Session does not support async operations"; internal ClientSession( @@ -780,7 +782,7 @@ public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref Reco [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, long address) => _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst) - && _clientSession.fht.UpdateSIForIPU(ref dst, address); + && _clientSession.fht.UpdateSIForIPU(ref dst, address, this.SecondaryIndexSessionBroker); [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) @@ -801,13 +803,13 @@ private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref public void ConcurrentDeleter(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) { if (!this.SupportsLocking) - ConcurrentDeleterNoLock(ref key, ref value, ref recordInfo, address); + ConcurrentDeleterNoLock(ref recordInfo); else ConcurrentDeleterLock(ref key, ref value, ref recordInfo, address); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConcurrentDeleterNoLock(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) + private void ConcurrentDeleterNoLock(ref RecordInfo recordInfo) { // Non-Advanced IFunctions has no ConcurrentDeleter recordInfo.Tombstone = true; @@ -819,7 +821,7 @@ private void ConcurrentDeleterLock(ref Key key, ref Value value, ref RecordInfo this.Lock(ref recordInfo, ref key, ref value, LockType.Exclusive, ref context); try { - ConcurrentDeleterNoLock(ref key, ref value, ref recordInfo, address); + ConcurrentDeleterNoLock(ref recordInfo); } finally { @@ -865,7 +867,7 @@ public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref Re [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool InPlaceUpdaterNoLock(ref Key key, ref Input input, ref Value value, long address) => _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value) - && _clientSession.fht.UpdateSIForIPU(ref value, address); + && _clientSession.fht.UpdateSIForIPU(ref value, address, this.SecondaryIndexSessionBroker); private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) { @@ -929,6 +931,8 @@ public IHeapContainer GetHeapContainer(ref Input input) return new VarLenHeapContainer(ref input, _clientSession.inputVariableLengthStruct, _clientSession.fht.hlog.bufferPool); } + + public SecondaryIndexSessionBroker SecondaryIndexSessionBroker => _clientSession.SecondaryIndexSessionBroker; } } } diff --git a/cs/src/core/Index/FASTER/FASTER.cs b/cs/src/core/Index/FASTER/FASTER.cs index 56de54404..7241be611 100644 --- a/cs/src/core/Index/FASTER/FASTER.cs +++ b/cs/src/core/Index/FASTER/FASTER.cs @@ -607,11 +607,11 @@ internal Status ContextUpsert(ref Key key } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool UpdateSIForIPU(ref Value value, long address) + internal bool UpdateSIForIPU(ref Value value, long address, SecondaryIndexSessionBroker indexSessionBroker) { // KeyIndexes do not need notification of in-place updates because the key does not change. if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Upsert(ref value, address); + this.SecondaryIndexBroker.Upsert(ref value, address, indexSessionBroker); return true; } @@ -620,20 +620,20 @@ private void UpdateSIForInsert(ref Key ke where FasterSession : IFasterSession { if (!fasterSession.SupportsLocking) - UpdateSIForInsertNoLock(ref key, ref value, ref recordInfo, address); + UpdateSIForInsertNoLock(ref key, ref value, ref recordInfo, address, fasterSession.SecondaryIndexSessionBroker); else UpdateSIForInsertLock(ref key, ref value, ref recordInfo, address, fasterSession); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void UpdateSIForInsertNoLock(ref Key key, ref Value value, ref RecordInfo recordInfo, long address) + private void UpdateSIForInsertNoLock(ref Key key, ref Value value, ref RecordInfo recordInfo, long address, SecondaryIndexSessionBroker indexSessionBroker) { if (!recordInfo.Invalid && !recordInfo.Tombstone) { if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) - this.SecondaryIndexBroker.Insert(ref key); + this.SecondaryIndexBroker.Insert(ref key, address, indexSessionBroker); if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Insert(ref value, address); + this.SecondaryIndexBroker.Insert(ref value, address, indexSessionBroker); } } @@ -644,7 +644,7 @@ private void UpdateSIForInsertLock(ref Ke fasterSession.Lock(ref recordInfo, ref key, ref value, LockType.Exclusive, ref context); try { - UpdateSIForInsertNoLock(ref key, ref value, ref recordInfo, address); + UpdateSIForInsertNoLock(ref key, ref value, ref recordInfo, address, fasterSession.SecondaryIndexSessionBroker); } finally { @@ -720,7 +720,7 @@ internal Status ContextDelete( // No need to lock here; we have just written a new record with a tombstone, so it will not be changed // TODO - but this can race with an INSERT... - this.UpdateSIForDelete(ref key, pcontext.logicalAddress, isNewRecord: true); + this.UpdateSIForDelete(ref key, pcontext.logicalAddress, isNewRecord: true, fasterSession.SecondaryIndexSessionBroker); } Debug.Assert(serialNo >= sessionCtx.serialNum, "Operation serial numbers must be non-decreasing"); diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index 97c7aa4c9..3e5e1602f 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -1179,12 +1179,12 @@ internal OperationStatus InternalDelete( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool UpdateSIForDelete(ref Key key, long address, bool isNewRecord) + internal bool UpdateSIForDelete(ref Key key, long address, bool isNewRecord, SecondaryIndexSessionBroker indexSessionBroker) { if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) - this.SecondaryIndexBroker.Delete(ref key); + this.SecondaryIndexBroker.Delete(ref key, address, indexSessionBroker); if (!isNewRecord && this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Delete(address); + this.SecondaryIndexBroker.Delete(address, indexSessionBroker); return true; } diff --git a/cs/src/core/Index/FASTER/FASTERLegacy.cs b/cs/src/core/Index/FASTER/FASTERLegacy.cs index b2b7fe7c6..c02b8b67d 100644 --- a/cs/src/core/Index/FASTER/FASTERLegacy.cs +++ b/cs/src/core/Index/FASTER/FASTERLegacy.cs @@ -409,6 +409,8 @@ public IHeapContainer GetHeapContainer(ref Input input) { return new StandardHeapContainer(ref input); } + + public SecondaryIndexSessionBroker SecondaryIndexSessionBroker => null; } } diff --git a/cs/src/core/Index/Interfaces/IFasterSession.cs b/cs/src/core/Index/Interfaces/IFasterSession.cs index 29fbfb65a..dbcd18579 100644 --- a/cs/src/core/Index/Interfaces/IFasterSession.cs +++ b/cs/src/core/Index/Interfaces/IFasterSession.cs @@ -25,5 +25,7 @@ internal interface IFasterSession internal interface IFasterSession : IAdvancedFunctions, IFasterSession, IVariableLengthStruct { IHeapContainer GetHeapContainer(ref Input input); + + SecondaryIndexSessionBroker SecondaryIndexSessionBroker { get; } } } \ No newline at end of file diff --git a/cs/src/core/SecondaryIndex/ISecondaryIndex.cs b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs index e5ba15305..ab90b810c 100644 --- a/cs/src/core/SecondaryIndex/ISecondaryIndex.cs +++ b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs @@ -17,6 +17,11 @@ public interface ISecondaryIndex /// If true, the index is updated immediately on each FasterKV operation; otherwise it is updated only when record pages go ReadOnly. /// bool IsMutable { get; } + + /// + /// The slot id to be passed to ; called by . + /// + void SetSessionSlot(long slot); } /// @@ -28,29 +33,35 @@ public interface ISecondaryKeyIndex : ISecondaryIndex /// Inserts a key into the secondary index. Called only for mutable indexes, on the initial insert of a Key. /// /// The key to be inserted; always mutable + /// The identifier of the record containing the + /// The for the primary FasterKV session making this call /// /// If the index is mutable and the is already there, this call should be ignored, because it is the result /// of a race in which the record in the primary FasterKV was updated after the initial insert but before this method /// was called. /// - void Insert(ref TKVKey key); + void Insert(ref TKVKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker); /// /// Upserts a key into the secondary index. This may be called either immediately during a FasterKV operation, or when the page containing a record goes ReadOnly. /// /// The key to be inserted - /// Whether the recordId was in the mutable region of FASTER. If true, the record may subsequently be Upserted or Deleted. + /// The identifier of the record containing the + /// Whether the recordId was in the mutable region of FASTER. If true, the record may subsequently be Upserted or Deleted. + /// The for the primary FasterKV session making this call /// /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. - /// In this case, is false, and the index may move the to an immutable storage area. + /// In this case, is false, and the index may move the to an immutable storage area. /// - void Upsert(ref TKVKey key, bool isMutable); + void Upsert(ref TKVKey key, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); /// /// Removes a key from the secondary index. Called only for mutable indexes. /// /// The key to be removed - void Delete(ref TKVKey key); + /// The identifier of the record containing the + /// The for the primary FasterKV session making this call + void Delete(ref TKVKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker); } /// @@ -64,13 +75,14 @@ public interface ISecondaryValueIndex : ISecondaryIndex /// /// The value to be inserted; always mutable /// The identifier of the record containing the + /// The for the primary FasterKV session making this call /// /// If the index is mutable and the is already there for this , /// this call should be ignored, because it is the result of a race in which the record in the primary FasterKV was /// updated after the initial insert but before this method was called, so the on this call /// would overwrite it with an obsolete value. /// - void Insert(ref TKVValue value, long recordId); + void Insert(ref TKVValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker); /// /// Upserts a recordId into the secondary index, with the associated value from which the index derives its key(s). @@ -78,17 +90,19 @@ public interface ISecondaryValueIndex : ISecondaryIndex /// /// The value to be upserted /// The identifier of the record containing the - /// Whether the recordId was in the mutable region of FASTER; if so, it may subsequently be Upserted or Deleted. + /// Whether the recordId was in the mutable region of FASTER; if so, it may subsequently be Upserted or Deleted. + /// The for the primary FasterKV session making this call /// /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. - ///In this case, is false, and the index may move the to an immutable storage area. + /// In this case, is false, and the index may move the to an immutable storage area. /// - void Upsert(ref TKVValue value, long recordId, bool isMutable); + void Upsert(ref TKVValue value, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); /// /// Removes a recordId from the secondary index. Called only for mutable indexes. /// /// The recordId to be removed - void Delete(long recordId); + /// The for the primary FasterKV session making this call + void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker); } } \ No newline at end of file diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs index 5a9de12ac..ef0cbbc60 100644 --- a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs +++ b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; +#pragma warning disable IDE0056 // Use index operator (^ is not supported on .NET Framework or NETCORE pre-3.0) + namespace FASTER.core { /// @@ -21,33 +23,39 @@ public class SecondaryIndexBroker private ISecondaryValueIndex[] mutableValueIndexes = Array.Empty>(); internal int MutableValueIndexCount => mutableValueIndexes.Length; + readonly object membershipLock = new object(); + /// /// Adds a secondary index to the list. /// /// public void AddIndex(ISecondaryIndex index) { - static bool addSpecific(TIndex idx, ref TIndex[] vec) + bool isMutable = false; + + bool addSpecific(TIndex idx, ref TIndex[] vec) where TIndex : ISecondaryIndex { - if (idx is { }) + if (idx is null) + return false; + if (idx.IsMutable) { - if (idx.IsMutable) - { - Array.Resize(ref vec, vec.Length + 1); -#pragma warning disable IDE0056 // Use index operator (^ is not supported on .NET Framework or NETCORE pre-3.0) - vec[vec.Length - 1] = idx; -#pragma warning restore IDE0056 // Use index operator - } - return true; + Array.Resize(ref vec, vec.Length + 1); + vec[vec.Length - 1] = idx; + isMutable = true; } - return false; + return true; } - if (!addSpecific(index as ISecondaryKeyIndex, ref mutableKeyIndexes) - && !addSpecific(index as ISecondaryValueIndex, ref mutableValueIndexes)) - throw new SecondaryIndexException("Object is not a KeyIndex or ValueIndex"); - indexes[index.Name] = index; + lock (membershipLock) + { + if (!addSpecific(index as ISecondaryKeyIndex, ref mutableKeyIndexes) + && !addSpecific(index as ISecondaryValueIndex, ref mutableValueIndexes)) + throw new SecondaryIndexException("Object is not a KeyIndex or ValueIndex"); + this.HasMutableIndexes |= isMutable; + indexes[index.Name] = index; + index.SetSessionSlot(SecondaryIndexSessionBroker.NextSessionSlot++); + } } /// @@ -58,7 +66,7 @@ static bool addSpecific(TIndex idx, ref TIndex[] vec) /// /// The number of indexes registered. /// - public bool HasMutableIndexes => mutableKeyIndexes.Length + mutableValueIndexes.Length > 0; + public bool HasMutableIndexes { get; private set; } /// /// Enumerates the list of indexes. @@ -79,30 +87,33 @@ static bool addSpecific(TIndex idx, ref TIndex[] vec) /// Inserts a mutable key into all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Insert(ref TKVKey key) + public void Insert(ref TKVKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { - foreach (var keyIndex in mutableKeyIndexes) - keyIndex.Insert(ref key); + var mki = this.mutableKeyIndexes; + foreach (var keyIndex in mki) + keyIndex.Insert(ref key, recordId, indexSessionBroker); } /// /// Upserts a mutable key into all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Upsert(ref TKVKey key) + public void Upsert(ref TKVKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { - foreach (var keyIndex in mutableKeyIndexes) - keyIndex.Upsert(ref key, true); + var mki = this.mutableKeyIndexes; + foreach (var keyIndex in mki) + keyIndex.Upsert(ref key, recordId, isMutableRecord: true, indexSessionBroker); } /// - /// Deletes a key from all mutable secondary key indexes. + /// Deletes recordId for a key from all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Delete(ref TKVKey key) + public void Delete(ref TKVKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { - foreach (var keyIndex in mutableKeyIndexes) - keyIndex.Delete(ref key); + var mki = this.mutableKeyIndexes; + foreach (var keyIndex in mki) + keyIndex.Delete(ref key, recordId, indexSessionBroker); } #endregion Mutable KeyIndexes @@ -111,46 +122,49 @@ public void Delete(ref TKVKey key) /// Inserts a recordId keyed by a mutable value into all mutable secondary value indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Insert(ref TKVValue value, long recordId) + public void Insert(ref TKVValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { - foreach (var valueIndex in mutableValueIndexes) - valueIndex.Insert(ref value, recordId); + var mvi = this.mutableValueIndexes; + foreach (var valueIndex in mvi) + valueIndex.Insert(ref value, recordId, indexSessionBroker); } /// /// Upserts a recordId keyed by a mutable value into all mutable secondary value indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Upsert(ref TKVValue value, long recordId) + public void Upsert(ref TKVValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { - foreach (var valueIndex in mutableValueIndexes) - valueIndex.Upsert(ref value, recordId, isMutable:false); + var mvi = this.mutableValueIndexes; + foreach (var valueIndex in mvi) + valueIndex.Upsert(ref value, recordId, isMutableRecord:false, indexSessionBroker); } /// /// Deletes a recordId keyed by a mutable value from all mutable secondary value indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Delete(long recordId) + public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) { - foreach (var valueIndex in mutableValueIndexes) - valueIndex.Delete(recordId); + var mvi = this.mutableValueIndexes; + foreach (var valueIndex in mvi) + valueIndex.Delete(recordId, indexSessionBroker); } #endregion Mutable ValueIndexes /// /// Upserts a readonly key into all secondary key indexes and readonly values into secondary value indexes. /// - public void UpsertReadOnly(ref TKVKey key, ref TKVValue value, long recordId) + public void UpsertReadOnly(ref TKVKey key, ref TKVValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { - foreach (var index in indexes) + var idxs = this.indexes; + foreach (var index in idxs) { if (index is ISecondaryKeyIndex keyIndex) - keyIndex.Upsert(ref key, isMutable: false); + keyIndex.Upsert(ref key, recordId, isMutableRecord: false, indexSessionBroker); else if (index is ISecondaryValueIndex valueIndex) - valueIndex.Upsert(ref value, recordId, isMutable: false); + valueIndex.Upsert(ref value, recordId, isMutableRecord: false, indexSessionBroker); } } - } } diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs new file mode 100644 index 000000000..8857f2fdb --- /dev/null +++ b/cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace FASTER.core +{ + /// + /// Allows an index to register its own concept of sessions to be attached to a primary FasterKV session. + /// + public class SecondaryIndexSessionBroker + { + readonly object sessionLock = new object(); + + // This never decreases in size (if we support removal, we'll just null the slot). + object[] indexSessions = new object[0]; + + // The next free session slot; these slots are only valid for the current instantiation of the FasterKV. + // If we support removing indexes, add a stack of free slots, and null the slot in SecondaryIndexSessionBroker.indexSessions. + internal static long NextSessionSlot = 0; + + /// + /// Gets a previously registered session object attached by an index to this primary FasterKV session. + /// + /// The value passed to by + /// The session object that was attached by an index to this primary FasterKV session, or null if none was attached. + public object GetSessionObject(long slot) => slot < this.indexSessions.Length ? this.indexSessions[slot] : null; + + /// + /// REgisters a session object to be attached by an index to this primary FasterKV session. + /// + /// The value passed to by + /// + /// The session object to be attached by an index to this primary FasterKV session. + public object SetSessionObject(long slot, object sessionObject) + { + if (slot >= this.indexSessions.Length) + { + if (slot > NextSessionSlot) + throw new FasterException("Secondary index session slot is out of range"); + + lock (sessionLock) + { + var vec = new object[slot + 1]; + Array.Copy(this.indexSessions, vec, this.indexSessions.Length); + this.indexSessions = vec; + } + } + + this.indexSessions[slot] = sessionObject; + return sessionObject; + } + } +} diff --git a/cs/test/LockTests.cs b/cs/test/LockTests.cs index 10729643e..b45bf5e37 100644 --- a/cs/test/LockTests.cs +++ b/cs/test/LockTests.cs @@ -15,11 +15,6 @@ internal class LockTests { internal class Functions : AdvancedSimpleFunctions { - public override void ConcurrentReader(ref int key, ref int input, ref int value, ref int dst, ref RecordInfo recordInfo, long address) - { - dst = value; - } - bool Increment(ref int dst) { ++dst; diff --git a/cs/test/ReadAddressTests.cs b/cs/test/ReadAddressTests.cs index d942e6a1a..2d653bfb2 100644 --- a/cs/test/ReadAddressTests.cs +++ b/cs/test/ReadAddressTests.cs @@ -68,11 +68,13 @@ private class InsertValueIndex : ISecondaryValueIndex public bool IsMutable => true; - public void Delete(long recordId) { } + public void SetSessionSlot(long slot) { } - public void Insert(ref Value value, long recordId) => lastWriteAddress = recordId; + public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Upsert(ref Value value, long recordId, bool isMutable) { } + public void Insert(ref Value value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) => lastWriteAddress = recordId; + + public void Upsert(ref Value value, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } } private static long SetReadOutput(long key, long value) => (key << 32) | value; diff --git a/cs/test/SimpleIndexBase.cs b/cs/test/SimpleIndexBase.cs new file mode 100644 index 000000000..cf6fcee7b --- /dev/null +++ b/cs/test/SimpleIndexBase.cs @@ -0,0 +1,229 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using FASTER.core; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FASTER.test +{ + class SimpleIndexBase + { + protected internal Dictionary> mutableRecords = new Dictionary>(); + protected internal Dictionary> immutableRecords = new Dictionary>(); + protected internal Dictionary reverseLookup = new Dictionary(); + + protected internal long sessionSlot = 0; + private bool isKeyIndex; + private readonly string indexType; + private Guid sessionId = Guid.Empty; + readonly Func indexKeyFunc; + + protected SimpleIndexBase(string name, bool isKeyIndex, Func indexKeyFunc, bool isMutableIndex) + { + this.Name = name; + this.IsMutable = isMutableIndex; + this.isKeyIndex = isKeyIndex; + this.indexType = isKeyIndex ? "KeyIndex" : "ValueIndex"; + this.indexKeyFunc = indexKeyFunc; + } + + public string Name { get; private set; } + + public bool IsMutable { get; private set; } + + public void SetSessionSlot(long slot) => this.sessionSlot = slot; + + // Name methods "BaseXxx" instead of using virtuals so we don't get unwanted implementations, e.g. of Delete taking a key for the ValueIndex. + + internal void BaseDelete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) + { + Assert.IsFalse(this.isKeyIndex); + if (!reverseLookup.TryGetValue(recordId, out TKey key)) + Assert.Fail($"RecordId {recordId} not found in revserse lookup for {indexType}"); + DoDelete(ref key, recordId, indexSessionBroker); + } + + internal void BaseDelete(ref TKey rawKey, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + => DoDelete(ref rawKey, recordId, indexSessionBroker); + + private void DoDelete(ref TKey rawKey, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + { + var key = this.indexKeyFunc(rawKey); + VerifyNotImmutable(ref key, recordId); + VerifySession(indexSessionBroker); + if (!mutableRecords.ContainsKey(key)) + Assert.Fail($"{indexType} '{key}' not found as index key"); + mutableRecords.Remove(key); + + Assert.IsTrue(reverseLookup.ContainsKey(recordId)); + reverseLookup.Remove(recordId); + } + + internal void BaseInsert(ref TKey rawKey, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + { + var key = this.indexKeyFunc(rawKey); + VerifyNotImmutable(ref key, recordId); + VerifySession(indexSessionBroker); + if (!mutableRecords.TryGetValue(key, out var recordIds)) + { + recordIds = new List(); + mutableRecords[key] = recordIds; + } + else if (recordIds.Contains(recordId)) + { + return; + } + + recordIds.Add(recordId); + AddToReverseLookup(ref key, recordId); + } + + internal void BaseUpsert(ref TKey rawKey, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) + { + if (isMutableRecord) + { + BaseInsert(ref rawKey, recordId, indexSessionBroker); + return; + } + + // Move from mutable to immutable + var key = this.indexKeyFunc(rawKey); + VerifyNotImmutable(ref key, recordId); + VerifySession(indexSessionBroker); + if (mutableRecords.TryGetValue(key, out var recordIds) && recordIds.Contains(recordId)) + recordIds.Remove(recordId); + + if (!immutableRecords.TryGetValue(key, out recordIds)) + { + recordIds = new List(); + mutableRecords[key] = recordIds; + } + recordIds.Add(recordId); + AddToReverseLookup(ref key, recordId); + } + + private void VerifySession(SecondaryIndexSessionBroker indexSessionBroker) + { + Assert.IsNotNull(indexSessionBroker); + var sessionObject = indexSessionBroker.GetSessionObject(this.sessionSlot); + Assert.AreEqual(sessionObject is null, this.sessionId == Guid.Empty); + + if (!(sessionObject is SimpleIndexSession session)) + { + if (sessionObject is { }) + Assert.Fail($"Unexpected session object type {sessionObject.GetType().Name} for {indexType}"); + + this.sessionId = Guid.NewGuid(); + session = new SimpleIndexSession() { Id = this.sessionId }; + indexSessionBroker.SetSessionObject(this.sessionSlot, session); + } + Assert.AreEqual(this.sessionId, session.Id); + } + + private List emptyRecordList = new List(); + + internal long[] Query(TKey rawKey) + { + var key = this.indexKeyFunc(rawKey); + if (!mutableRecords.TryGetValue(key, out var mutableRecordList)) + mutableRecordList = emptyRecordList; + if (!immutableRecords.TryGetValue(key, out var immutableRecordList)) + immutableRecordList = emptyRecordList; + + return mutableRecordList.Concat(immutableRecordList).ToArray(); + } + + internal int DistinctKeyCount => this.mutableRecords.Count + this.immutableRecords.Count; + + private void VerifyNotImmutable(ref TKey key, long recordId) + { + if (immutableRecords.TryGetValue(key, out var recordIds) && recordIds.Contains(recordId)) + Assert.Fail($"Unexpected update of recordId {recordId} for {indexType} '{key}'"); + } + + private void AddToReverseLookup(ref TKey key, long recordId) + { + if (reverseLookup.TryGetValue(recordId, out TKey existingKey)) + { + if (!existingKey.Equals(key)) + Assert.Fail($"Unexpected update of recordId {recordId} for {indexType} '{key}'"); + return; + } + reverseLookup[recordId] = key; + } + } + + class SimpleIndexSession + { + public Guid Id { get; set; } + } + + static class SimpleIndexUtils + { + internal const long NumKeys = 2_000L; + internal const long KeySpace = 1L << 14; + + internal const int ValueStart = 10_000; + + public static void PopulateInts(FasterKV fkv, bool useRMW, bool isAsync) + { + using var session = fkv.NewSession(new SimpleFunctions()); + + // Prpcess the batch of input data + for (int key = 0; key < NumKeys; key++) + { + var value = key + ValueStart; + if (useRMW) + { + if (isAsync) + session.RMWAsync(ref key, ref value).GetAwaiter().GetResult().Complete(); + else + session.RMW(ref key, ref value); + } + else + { + // TODO: UpsertAsync + //if (isAsync) + // session.UpsertAsync(ref key, ref value).GetAwaiter().GetResult().Complete(); + //else + session.Upsert(ref key, ref value); + } + } + + // Make sure operations are completed + session.CompletePending(true); + } + + public static void PopulateIntsWithAdvancedFunctions(FasterKV fkv, bool useRMW, bool isAsync) + { + using var session = fkv.NewSession(new AdvancedSimpleFunctions()); + + // Prpcess the batch of input data + for (int key = 0; key < NumKeys; key++) + { + var value = key + ValueStart; + if (useRMW) + { + if (isAsync) + session.RMWAsync(ref key, ref value).GetAwaiter().GetResult().Complete(); + else + session.RMW(ref key, ref value); + } + else + { + // TODO: UpsertAsync + //if (isAsync) + // session.UpsertAsync(ref key, ref value).GetAwaiter().GetResult().Complete(); + //else + session.Upsert(ref key, ref value); + } + } + + // Make sure operations are completed + session.CompletePending(true); + } + } +} diff --git a/cs/test/SimpleKeyIndexTests.cs b/cs/test/SimpleKeyIndexTests.cs new file mode 100644 index 000000000..1f0335bbf --- /dev/null +++ b/cs/test/SimpleKeyIndexTests.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.IO; +using FASTER.core; +using NUnit.Framework; + +namespace FASTER.test.SubsetIndex.SimpleKeyIndexTests +{ + class SimpleKeyIndexTests + { + class SimpleMutableKeyIndex : SimpleIndexBase, ISecondaryKeyIndex + { + internal SimpleMutableKeyIndex(string name, Func indexKeyFunc) : base(name, isKeyIndex: true, indexKeyFunc, isMutableIndex: true) { } + + public void Delete(ref TKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + => BaseDelete(ref key, recordId, indexSessionBroker); + + public void Insert(ref TKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + => BaseInsert(ref key, recordId, indexSessionBroker); + + public void Upsert(ref TKey key, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) + => BaseUpsert(ref key, recordId, isMutableRecord, indexSessionBroker); + } + + class SimpleImmutableKeyIndex : SimpleIndexBase, ISecondaryKeyIndex + { + internal SimpleImmutableKeyIndex(string name, Func indexKeyFunc) : base(name, isKeyIndex: true, indexKeyFunc, isMutableIndex: true) { } + + public void Delete(ref TKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + => BaseDelete(ref key, recordId, indexSessionBroker); + + public void Insert(ref TKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + => BaseInsert(ref key, recordId, indexSessionBroker); + + public void Upsert(ref TKey key, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) + => BaseUpsert(ref key, recordId, isMutableRecord, indexSessionBroker); + } + + const int keyDivisor = 20; + private string testPath; + private FasterKV fkv; + private IDevice log; + + [SetUp] + public void Setup() + { + if (testPath == null) + { + testPath = TestContext.CurrentContext.TestDirectory + "/" + Path.GetRandomFileName(); + if (!Directory.Exists(testPath)) + Directory.CreateDirectory(testPath); + } + + log = Devices.CreateLogDevice(testPath + $"/{TestContext.CurrentContext.Test.Name}.log", false); + + fkv = new FasterKV(SimpleIndexUtils.KeySpace, new LogSettings { LogDevice = log }, + new CheckpointSettings { CheckpointDir = testPath, CheckPointType = CheckpointType.Snapshot } + ); + } + + [TearDown] + public void TearDown() + { + fkv.Dispose(); + fkv = null; + log.Dispose(); + TestUtils.DeleteDirectory(testPath); + } + + private ISecondaryIndex CreateIndex(bool isMutable, bool isAsync, Func indexKeyFunc) + => isMutable + ? (ISecondaryIndex)new SimpleMutableKeyIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc) + : new SimpleImmutableKeyIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc); + + [Test] + [Category("FasterKV")] + public void MutableInsertTest([Values] bool useRMW, [Values] bool useAdvancedFunctions, [Values] bool isAsync) + { + var secondaryIndex = CreateIndex(isMutable: true, isAsync, rawKey => rawKey / keyDivisor); + fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); + if (useAdvancedFunctions) + SimpleIndexUtils.PopulateIntsWithAdvancedFunctions(fkv, useRMW, isAsync); + else + SimpleIndexUtils.PopulateInts(fkv, useRMW, isAsync); + + var indexBase = secondaryIndex as SimpleIndexBase; + Assert.IsNotNull(indexBase); + var records = indexBase.Query(42); + Assert.AreEqual(SimpleIndexUtils.NumKeys / keyDivisor, indexBase.DistinctKeyCount); + Assert.AreEqual(keyDivisor, records.Length); + } + } +} diff --git a/cs/test/SimpleValueIndexTests.cs b/cs/test/SimpleValueIndexTests.cs new file mode 100644 index 000000000..f0b4793ee --- /dev/null +++ b/cs/test/SimpleValueIndexTests.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.IO; +using FASTER.core; +using NUnit.Framework; + +namespace FASTER.test.SubsetIndex.SimpleValueIndexTests +{ + class SimpleValueIndexTests + { + class SimpleMutableValueIndex : SimpleIndexBase, ISecondaryValueIndex + { + internal SimpleMutableValueIndex(string name, Func indexKeyFunc) : base(name, isKeyIndex: false, indexKeyFunc, isMutableIndex: true) { } + + public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) + => BaseDelete(recordId, indexSessionBroker); + + public void Insert(ref TValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + => BaseInsert(ref value, recordId, indexSessionBroker); + + public void Upsert(ref TValue value, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) + => BaseUpsert(ref value, recordId, isMutableRecord, indexSessionBroker); + } + + class SimpleImmutableValueIndex : SimpleIndexBase, ISecondaryValueIndex + { + internal SimpleImmutableValueIndex(string name, Func indexKeyFunc) : base(name, isKeyIndex: false, indexKeyFunc, isMutableIndex: true) { } + + public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) + => BaseDelete(recordId, indexSessionBroker); + + public void Insert(ref TValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + => BaseInsert(ref value, recordId, indexSessionBroker); + + public void Upsert(ref TValue value, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) + => BaseUpsert(ref value, recordId, isMutableRecord, indexSessionBroker); + } + + const int valueDivisor = 50; + private string testPath; + private FasterKV fkv; + private IDevice log; + + [SetUp] + public void Setup() + { + if (testPath == null) + { + testPath = TestContext.CurrentContext.TestDirectory + "/" + Path.GetRandomFileName(); + if (!Directory.Exists(testPath)) + Directory.CreateDirectory(testPath); + } + + log = Devices.CreateLogDevice(testPath + $"/{TestContext.CurrentContext.Test.Name}.log", false); + + fkv = new FasterKV(SimpleIndexUtils.KeySpace, new LogSettings { LogDevice = log }, + new CheckpointSettings { CheckpointDir = testPath, CheckPointType = CheckpointType.Snapshot } + ); + } + + [TearDown] + public void TearDown() + { + fkv.Dispose(); + fkv = null; + log.Dispose(); + TestUtils.DeleteDirectory(testPath); + } + + private ISecondaryIndex CreateIndex(bool isMutable, bool isAsync, Func indexKeyFunc) + => isMutable + ? (ISecondaryIndex)new SimpleMutableValueIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc) + : new SimpleImmutableValueIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc); + + [Test] + [Category("FasterKV")] + public void MutableInsertTest([Values] bool useRMW, [Values] bool useAdvancedFunctions, [Values] bool isAsync) + { + var secondaryIndex = CreateIndex(isMutable: true, isAsync, rawValue => (rawValue - SimpleIndexUtils.ValueStart) / valueDivisor); + fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); + if (useAdvancedFunctions) + SimpleIndexUtils.PopulateIntsWithAdvancedFunctions(fkv, useRMW, isAsync); + else + SimpleIndexUtils.PopulateInts(fkv, useRMW, isAsync); + + var indexBase = secondaryIndex as SimpleIndexBase; + Assert.IsNotNull(indexBase); + var records = indexBase.Query(SimpleIndexUtils.ValueStart + 42); + Assert.AreEqual(SimpleIndexUtils.NumKeys / valueDivisor, indexBase.DistinctKeyCount); + Assert.AreEqual(valueDivisor, records.Length); + } + } +} From c179dc824112199e3c0b99f90b355489030fed61 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Mon, 29 Mar 2021 00:59:38 -0700 Subject: [PATCH 23/37] Implement ReadOnlyObserver for SI; add Immutable and Mixed index test scenarios --- .../ClientSession/AdvancedClientSession.cs | 1 + cs/src/core/ClientSession/ClientSession.cs | 1 + cs/src/core/Index/FASTER/FASTER.cs | 4 +- .../core/SecondaryIndex/ReadOnlyObserver.cs | 33 ++++ .../SecondaryIndex/SecondaryIndexBroker.cs | 59 ++++--- .../SecondaryIndexSessionBroker.cs | 23 ++- cs/test/SimpleIndexBase.cs | 147 +++++++++++++++--- cs/test/SimpleKeyIndexTests.cs | 89 ++++------- cs/test/SimpleValueIndexTests.cs | 89 ++++------- 9 files changed, 280 insertions(+), 166 deletions(-) create mode 100644 cs/src/core/SecondaryIndex/ReadOnlyObserver.cs diff --git a/cs/src/core/ClientSession/AdvancedClientSession.cs b/cs/src/core/ClientSession/AdvancedClientSession.cs index 13eb25eb4..75d307f9a 100644 --- a/cs/src/core/ClientSession/AdvancedClientSession.cs +++ b/cs/src/core/ClientSession/AdvancedClientSession.cs @@ -147,6 +147,7 @@ public void Dispose() { CompletePending(true); fht.DisposeClientSession(ID); + SecondaryIndexSessionBroker.Dispose(); // Session runs on a single thread if (!SupportAsync) diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index b99cf6487..59ce6b4be 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -147,6 +147,7 @@ public void Dispose() { CompletePending(true); fht.DisposeClientSession(ID); + SecondaryIndexSessionBroker.Dispose(); // Session runs on a single thread if (!SupportAsync) diff --git a/cs/src/core/Index/FASTER/FASTER.cs b/cs/src/core/Index/FASTER/FASTER.cs index 7241be611..77ed8fd4a 100644 --- a/cs/src/core/Index/FASTER/FASTER.cs +++ b/cs/src/core/Index/FASTER/FASTER.cs @@ -83,7 +83,7 @@ public partial class FasterKV : FasterBase, /// /// Manages secondary indexes for this FASTER instance. /// - public SecondaryIndexBroker SecondaryIndexBroker { get; } = new SecondaryIndexBroker(); + public SecondaryIndexBroker SecondaryIndexBroker { get; } /// /// Create FASTER instance @@ -222,6 +222,8 @@ public FasterKV(long size, LogSettings logSettings, hlog.Initialize(); + this.SecondaryIndexBroker = new SecondaryIndexBroker(this); + sectorSize = (int)logSettings.LogDevice.SectorSize; Initialize(size, sectorSize); diff --git a/cs/src/core/SecondaryIndex/ReadOnlyObserver.cs b/cs/src/core/SecondaryIndex/ReadOnlyObserver.cs new file mode 100644 index 000000000..dc16ab676 --- /dev/null +++ b/cs/src/core/SecondaryIndex/ReadOnlyObserver.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace FASTER.core +{ + class ReadOnlyObserver : IObserver> + { + readonly SecondaryIndexBroker secondaryIndexBroker; + readonly SecondaryIndexSessionBroker indexSessionBroker = new SecondaryIndexSessionBroker(); + + internal ReadOnlyObserver(SecondaryIndexBroker sib) => this.secondaryIndexBroker = sib; + + public void OnCompleted() + { + // Called when AllocatorBase is Disposed; nothing to do here. + } + + public void OnError(Exception error) + { + // Apparently not called by FASTER + } + + public void OnNext(IFasterScanIterator iter) + { + while (iter.GetNext(out _, out TKVKey key, out TKVValue value)) + { + secondaryIndexBroker.UpsertReadOnly(ref key, ref value, iter.CurrentAddress, indexSessionBroker); + } + } + } +} diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs index ef0cbbc60..6610301c7 100644 --- a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs +++ b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs @@ -14,7 +14,8 @@ namespace FASTER.core /// public class SecondaryIndexBroker { - private readonly Dictionary indexes = new Dictionary(); + private ISecondaryKeyIndex[] allKeyIndexes; + private ISecondaryValueIndex[] allValueIndexes; // Use arrays for faster traversal. private ISecondaryKeyIndex[] mutableKeyIndexes = Array.Empty>(); @@ -24,6 +25,11 @@ public class SecondaryIndexBroker internal int MutableValueIndexCount => mutableValueIndexes.Length; readonly object membershipLock = new object(); + + readonly FasterKV primaryFkv; + IDisposable logSubscribeDisposable; // Used if we implement index removal, if we go to zero indexes. + + internal SecondaryIndexBroker(FasterKV pFkv) => this.primaryFkv = pFkv; /// /// Adds a secondary index to the list. @@ -33,53 +39,52 @@ public void AddIndex(ISecondaryIndex index) { bool isMutable = false; - bool addSpecific(TIndex idx, ref TIndex[] vec) + void AppendToArray(ref TIndex[] vec, TIndex idx) + { + var resizedVec = new TIndex[vec is null ? 1 : vec.Length + 1]; + if (vec is { }) + Array.Copy(vec, resizedVec, vec.Length); + resizedVec[resizedVec.Length - 1] = idx; + vec = resizedVec; + } + + bool addSpecific(ref TIndex[] allVec, ref TIndex[] mutableVec, TIndex idx) where TIndex : ISecondaryIndex { if (idx is null) return false; if (idx.IsMutable) { - Array.Resize(ref vec, vec.Length + 1); - vec[vec.Length - 1] = idx; + AppendToArray(ref mutableVec, idx); isMutable = true; } + AppendToArray(ref allVec, idx); return true; } lock (membershipLock) { - if (!addSpecific(index as ISecondaryKeyIndex, ref mutableKeyIndexes) - && !addSpecific(index as ISecondaryValueIndex, ref mutableValueIndexes)) + if (!addSpecific(ref allKeyIndexes, ref mutableKeyIndexes, index as ISecondaryKeyIndex) + && !addSpecific(ref allValueIndexes, ref mutableValueIndexes, index as ISecondaryValueIndex)) throw new SecondaryIndexException("Object is not a KeyIndex or ValueIndex"); - this.HasMutableIndexes |= isMutable; - indexes[index.Name] = index; + this.HasMutableIndexes |= isMutable; // Note: removing indexes will have to recalculate this index.SetSessionSlot(SecondaryIndexSessionBroker.NextSessionSlot++); + + if (logSubscribeDisposable is null) + logSubscribeDisposable = primaryFkv.Log.Subscribe(new ReadOnlyObserver(primaryFkv.SecondaryIndexBroker)); } } /// /// The number of indexes registered. /// - public int Count => indexes.Count; + public int Count => allKeyIndexes.Length + allValueIndexes.Length; /// /// The number of indexes registered. /// public bool HasMutableIndexes { get; private set; } - /// - /// Enumerates the list of indexes. - /// - /// - public IEnumerable GetIndexes() => indexes.Values; - - /// - /// Returns the index with the specified name. - /// - /// - public ISecondaryIndex GetIndex(string name) => indexes[name]; - // On failure of an operation, a SecondaryIndexException is thrown by the Index #region Mutable KeyIndexes @@ -157,12 +162,16 @@ public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker /// public void UpsertReadOnly(ref TKVKey key, ref TKVValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { - var idxs = this.indexes; - foreach (var index in idxs) + var ki = this.allKeyIndexes; + var vi = this.allValueIndexes; + if (ki is { }) { - if (index is ISecondaryKeyIndex keyIndex) + foreach (var keyIndex in ki) keyIndex.Upsert(ref key, recordId, isMutableRecord: false, indexSessionBroker); - else if (index is ISecondaryValueIndex valueIndex) + } + if (vi is { }) + { + foreach (var valueIndex in vi) valueIndex.Upsert(ref value, recordId, isMutableRecord: false, indexSessionBroker); } } diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs index 8857f2fdb..c006f9703 100644 --- a/cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs +++ b/cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs @@ -2,13 +2,14 @@ // Licensed under the MIT license. using System; +using System.Threading; namespace FASTER.core { /// /// Allows an index to register its own concept of sessions to be attached to a primary FasterKV session. /// - public class SecondaryIndexSessionBroker + public class SecondaryIndexSessionBroker : IDisposable { readonly object sessionLock = new object(); @@ -50,5 +51,25 @@ public object SetSessionObject(long slot, object sessionObject) this.indexSessions[slot] = sessionObject; return sessionObject; } + + /// + public void Dispose() + { + var sessions = this.indexSessions; + if (sessions == null) + return; + + sessions = Interlocked.CompareExchange(ref this.indexSessions, null, sessions); + if (sessions == null) + return; + + foreach (var session in sessions) + { + if (session is IDisposable idisp) + idisp.Dispose(); + } + + GC.SuppressFinalize(this); + } } } diff --git a/cs/test/SimpleIndexBase.cs b/cs/test/SimpleIndexBase.cs index cf6fcee7b..734ae90fc 100644 --- a/cs/test/SimpleIndexBase.cs +++ b/cs/test/SimpleIndexBase.cs @@ -5,18 +5,19 @@ using NUnit.Framework; using System; using System.Collections.Generic; +using System.IO; using System.Linq; namespace FASTER.test { class SimpleIndexBase { - protected internal Dictionary> mutableRecords = new Dictionary>(); - protected internal Dictionary> immutableRecords = new Dictionary>(); - protected internal Dictionary reverseLookup = new Dictionary(); + protected readonly internal Dictionary> MutableRecords = new Dictionary>(); + protected readonly internal Dictionary> ImmutableRecords = new Dictionary>(); + private readonly Dictionary reverseLookup = new Dictionary(); protected internal long sessionSlot = 0; - private bool isKeyIndex; + private readonly bool isKeyIndex; private readonly string indexType; private Guid sessionId = Guid.Empty; readonly Func indexKeyFunc; @@ -54,9 +55,9 @@ private void DoDelete(ref TKey rawKey, long recordId, SecondaryIndexSessionBroke var key = this.indexKeyFunc(rawKey); VerifyNotImmutable(ref key, recordId); VerifySession(indexSessionBroker); - if (!mutableRecords.ContainsKey(key)) + if (!MutableRecords.ContainsKey(key)) Assert.Fail($"{indexType} '{key}' not found as index key"); - mutableRecords.Remove(key); + MutableRecords.Remove(key); Assert.IsTrue(reverseLookup.ContainsKey(recordId)); reverseLookup.Remove(recordId); @@ -67,10 +68,10 @@ internal void BaseInsert(ref TKey rawKey, long recordId, SecondaryIndexSessionBr var key = this.indexKeyFunc(rawKey); VerifyNotImmutable(ref key, recordId); VerifySession(indexSessionBroker); - if (!mutableRecords.TryGetValue(key, out var recordIds)) + if (!MutableRecords.TryGetValue(key, out var recordIds)) { recordIds = new List(); - mutableRecords[key] = recordIds; + MutableRecords[key] = recordIds; } else if (recordIds.Contains(recordId)) { @@ -92,55 +93,63 @@ internal void BaseUpsert(ref TKey rawKey, long recordId, bool isMutableRecord, S // Move from mutable to immutable var key = this.indexKeyFunc(rawKey); VerifyNotImmutable(ref key, recordId); - VerifySession(indexSessionBroker); - if (mutableRecords.TryGetValue(key, out var recordIds) && recordIds.Contains(recordId)) + VerifySession(indexSessionBroker, isMutableRecord); + if (MutableRecords.TryGetValue(key, out var recordIds) && recordIds.Contains(recordId)) + { recordIds.Remove(recordId); + if (recordIds.Count == 0) + MutableRecords.Remove(key); + } - if (!immutableRecords.TryGetValue(key, out recordIds)) + if (!ImmutableRecords.TryGetValue(key, out recordIds)) { recordIds = new List(); - mutableRecords[key] = recordIds; + ImmutableRecords[key] = recordIds; } recordIds.Add(recordId); AddToReverseLookup(ref key, recordId); } - private void VerifySession(SecondaryIndexSessionBroker indexSessionBroker) + private void VerifySession(SecondaryIndexSessionBroker indexSessionBroker, bool isMutableRecord = true) { Assert.IsNotNull(indexSessionBroker); var sessionObject = indexSessionBroker.GetSessionObject(this.sessionSlot); - Assert.AreEqual(sessionObject is null, this.sessionId == Guid.Empty); + + // For these tests, we will always do all mutable inserts before checkpointing and getting readonly inserts. + // The readonly inserts come from a different SecondaryIndexSessionBroker (owned by the SecondaryIndexBroker + // for the ReadOnlyObserver), so we expect not to find a session there on the first call. + if (isMutableRecord) + Assert.AreEqual(sessionObject is null, this.sessionId == Guid.Empty); if (!(sessionObject is SimpleIndexSession session)) { if (sessionObject is { }) Assert.Fail($"Unexpected session object type {sessionObject.GetType().Name} for {indexType}"); - this.sessionId = Guid.NewGuid(); + if (this.sessionId == Guid.Empty) + this.sessionId = Guid.NewGuid(); session = new SimpleIndexSession() { Id = this.sessionId }; indexSessionBroker.SetSessionObject(this.sessionSlot, session); } Assert.AreEqual(this.sessionId, session.Id); } - private List emptyRecordList = new List(); + private readonly List emptyRecordList = new List(); internal long[] Query(TKey rawKey) { var key = this.indexKeyFunc(rawKey); - if (!mutableRecords.TryGetValue(key, out var mutableRecordList)) + if (!MutableRecords.TryGetValue(key, out var mutableRecordList)) mutableRecordList = emptyRecordList; - if (!immutableRecords.TryGetValue(key, out var immutableRecordList)) + if (!ImmutableRecords.TryGetValue(key, out var immutableRecordList)) immutableRecordList = emptyRecordList; return mutableRecordList.Concat(immutableRecordList).ToArray(); } - internal int DistinctKeyCount => this.mutableRecords.Count + this.immutableRecords.Count; - private void VerifyNotImmutable(ref TKey key, long recordId) { - if (immutableRecords.TryGetValue(key, out var recordIds) && recordIds.Contains(recordId)) + if (ImmutableRecords.TryGetValue(key, out var recordIds) && recordIds.Contains(recordId)) Assert.Fail($"Unexpected update of recordId {recordId} for {indexType} '{key}'"); } @@ -156,19 +165,61 @@ private void AddToReverseLookup(ref TKey key, long recordId) } } - class SimpleIndexSession + internal class SimpleIndexSession { public Guid Id { get; set; } } + internal class PrimaryFasterKV + { + private string testPath; + internal FasterKV fkv; + private IDevice log; + + internal void Setup() + { + testPath = TestContext.CurrentContext.TestDirectory + "/" + Path.GetRandomFileName(); + if (!Directory.Exists(testPath)) + Directory.CreateDirectory(testPath); + + log = Devices.CreateLogDevice(testPath + $"/{TestContext.CurrentContext.Test.Name}.log", false); + + fkv = new FasterKV(SimpleIndexUtils.KeySpace, new LogSettings { LogDevice = log }, + new CheckpointSettings { CheckpointDir = testPath, CheckPointType = CheckpointType.FoldOver } + ); + } + + internal void Populate(bool useAdvancedFunctions, bool useRMW, bool isAsync) + { + if (useAdvancedFunctions) + SimpleIndexUtils.PopulateIntsWithAdvancedFunctions(this.fkv, useRMW, isAsync); + else + SimpleIndexUtils.PopulateInts(this.fkv, useRMW, isAsync); + } + + internal void Checkpoint() + { + this.fkv.TakeFullCheckpoint(out _); + this.fkv.CompleteCheckpointAsync().GetAwaiter().GetResult(); + } + + internal void TearDown() + { + fkv.Dispose(); + fkv = null; + log.Dispose(); + TestUtils.DeleteDirectory(testPath); + } + } + static class SimpleIndexUtils { - internal const long NumKeys = 2_000L; + internal const int NumKeys = 2_000; internal const long KeySpace = 1L << 14; internal const int ValueStart = 10_000; - public static void PopulateInts(FasterKV fkv, bool useRMW, bool isAsync) + internal static void PopulateInts(FasterKV fkv, bool useRMW, bool isAsync) { using var session = fkv.NewSession(new SimpleFunctions()); @@ -197,7 +248,7 @@ public static void PopulateInts(FasterKV fkv, bool useRMW, bool isAsyn session.CompletePending(true); } - public static void PopulateIntsWithAdvancedFunctions(FasterKV fkv, bool useRMW, bool isAsync) + internal static void PopulateIntsWithAdvancedFunctions(FasterKV fkv, bool useRMW, bool isAsync) { using var session = fkv.NewSession(new AdvancedSimpleFunctions()); @@ -225,5 +276,51 @@ public static void PopulateIntsWithAdvancedFunctions(FasterKV fkv, boo // Make sure operations are completed session.CompletePending(true); } + + internal static void VerifyMutableIndex(ISecondaryIndex secondaryIndex, int indexKeyDivisor, int queryKeyOffset) + { + var indexBase = secondaryIndex as SimpleIndexBase; + Assert.AreEqual(NumKeys / indexKeyDivisor, indexBase.MutableRecords.Count); + Assert.AreEqual(0, indexBase.ImmutableRecords.Count); + + var records = indexBase.Query(42 + queryKeyOffset); + Assert.AreEqual(indexKeyDivisor, records.Length); + } + + internal static void VerifyImmutableIndex(ISecondaryIndex secondaryIndex, int indexKeyDivisor, int queryKeyOffset, PrimaryFasterKV store) + { + var indexBase = secondaryIndex as SimpleIndexBase; + Assert.AreEqual(0, indexBase.MutableRecords.Count); + Assert.AreEqual(0, indexBase.ImmutableRecords.Count); + + store.Checkpoint(); + Assert.AreEqual(0, indexBase.MutableRecords.Count); + Assert.AreEqual(NumKeys / indexKeyDivisor, indexBase.ImmutableRecords.Count); + + var records = indexBase.Query(42 + queryKeyOffset); + Assert.AreEqual(indexKeyDivisor, records.Length); + } + + internal static void VerifyMixedIndexes(ISecondaryIndex mutableIndex, ISecondaryIndex immutableIndex, int indexKeyDivisor, int queryKeyOffset, PrimaryFasterKV store) + { + var mutableIndexBase = mutableIndex as SimpleIndexBase; + var immutableIndexBase = immutableIndex as SimpleIndexBase; + Assert.AreEqual(NumKeys / indexKeyDivisor, mutableIndexBase.MutableRecords.Count); + Assert.AreEqual(0, mutableIndexBase.ImmutableRecords.Count); + Assert.AreEqual(0, immutableIndexBase.MutableRecords.Count); + Assert.AreEqual(0, immutableIndexBase.ImmutableRecords.Count); + + store.Checkpoint(); + Assert.AreEqual(0, mutableIndexBase.MutableRecords.Count); + Assert.AreEqual(NumKeys / indexKeyDivisor, mutableIndexBase.ImmutableRecords.Count); + Assert.AreEqual(0, immutableIndexBase.MutableRecords.Count); + Assert.AreEqual(NumKeys / indexKeyDivisor, immutableIndexBase.ImmutableRecords.Count); + + var records = mutableIndexBase.Query(42 + queryKeyOffset); + Assert.AreEqual(indexKeyDivisor, records.Length); + + records = immutableIndexBase.Query(42 + queryKeyOffset); + Assert.AreEqual(indexKeyDivisor, records.Length); + } } } diff --git a/cs/test/SimpleKeyIndexTests.cs b/cs/test/SimpleKeyIndexTests.cs index 1f0335bbf..4468677b9 100644 --- a/cs/test/SimpleKeyIndexTests.cs +++ b/cs/test/SimpleKeyIndexTests.cs @@ -2,17 +2,16 @@ // Licensed under the MIT license. using System; -using System.IO; using FASTER.core; using NUnit.Framework; -namespace FASTER.test.SubsetIndex.SimpleKeyIndexTests +namespace FASTER.test.SubsetIndex.SimpleIndexTests { class SimpleKeyIndexTests { - class SimpleMutableKeyIndex : SimpleIndexBase, ISecondaryKeyIndex + class SimpleKeyIndex : SimpleIndexBase, ISecondaryKeyIndex { - internal SimpleMutableKeyIndex(string name, Func indexKeyFunc) : base(name, isKeyIndex: true, indexKeyFunc, isMutableIndex: true) { } + internal SimpleKeyIndex(string name, Func indexKeyFunc, bool isMutableIndex) : base(name, isKeyIndex: true, indexKeyFunc, isMutableIndex: isMutableIndex) { } public void Delete(ref TKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) => BaseDelete(ref key, recordId, indexSessionBroker); @@ -24,72 +23,48 @@ public void Upsert(ref TKey key, long recordId, bool isMutableRecord, SecondaryI => BaseUpsert(ref key, recordId, isMutableRecord, indexSessionBroker); } - class SimpleImmutableKeyIndex : SimpleIndexBase, ISecondaryKeyIndex - { - internal SimpleImmutableKeyIndex(string name, Func indexKeyFunc) : base(name, isKeyIndex: true, indexKeyFunc, isMutableIndex: true) { } - - public void Delete(ref TKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) - => BaseDelete(ref key, recordId, indexSessionBroker); + private const int keyDivisor = 20; + readonly PrimaryFasterKV store = new PrimaryFasterKV(); - public void Insert(ref TKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) - => BaseInsert(ref key, recordId, indexSessionBroker); + [SetUp] + public void Setup() => store.Setup(); - public void Upsert(ref TKey key, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) - => BaseUpsert(ref key, recordId, isMutableRecord, indexSessionBroker); - } + [TearDown] + public void TearDown() => store.TearDown(); - const int keyDivisor = 20; - private string testPath; - private FasterKV fkv; - private IDevice log; + private ISecondaryIndex CreateIndex(bool isMutable, bool isAsync, Func indexKeyFunc) + => new SimpleKeyIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc, isMutable); - [SetUp] - public void Setup() + [Test] + [Category("FasterKV")] + public void MutableInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { - if (testPath == null) - { - testPath = TestContext.CurrentContext.TestDirectory + "/" + Path.GetRandomFileName(); - if (!Directory.Exists(testPath)) - Directory.CreateDirectory(testPath); - } - - log = Devices.CreateLogDevice(testPath + $"/{TestContext.CurrentContext.Test.Name}.log", false); - - fkv = new FasterKV(SimpleIndexUtils.KeySpace, new LogSettings { LogDevice = log }, - new CheckpointSettings { CheckpointDir = testPath, CheckPointType = CheckpointType.Snapshot } - ); + var secondaryIndex = CreateIndex(isMutable: true, isAsync, rawKey => rawKey / keyDivisor); + store.fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); + store.Populate(useAdvancedFunctions, useRMW, isAsync); + SimpleIndexUtils.VerifyMutableIndex(secondaryIndex, keyDivisor, 0); } - [TearDown] - public void TearDown() + [Test] + [Category("FasterKV")] + public void ImmutableInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { - fkv.Dispose(); - fkv = null; - log.Dispose(); - TestUtils.DeleteDirectory(testPath); + var secondaryIndex = CreateIndex(isMutable: false, isAsync, rawKey => rawKey / keyDivisor); + store.fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); + store.Populate(useAdvancedFunctions, useRMW, isAsync); + SimpleIndexUtils.VerifyImmutableIndex(secondaryIndex, keyDivisor, 0, store); } - private ISecondaryIndex CreateIndex(bool isMutable, bool isAsync, Func indexKeyFunc) - => isMutable - ? (ISecondaryIndex)new SimpleMutableKeyIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc) - : new SimpleImmutableKeyIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc); - [Test] [Category("FasterKV")] - public void MutableInsertTest([Values] bool useRMW, [Values] bool useAdvancedFunctions, [Values] bool isAsync) + public void MixedInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { - var secondaryIndex = CreateIndex(isMutable: true, isAsync, rawKey => rawKey / keyDivisor); - fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); - if (useAdvancedFunctions) - SimpleIndexUtils.PopulateIntsWithAdvancedFunctions(fkv, useRMW, isAsync); - else - SimpleIndexUtils.PopulateInts(fkv, useRMW, isAsync); - - var indexBase = secondaryIndex as SimpleIndexBase; - Assert.IsNotNull(indexBase); - var records = indexBase.Query(42); - Assert.AreEqual(SimpleIndexUtils.NumKeys / keyDivisor, indexBase.DistinctKeyCount); - Assert.AreEqual(keyDivisor, records.Length); + var mutableIndex = CreateIndex(isMutable: true, isAsync, rawKey => rawKey / keyDivisor); + var immutableIndex = CreateIndex(isMutable: false, isAsync, rawKey => rawKey / keyDivisor); + store.fkv.SecondaryIndexBroker.AddIndex(mutableIndex); + store.fkv.SecondaryIndexBroker.AddIndex(immutableIndex); + store.Populate(useAdvancedFunctions, useRMW, isAsync); + SimpleIndexUtils.VerifyMixedIndexes(mutableIndex, immutableIndex, keyDivisor, 0, store); } } } diff --git a/cs/test/SimpleValueIndexTests.cs b/cs/test/SimpleValueIndexTests.cs index f0b4793ee..234bee518 100644 --- a/cs/test/SimpleValueIndexTests.cs +++ b/cs/test/SimpleValueIndexTests.cs @@ -2,17 +2,16 @@ // Licensed under the MIT license. using System; -using System.IO; using FASTER.core; using NUnit.Framework; -namespace FASTER.test.SubsetIndex.SimpleValueIndexTests +namespace FASTER.test.SubsetIndex.SimpleIndexTests { class SimpleValueIndexTests { - class SimpleMutableValueIndex : SimpleIndexBase, ISecondaryValueIndex + class SimpleValueIndex : SimpleIndexBase, ISecondaryValueIndex { - internal SimpleMutableValueIndex(string name, Func indexKeyFunc) : base(name, isKeyIndex: false, indexKeyFunc, isMutableIndex: true) { } + internal SimpleValueIndex(string name, Func indexKeyFunc, bool isMutableIndex) : base(name, isKeyIndex: false, indexKeyFunc, isMutableIndex: isMutableIndex) { } public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) => BaseDelete(recordId, indexSessionBroker); @@ -24,72 +23,48 @@ public void Upsert(ref TValue value, long recordId, bool isMutableRecord, Second => BaseUpsert(ref value, recordId, isMutableRecord, indexSessionBroker); } - class SimpleImmutableValueIndex : SimpleIndexBase, ISecondaryValueIndex - { - internal SimpleImmutableValueIndex(string name, Func indexKeyFunc) : base(name, isKeyIndex: false, indexKeyFunc, isMutableIndex: true) { } - - public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) - => BaseDelete(recordId, indexSessionBroker); + private const int valueDivisor = 50; + readonly PrimaryFasterKV store = new PrimaryFasterKV(); - public void Insert(ref TValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) - => BaseInsert(ref value, recordId, indexSessionBroker); + [SetUp] + public void Setup() => store.Setup(); - public void Upsert(ref TValue value, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) - => BaseUpsert(ref value, recordId, isMutableRecord, indexSessionBroker); - } + [TearDown] + public void TearDown() => store.TearDown(); - const int valueDivisor = 50; - private string testPath; - private FasterKV fkv; - private IDevice log; + private ISecondaryIndex CreateIndex(bool isMutable, bool isAsync, Func indexKeyFunc) + => new SimpleValueIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc, isMutable); - [SetUp] - public void Setup() + [Test] + [Category("FasterKV")] + public void MutableInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { - if (testPath == null) - { - testPath = TestContext.CurrentContext.TestDirectory + "/" + Path.GetRandomFileName(); - if (!Directory.Exists(testPath)) - Directory.CreateDirectory(testPath); - } - - log = Devices.CreateLogDevice(testPath + $"/{TestContext.CurrentContext.Test.Name}.log", false); - - fkv = new FasterKV(SimpleIndexUtils.KeySpace, new LogSettings { LogDevice = log }, - new CheckpointSettings { CheckpointDir = testPath, CheckPointType = CheckpointType.Snapshot } - ); + var secondaryIndex = CreateIndex(isMutable: true, isAsync, rawValue => (rawValue - SimpleIndexUtils.ValueStart) / valueDivisor); + store.fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); + store.Populate(useAdvancedFunctions, useRMW, isAsync); + SimpleIndexUtils.VerifyMutableIndex(secondaryIndex, valueDivisor, SimpleIndexUtils.ValueStart); } - [TearDown] - public void TearDown() + [Test] + [Category("FasterKV")] + public void ImmutableInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { - fkv.Dispose(); - fkv = null; - log.Dispose(); - TestUtils.DeleteDirectory(testPath); + var secondaryIndex = CreateIndex(isMutable: false, isAsync, rawValue => (rawValue - SimpleIndexUtils.ValueStart) / valueDivisor); + store.fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); + store.Populate(useAdvancedFunctions, useRMW, isAsync); + SimpleIndexUtils.VerifyImmutableIndex(secondaryIndex, valueDivisor, SimpleIndexUtils.ValueStart, store); } - private ISecondaryIndex CreateIndex(bool isMutable, bool isAsync, Func indexKeyFunc) - => isMutable - ? (ISecondaryIndex)new SimpleMutableValueIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc) - : new SimpleImmutableValueIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc); - [Test] [Category("FasterKV")] - public void MutableInsertTest([Values] bool useRMW, [Values] bool useAdvancedFunctions, [Values] bool isAsync) + public void MixedInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { - var secondaryIndex = CreateIndex(isMutable: true, isAsync, rawValue => (rawValue - SimpleIndexUtils.ValueStart) / valueDivisor); - fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); - if (useAdvancedFunctions) - SimpleIndexUtils.PopulateIntsWithAdvancedFunctions(fkv, useRMW, isAsync); - else - SimpleIndexUtils.PopulateInts(fkv, useRMW, isAsync); - - var indexBase = secondaryIndex as SimpleIndexBase; - Assert.IsNotNull(indexBase); - var records = indexBase.Query(SimpleIndexUtils.ValueStart + 42); - Assert.AreEqual(SimpleIndexUtils.NumKeys / valueDivisor, indexBase.DistinctKeyCount); - Assert.AreEqual(valueDivisor, records.Length); + var mutableIndex = CreateIndex(isMutable: true, isAsync, rawValue => (rawValue - SimpleIndexUtils.ValueStart) / valueDivisor); + var immutableIndex = CreateIndex(isMutable: false, isAsync, rawValue => (rawValue - SimpleIndexUtils.ValueStart) / valueDivisor); + store.fkv.SecondaryIndexBroker.AddIndex(mutableIndex); + store.fkv.SecondaryIndexBroker.AddIndex(immutableIndex); + store.Populate(useAdvancedFunctions, useRMW, isAsync); + SimpleIndexUtils.VerifyMixedIndexes(mutableIndex, immutableIndex, valueDivisor, SimpleIndexUtils.ValueStart, store); } } } From 42619d7150d64019e01f9d50460ffe557befc988 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Tue, 30 Mar 2021 23:29:27 -0700 Subject: [PATCH 24/37] Support multiple ReadOnly and Eviction Observers --- cs/src/core/Allocator/AllocatorBase.cs | 97 +++++++++++++++++-- cs/src/core/Allocator/BlittableAllocator.cs | 8 +- cs/src/core/Allocator/GenericAllocator.cs | 8 +- .../Allocator/VarLenBlittableAllocator.cs | 8 +- cs/src/core/Index/FASTER/LogAccessor.cs | 16 +-- .../SecondaryIndex/SecondaryIndexBroker.cs | 2 +- cs/test/ObserverTests.cs | 89 +++++++++++++++++ 7 files changed, 208 insertions(+), 20 deletions(-) create mode 100644 cs/test/ObserverTests.cs diff --git a/cs/src/core/Allocator/AllocatorBase.cs b/cs/src/core/Allocator/AllocatorBase.cs index 59dc3c797..d5e672732 100644 --- a/cs/src/core/Allocator/AllocatorBase.cs +++ b/cs/src/core/Allocator/AllocatorBase.cs @@ -226,12 +226,16 @@ public unsafe abstract partial class AllocatorBase : IDisposable /// /// Observer for records entering read-only region /// - internal IObserver> OnReadOnlyObserver; + internal IObserver>[] OnReadOnlyObservers = Array.Empty>>(); /// /// Observer for records getting evicted from memory (page closed) /// - internal IObserver> OnEvictionObserver; + internal IObserver>[] OnEvictionObservers = Array.Empty>>(); + + // Observer membership locks + private readonly object readOnlyObserverLock = new object(); + private readonly object evictionObserverLock = new object(); #region Abstract methods /// @@ -645,8 +649,7 @@ public virtual void Dispose() epoch.Dispose(); bufferPool.Free(); - OnReadOnlyObserver?.OnCompleted(); - OnEvictionObserver?.OnCompleted(); + this.OnCompleted(); } /// @@ -654,6 +657,86 @@ public virtual void Dispose() /// internal abstract void DeleteFromMemory(); + internal void AddReadOnlyObserver(IObserver> observer) + => AddObserver(ref OnReadOnlyObservers, observer, readOnlyObserverLock); + internal void RemoveReadOnlyObserver(IObserver> observer) + => RemoveObserver(ref OnReadOnlyObservers, observer, readOnlyObserverLock); + + internal void AddEvictionObserver(IObserver> observer) + => AddObserver(ref OnEvictionObservers, observer, evictionObserverLock); + internal void RemoveEvictionObserver(IObserver> observer) + => RemoveObserver(ref OnEvictionObservers, observer, evictionObserverLock); + + internal static void AddObserver(ref IObserver>[] observers, IObserver> observer, object lockObject) + { + lock (lockObject) + { + if (observers.Length == 0) + { + observers = new[] { observer }; + return; + } + var equatable = observer as IEquatable>>; + foreach (var existing in observers) + { + bool found = equatable is { } && existing is IEquatable>> existingEquatable + ? equatable.Equals(existingEquatable) + : observer.Equals(existing); + if (found) + return; + } + var vec = new IObserver>[observers.Length + 1]; + Array.Copy(observers, vec, observers.Length); + vec[observers.Length] = observer; + observers = vec; + } + } + + internal static void RemoveObserver(ref IObserver>[] observers, IObserver> observer, object lockObject) + { + lock (lockObject) + { + if (observers.Length == 0) + return; + var equatable = observer as IEquatable>>; + for (var ii = 0; ii < observers.Length; ++ii) + { + var existing = observers[ii]; + bool found = equatable is { } && existing is IEquatable>> existingEquatable + ? equatable.Equals(existingEquatable) + : observer.Equals(existing); + if (found) + { + if (observers.Length == 1) + { + observers = Array.Empty>>(); + return; + } + var vec = new IObserver>[observers.Length - 1]; + if (ii > 0) + Array.Copy(observers, vec, ii); + if (ii < observers.Length - 1) + Array.Copy(observers, ii + 1, vec, ii, observers.Length - ii - 1); + observers = vec; + } + } + } + } + + void OnCompleted() + { + OnCompleted(this.OnReadOnlyObservers); + OnCompleted(this.OnEvictionObservers); + } + + internal static void OnCompleted(IObserver>[] observers) + { + if (observers is null) + return; + var localObservers = observers; + foreach (var observer in localObservers) + observer.OnCompleted(); + } /// /// Segment size @@ -930,10 +1013,12 @@ public void OnPagesMarkedReadOnly(long newSafeReadOnlyAddress) if (Utility.MonotonicUpdate(ref SafeReadOnlyAddress, newSafeReadOnlyAddress, out long oldSafeReadOnlyAddress)) { Debug.WriteLine("SafeReadOnly shifted from {0:X} to {1:X}", oldSafeReadOnlyAddress, newSafeReadOnlyAddress); - if (OnReadOnlyObserver != null) + + var localObservers = this.OnReadOnlyObservers; + foreach (var observer in localObservers) { using var iter = Scan(oldSafeReadOnlyAddress, newSafeReadOnlyAddress, ScanBufferingMode.NoBuffering); - OnReadOnlyObserver?.OnNext(iter); + observer.OnNext(iter); } AsyncFlushPages(oldSafeReadOnlyAddress, newSafeReadOnlyAddress); } diff --git a/cs/src/core/Allocator/BlittableAllocator.cs b/cs/src/core/Allocator/BlittableAllocator.cs index a6f288473..98cdbc324 100644 --- a/cs/src/core/Allocator/BlittableAllocator.cs +++ b/cs/src/core/Allocator/BlittableAllocator.cs @@ -328,8 +328,12 @@ public override IFasterScanIterator Scan(long beginAddress, long end /// internal override void MemoryPageScan(long beginAddress, long endAddress) { - using var iter = new BlittableScanIterator(this, beginAddress, endAddress, ScanBufferingMode.NoBuffering, epoch, true); - OnEvictionObserver?.OnNext(iter); + var localObservers = OnEvictionObservers; + foreach (var observer in localObservers) + { + using var iter = new BlittableScanIterator(this, beginAddress, endAddress, ScanBufferingMode.NoBuffering, epoch, true); + observer.OnNext(iter); + } } /// diff --git a/cs/src/core/Allocator/GenericAllocator.cs b/cs/src/core/Allocator/GenericAllocator.cs index 10f2f6675..acbdc43bb 100644 --- a/cs/src/core/Allocator/GenericAllocator.cs +++ b/cs/src/core/Allocator/GenericAllocator.cs @@ -985,12 +985,16 @@ internal override void MemoryPageScan(long beginAddress, long endAddress) int start = (int)(beginAddress & PageSizeMask) / recordSize; int count = (int)(endAddress - beginAddress) / recordSize; int end = start + count; - using var iter = new MemoryPageScanIterator(values[page], start, end); Debug.Assert(epoch.ThisInstanceProtected()); try { epoch.Suspend(); - OnEvictionObserver?.OnNext(iter); + var localObservers = OnEvictionObservers; + foreach (var observer in localObservers) + { + using var iter = new MemoryPageScanIterator(values[page], start, end); + observer.OnNext(iter); + } } finally { diff --git a/cs/src/core/Allocator/VarLenBlittableAllocator.cs b/cs/src/core/Allocator/VarLenBlittableAllocator.cs index 81df3806b..5a1b0819d 100644 --- a/cs/src/core/Allocator/VarLenBlittableAllocator.cs +++ b/cs/src/core/Allocator/VarLenBlittableAllocator.cs @@ -450,8 +450,12 @@ public override IFasterScanIterator Scan(long beginAddress, long end /// internal override void MemoryPageScan(long beginAddress, long endAddress) { - using var iter = new VariableLengthBlittableScanIterator(this, beginAddress, endAddress, ScanBufferingMode.NoBuffering, epoch, true); - OnEvictionObserver?.OnNext(iter); + var localObservers = OnEvictionObservers; + foreach (var observer in localObservers) + { + using var iter = new VariableLengthBlittableScanIterator(this, beginAddress, endAddress, ScanBufferingMode.NoBuffering, epoch, true); + observer.OnNext(iter); + } } diff --git a/cs/src/core/Index/FASTER/LogAccessor.cs b/cs/src/core/Index/FASTER/LogAccessor.cs index 03f7657e5..8906c9fa8 100644 --- a/cs/src/core/Index/FASTER/LogAccessor.cs +++ b/cs/src/core/Index/FASTER/LogAccessor.cs @@ -118,8 +118,8 @@ public void ShiftHeadAddress(long newHeadAddress, bool wait) /// Observer to which scan iterator is pushed public IDisposable Subscribe(IObserver> readOnlyObserver) { - allocator.OnReadOnlyObserver = readOnlyObserver; - return new LogSubscribeDisposable(allocator, true); + allocator.AddReadOnlyObserver(readOnlyObserver); + return new LogSubscribeDisposable(allocator, readOnlyObserver, true); } /// @@ -131,8 +131,8 @@ public IDisposable Subscribe(IObserver> readOnly /// Observer to which scan iterator is pushed public IDisposable SubscribeEvictions(IObserver> evictionObserver) { - allocator.OnEvictionObserver = evictionObserver; - return new LogSubscribeDisposable(allocator, false); + allocator.AddEvictionObserver(evictionObserver); + return new LogSubscribeDisposable(allocator, evictionObserver, false); } /// @@ -141,20 +141,22 @@ public IDisposable SubscribeEvictions(IObserver> class LogSubscribeDisposable : IDisposable { private readonly AllocatorBase allocator; + private readonly IObserver> observer; private readonly bool readOnly; - public LogSubscribeDisposable(AllocatorBase allocator, bool readOnly) + public LogSubscribeDisposable(AllocatorBase allocator, IObserver> observer, bool readOnly) { this.allocator = allocator; + this.observer = observer; this.readOnly = readOnly; } public void Dispose() { if (readOnly) - allocator.OnReadOnlyObserver = null; + allocator.RemoveReadOnlyObserver(observer); else - allocator.OnEvictionObserver = null; + allocator.RemoveEvictionObserver(observer); } } diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs index 6610301c7..d658493c0 100644 --- a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs +++ b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs @@ -27,7 +27,7 @@ public class SecondaryIndexBroker readonly object membershipLock = new object(); readonly FasterKV primaryFkv; - IDisposable logSubscribeDisposable; // Used if we implement index removal, if we go to zero indexes. + IDisposable logSubscribeDisposable; // Used if we implement index removal, if we go to zero indexes; Dispose() and null this then. internal SecondaryIndexBroker(FasterKV pFkv) => this.primaryFkv = pFkv; diff --git a/cs/test/ObserverTests.cs b/cs/test/ObserverTests.cs new file mode 100644 index 000000000..418132e6e --- /dev/null +++ b/cs/test/ObserverTests.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using FASTER.core; +using NUnit.Framework; +using System; + +namespace FASTER.test +{ + [TestFixture] + class BasicObserverTests + { + internal IObserver>[] TestObservers; + readonly object lockObject = new object(); + + class TestObserver : IObserver> + { + internal int Id; + internal bool IsCompleted = false; + internal int OnNextCalls = 0; + + internal TestObserver(int id) => this.Id = id; + + public void OnCompleted() => this.IsCompleted = true; + + public void OnError(Exception error) { /* Apparently not called by FASTER */ } + + public void OnNext(IFasterScanIterator iter) { ++this.OnNextCalls; } + } + + [SetUp] + public void Setup() + { + this.TestObservers = Array.Empty>>(); + } + + [TearDown] + public void TearDown() + { + } + + [Test] + [Category("FasterLog")] + public void ObserverMembershipTests() + { + const int NumObservers = 5; + for (var ii = 0; ii < NumObservers; ++ii) + { + AllocatorBase.AddObserver(ref TestObservers, new TestObserver(ii), lockObject); + } + + Assert.AreEqual(NumObservers, TestObservers.Length); + + // We don't actually use the iterator in this test. + IFasterScanIterator iter = null; + + foreach (var observer in this.TestObservers) + observer.OnNext(iter); + foreach (var observer in this.TestObservers) + Assert.AreEqual(1, (observer as TestObserver).OnNextCalls); + + const int toRemove = 1; + AllocatorBase.RemoveObserver(ref TestObservers, TestObservers[toRemove], lockObject); + Assert.AreEqual(NumObservers - 1, TestObservers.Length); + + for (var ii = 0; ii < NumObservers - 1; ++ii) + { + int comparand = ii < toRemove ? ii : ii + 1; + Assert.AreEqual(comparand, (TestObservers[ii] as TestObserver).Id); + } + + AllocatorBase.RemoveObserver(ref TestObservers, TestObservers[0], lockObject); + Assert.AreEqual(NumObservers - 2, TestObservers.Length); + AllocatorBase.RemoveObserver(ref TestObservers, TestObservers[TestObservers.Length - 1], lockObject); + Assert.AreEqual(NumObservers - 3, TestObservers.Length); + + foreach (var observer in this.TestObservers) + observer.OnNext(iter); + foreach (var observer in this.TestObservers) + Assert.AreEqual(2, (observer as TestObserver).OnNextCalls); + + while (TestObservers.Length > 0) + { + AllocatorBase.RemoveObserver(ref TestObservers, TestObservers[0], lockObject); + } + Assert.AreEqual(0, TestObservers.Length); + } + } +} From 01220cf7bd074fb5cf59e755a9b1953feff88b0a Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Tue, 13 Apr 2021 23:04:29 -0700 Subject: [PATCH 25/37] more files from merge --- cs/benchmark/scripts/compare_runs.ps1 | 2 +- cs/benchmark/scripts/run_benchmark.ps1 | 4 +--- cs/src/core/ClientSession/FASTERAsync.cs | 9 ++++++++- cs/src/core/Index/FASTER/FASTERImpl.cs | 3 +-- cs/test/ReadAddressTests.cs | 3 ++- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/cs/benchmark/scripts/compare_runs.ps1 b/cs/benchmark/scripts/compare_runs.ps1 index f9b98cdb0..15889cc08 100644 --- a/cs/benchmark/scripts/compare_runs.ps1 +++ b/cs/benchmark/scripts/compare_runs.ps1 @@ -33,7 +33,7 @@ param ( ) class Result : System.IComparable, System.IEquatable[Object] { - # To make things work in one class, name the properties "Left", "Right", and "Diff"--they aren't displayed until the Diff is calculated. + # To make things work in one class, name the properties "Baseline", "Current", and "Diff"--they aren't displayed until the Diff is calculated. [double]$BaselineMean [double]$BaselineStdDev [double]$CurrentMean diff --git a/cs/benchmark/scripts/run_benchmark.ps1 b/cs/benchmark/scripts/run_benchmark.ps1 index a3cdef149..7166fff4a 100644 --- a/cs/benchmark/scripts/run_benchmark.ps1 +++ b/cs/benchmark/scripts/run_benchmark.ps1 @@ -160,7 +160,6 @@ foreach ($d in $distributions) { foreach ($sy in $syntheticDatas) { Write-Host Write-Host "Permutation $permutation of $permutations" - ++$permutation # Only certain combinations of Numa/Threads are supported $n = ($t -lt 48) ? 0 : 1; @@ -173,8 +172,7 @@ foreach ($d in $distributions) { Write-Host "Permutation $permutation/$permutations generating results $($ii + 1)/$($exeNames.Count) to $resultDir for: -n $n -d $d -r $r -t $t -z $z -i $iterations --runsec $RunSeconds $k" # RunSec and Recover are for one-off operations and are not recorded in the filenames. - & "$exeName" -b 0 -n $n -d $d -r $r -t $t -x $x -z $z -i $iterations --runsec $RunSeconds $k | Tee-Object "$resultDir/results_n-$($n)_d-$($d)_r-$($r)_t-$($t)_x-$($x)_z-$($z).txt" - } + & "$exeName" -b 0 -n $n -d $d -r $r -t $t -x $x -z $z -i $iterations --runsec $RunSeconds $k | Tee-Object "$resultDir/results_n-$($n)_d-$($d)_r-$($r)_t-$($t)_x-$($x)_z-$($z).txt" } ++$permutation } diff --git a/cs/src/core/ClientSession/FASTERAsync.cs b/cs/src/core/ClientSession/FASTERAsync.cs index a7acfb76f..e46b1459b 100644 --- a/cs/src/core/ClientSession/FASTERAsync.cs +++ b/cs/src/core/ClientSession/FASTERAsync.cs @@ -819,7 +819,14 @@ internal ValueTask> RmwAsync>(ref key, ref value, ref recordInfo, pcontext.logicalAddress, fasterSession); + } + return new ValueTask>(new RmwAsyncResult((Status)internalStatus, default)); } else if (internalStatus != OperationStatus.ALLOCATE_FAILED) { diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index 01c9cf33b..073d28df2 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -1359,8 +1359,7 @@ internal void InternalContinuePendingReadCopyToTail 0); InsertAddresses[ii] = insertValueIndex.lastWriteAddress; //Assert.IsTrue(session.ctx.HasNoPendingRequests); From ab1c815107ab6c55771f395e54751890f22fbb22 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Thu, 15 Apr 2021 23:25:58 -0700 Subject: [PATCH 26/37] Add RecordId struct; move QueryRecord into SI from HVI; remove recordId from Key Index and split out the Key vs. Value index test base classes; make IHeapContainer inherit from IDisposable; add SI calls to UpsertAsync --- cs/benchmark/SecondaryIndexes.cs | 12 +- .../ClientSession/AdvancedClientSession.cs | 4 +- cs/src/core/ClientSession/ClientSession.cs | 16 +- cs/src/core/ClientSession/FASTERAsync.cs | 15 +- cs/src/core/Index/Common/CompletedOutput.cs | 3 +- cs/src/core/Index/Common/HeapContainer.cs | 16 +- cs/src/core/Index/FASTER/FASTER.cs | 10 +- cs/src/core/Index/FASTER/FASTERImpl.cs | 6 +- cs/src/core/SecondaryIndex/ISecondaryIndex.cs | 18 +- cs/src/core/SecondaryIndex/QueryRecord.cs | 47 +++ .../core/SecondaryIndex/ReadOnlyObserver.cs | 9 +- cs/src/core/SecondaryIndex/RecordId.cs | 59 ++++ .../SecondaryIndex/SecondaryIndexBroker.cs | 22 +- cs/test/ReadAddressTests.cs | 6 +- cs/test/SimpleIndexBase.cs | 303 +++++++++++------- cs/test/SimpleKeyIndexTests.cs | 42 +-- cs/test/SimpleValueIndexTests.cs | 22 +- 17 files changed, 392 insertions(+), 218 deletions(-) create mode 100644 cs/src/core/SecondaryIndex/QueryRecord.cs create mode 100644 cs/src/core/SecondaryIndex/RecordId.cs diff --git a/cs/benchmark/SecondaryIndexes.cs b/cs/benchmark/SecondaryIndexes.cs index 3922fc10d..0bb81240b 100644 --- a/cs/benchmark/SecondaryIndexes.cs +++ b/cs/benchmark/SecondaryIndexes.cs @@ -13,11 +13,11 @@ class NullKeyIndex : ISecondaryKeyIndex public void SetSessionSlot(long slot) { } - public void Delete(ref Key key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Delete(ref Key key, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Insert(ref Key key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Insert(ref Key key, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Upsert(ref Key key, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Upsert(ref Key key, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } } class NullValueIndex : ISecondaryValueIndex @@ -28,10 +28,10 @@ class NullValueIndex : ISecondaryValueIndex public void SetSessionSlot(long slot) { } - public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Delete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Insert(ref Value value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Insert(ref Value value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Upsert(ref Value value, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Upsert(ref Value value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } } } diff --git a/cs/src/core/ClientSession/AdvancedClientSession.cs b/cs/src/core/ClientSession/AdvancedClientSession.cs index 08dede9d1..bee96a28a 100644 --- a/cs/src/core/ClientSession/AdvancedClientSession.cs +++ b/cs/src/core/ClientSession/AdvancedClientSession.cs @@ -884,7 +884,7 @@ public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref Reco [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) => _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, ref recordInfo, address) - && _clientSession.fht.UpdateSIForIPU(ref dst, address, this.SecondaryIndexSessionBroker); + && _clientSession.fht.UpdateSIForIPU(ref dst, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) { @@ -968,7 +968,7 @@ public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref Re [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool InPlaceUpdaterNoLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) => _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, ref recordInfo, address) - && _clientSession.fht.UpdateSIForIPU(ref value, address, this.SecondaryIndexSessionBroker); + && _clientSession.fht.UpdateSIForIPU(ref value, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) { diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index e3f53de3e..dd381b28d 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -886,13 +886,13 @@ public void ConcurrentReaderLock(ref Key key, ref Input input, ref Value value, [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ConcurrentWriter(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) => !this.SupportsLocking - ? ConcurrentWriterNoLock(ref key, ref src, ref dst, address) + ? ConcurrentWriterNoLock(ref key, ref src, ref dst, ref recordInfo, address) : ConcurrentWriterLock(ref key, ref src, ref dst, ref recordInfo, address); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, long address) + private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) => _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst) - && _clientSession.fht.UpdateSIForIPU(ref dst, address, this.SecondaryIndexSessionBroker); + && _clientSession.fht.UpdateSIForIPU(ref dst, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) @@ -901,7 +901,7 @@ private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref this.Lock(ref recordInfo, ref key, ref dst, LockType.Exclusive, ref context); try { - return !recordInfo.Tombstone && ConcurrentWriterNoLock(ref key, ref src, ref dst, address); + return !recordInfo.Tombstone && ConcurrentWriterNoLock(ref key, ref src, ref dst, ref recordInfo, address); } finally { @@ -971,13 +971,13 @@ public void InitialUpdater(ref Key key, ref Input input, ref Value value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool InPlaceUpdater(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) => !this.SupportsLocking - ? InPlaceUpdaterNoLock(ref key, ref input, ref value, address) + ? InPlaceUpdaterNoLock(ref key, ref input, ref value, ref recordInfo, address) : InPlaceUpdaterLock(ref key, ref input, ref value, ref recordInfo, address); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool InPlaceUpdaterNoLock(ref Key key, ref Input input, ref Value value, long address) + private bool InPlaceUpdaterNoLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) => _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value) - && _clientSession.fht.UpdateSIForIPU(ref value, address, this.SecondaryIndexSessionBroker); + && _clientSession.fht.UpdateSIForIPU(ref value, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) { @@ -985,7 +985,7 @@ private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, r this.Lock(ref recordInfo, ref key, ref value, LockType.Exclusive, ref context); try { - return !recordInfo.Tombstone && InPlaceUpdaterNoLock(ref key, ref input, ref value, address); + return !recordInfo.Tombstone && InPlaceUpdaterNoLock(ref key, ref input, ref value, ref recordInfo, address); } finally { diff --git a/cs/src/core/ClientSession/FASTERAsync.cs b/cs/src/core/ClientSession/FASTERAsync.cs index e46b1459b..bcba9ac3b 100644 --- a/cs/src/core/ClientSession/FASTERAsync.cs +++ b/cs/src/core/ClientSession/FASTERAsync.cs @@ -540,7 +540,12 @@ internal ValueTask> UpsertAsync>(ref key, ref this.hlog.GetValue(physicalAddress), + ref this.hlog.GetInfo(physicalAddress), pcontext.logicalAddress, fasterSession); return new ValueTask>(new UpsertAsyncResult(internalStatus)); + } Debug.Assert(internalStatus == OperationStatus.ALLOCATE_FAILED); } finally @@ -822,9 +827,8 @@ internal ValueTask> RmwAsync>(ref key, ref value, ref recordInfo, pcontext.logicalAddress, fasterSession); + UpdateSIForInsert>(ref key, ref this.hlog.GetValue(physicalAddress), + ref this.hlog.GetInfo(physicalAddress), pcontext.logicalAddress, fasterSession); } return new ValueTask>(new RmwAsyncResult((Status)internalStatus, default)); } @@ -839,9 +843,8 @@ internal ValueTask> RmwAsync>(ref key, ref value, ref recordInfo, pcontext.logicalAddress, fasterSession); + UpdateSIForInsert>(ref key, ref this.hlog.GetValue(physicalAddress), + ref this.hlog.GetInfo(physicalAddress), pcontext.logicalAddress, fasterSession); } return new ValueTask>(new RmwAsyncResult(status, default)); } diff --git a/cs/src/core/Index/Common/CompletedOutput.cs b/cs/src/core/Index/Common/CompletedOutput.cs index bd6b7fbd2..152aa34d4 100644 --- a/cs/src/core/Index/Common/CompletedOutput.cs +++ b/cs/src/core/Index/Common/CompletedOutput.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using System; -using System.Collections.Generic; namespace FASTER.core { @@ -94,7 +93,7 @@ public struct CompletedOutput public ref TInput Input => ref inputContainer.Get(); /// - /// The output for this pending operation. + /// The output for this pending operation. It is the caller's responsibility to dispose this if necessary; will not affect it. /// public TOutput Output; diff --git a/cs/src/core/Index/Common/HeapContainer.cs b/cs/src/core/Index/Common/HeapContainer.cs index da928b72c..2a2833d90 100644 --- a/cs/src/core/Index/Common/HeapContainer.cs +++ b/cs/src/core/Index/Common/HeapContainer.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using System; -using System.Runtime.CompilerServices; namespace FASTER.core { @@ -10,18 +9,12 @@ namespace FASTER.core /// Heap container to store keys and values when they go pending /// /// - public interface IHeapContainer + public interface IHeapContainer : IDisposable { /// - /// Get object + /// Get a reference to the contained object /// - /// ref T Get(); - - /// - /// Dispose container - /// - void Dispose(); } /// @@ -32,10 +25,7 @@ internal class StandardHeapContainer : IHeapContainer { private T obj; - public StandardHeapContainer(ref T obj) - { - this.obj = obj; - } + public StandardHeapContainer(ref T obj) => this.obj = obj; public ref T Get() => ref obj; diff --git a/cs/src/core/Index/FASTER/FASTER.cs b/cs/src/core/Index/FASTER/FASTER.cs index 77ed8fd4a..9920c8352 100644 --- a/cs/src/core/Index/FASTER/FASTER.cs +++ b/cs/src/core/Index/FASTER/FASTER.cs @@ -609,11 +609,11 @@ internal Status ContextUpsert(ref Key key } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool UpdateSIForIPU(ref Value value, long address, SecondaryIndexSessionBroker indexSessionBroker) + internal bool UpdateSIForIPU(ref Value value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { // KeyIndexes do not need notification of in-place updates because the key does not change. if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Upsert(ref value, address, indexSessionBroker); + this.SecondaryIndexBroker.Upsert(ref value, recordId, indexSessionBroker); return true; } @@ -633,9 +633,9 @@ private void UpdateSIForInsertNoLock(ref Key key, ref Value value, ref RecordInf if (!recordInfo.Invalid && !recordInfo.Tombstone) { if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) - this.SecondaryIndexBroker.Insert(ref key, address, indexSessionBroker); + this.SecondaryIndexBroker.Insert(ref key, indexSessionBroker); if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Insert(ref value, address, indexSessionBroker); + this.SecondaryIndexBroker.Insert(ref value, new RecordId(address, recordInfo), indexSessionBroker); } } @@ -722,7 +722,7 @@ internal Status ContextDelete( // No need to lock here; we have just written a new record with a tombstone, so it will not be changed // TODO - but this can race with an INSERT... - this.UpdateSIForDelete(ref key, pcontext.logicalAddress, isNewRecord: true, fasterSession.SecondaryIndexSessionBroker); + this.UpdateSIForDelete(ref key, new RecordId(pcontext.logicalAddress, pcontext.recordInfo), isNewRecord: true, fasterSession.SecondaryIndexSessionBroker); } Debug.Assert(serialNo >= sessionCtx.serialNum, "Operation serial numbers must be non-decreasing"); diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index 1155fce82..dd5e9de35 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -1208,12 +1208,12 @@ internal OperationStatus InternalDelete( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool UpdateSIForDelete(ref Key key, long address, bool isNewRecord, SecondaryIndexSessionBroker indexSessionBroker) + internal bool UpdateSIForDelete(ref Key key, RecordId recordId, bool isNewRecord, SecondaryIndexSessionBroker indexSessionBroker) { if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) - this.SecondaryIndexBroker.Delete(ref key, address, indexSessionBroker); + this.SecondaryIndexBroker.Delete(ref key, indexSessionBroker); if (!isNewRecord && this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Delete(address, indexSessionBroker); + this.SecondaryIndexBroker.Delete(recordId, indexSessionBroker); return true; } diff --git a/cs/src/core/SecondaryIndex/ISecondaryIndex.cs b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs index ab90b810c..ea0bd4fbf 100644 --- a/cs/src/core/SecondaryIndex/ISecondaryIndex.cs +++ b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs @@ -30,38 +30,36 @@ public interface ISecondaryIndex public interface ISecondaryKeyIndex : ISecondaryIndex { /// - /// Inserts a key into the secondary index. Called only for mutable indexes, on the initial insert of a Key. + /// Inserts a key into the secondary index. Called only for mutable indexes, on the initial insert of a Key. KeyIndexes do not take RecordIds + /// because they reflect the current value of the primary FasterKV Key. /// /// The key to be inserted; always mutable - /// The identifier of the record containing the /// The for the primary FasterKV session making this call /// /// If the index is mutable and the is already there, this call should be ignored, because it is the result /// of a race in which the record in the primary FasterKV was updated after the initial insert but before this method /// was called. /// - void Insert(ref TKVKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker); + void Insert(ref TKVKey key, SecondaryIndexSessionBroker indexSessionBroker); /// /// Upserts a key into the secondary index. This may be called either immediately during a FasterKV operation, or when the page containing a record goes ReadOnly. /// /// The key to be inserted - /// The identifier of the record containing the /// Whether the recordId was in the mutable region of FASTER. If true, the record may subsequently be Upserted or Deleted. /// The for the primary FasterKV session making this call /// /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. /// In this case, is false, and the index may move the to an immutable storage area. /// - void Upsert(ref TKVKey key, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); + void Upsert(ref TKVKey key, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); /// /// Removes a key from the secondary index. Called only for mutable indexes. /// /// The key to be removed - /// The identifier of the record containing the /// The for the primary FasterKV session making this call - void Delete(ref TKVKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker); + void Delete(ref TKVKey key, SecondaryIndexSessionBroker indexSessionBroker); } /// @@ -82,7 +80,7 @@ public interface ISecondaryValueIndex : ISecondaryIndex /// updated after the initial insert but before this method was called, so the on this call /// would overwrite it with an obsolete value. /// - void Insert(ref TKVValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker); + void Insert(ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); /// /// Upserts a recordId into the secondary index, with the associated value from which the index derives its key(s). @@ -96,13 +94,13 @@ public interface ISecondaryValueIndex : ISecondaryIndex /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. /// In this case, is false, and the index may move the to an immutable storage area. /// - void Upsert(ref TKVValue value, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); + void Upsert(ref TKVValue value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); /// /// Removes a recordId from the secondary index. Called only for mutable indexes. /// /// The recordId to be removed /// The for the primary FasterKV session making this call - void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker); + void Delete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); } } \ No newline at end of file diff --git a/cs/src/core/SecondaryIndex/QueryRecord.cs b/cs/src/core/SecondaryIndex/QueryRecord.cs new file mode 100644 index 000000000..7e4532233 --- /dev/null +++ b/cs/src/core/SecondaryIndex/QueryRecord.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace FASTER.core.SecondaryIndex +{ + /// + /// The wrapper around the provider data stored in the primary faster instance. + /// + /// The type of the key in the primary FasterKV instance + /// The type of the value in the primary FasterKV instance + /// Having this class enables separation between the LogicalAddress stored in the Index-implementing + /// FasterKV instances, and the actual and + /// types. + public class QueryRecord : IDisposable + { + internal IHeapContainer keyContainer; + internal IHeapContainer valueContainer; + + internal QueryRecord(IHeapContainer keyContainer, IHeapContainer valueContainer) + { + this.keyContainer = keyContainer; + this.valueContainer = valueContainer; + } + + /// + /// A reference to the record key + /// + public ref TKVKey Key => ref this.keyContainer.Get(); + + /// + /// A reference to the record value + /// + public ref TKVValue Value => ref this.valueContainer.Get(); + + /// + public void Dispose() + { + this.keyContainer.Dispose(); + this.valueContainer.Dispose(); + } + + /// + public override string ToString() => $"Key = {this.keyContainer.Get()}; Value = {this.valueContainer.Get()}"; + } +} diff --git a/cs/src/core/SecondaryIndex/ReadOnlyObserver.cs b/cs/src/core/SecondaryIndex/ReadOnlyObserver.cs index dc16ab676..fcdb9a068 100644 --- a/cs/src/core/SecondaryIndex/ReadOnlyObserver.cs +++ b/cs/src/core/SecondaryIndex/ReadOnlyObserver.cs @@ -8,13 +8,16 @@ namespace FASTER.core class ReadOnlyObserver : IObserver> { readonly SecondaryIndexBroker secondaryIndexBroker; + + // We're not operating in the context of a FasterKV session, so we need our own sessionBroker. readonly SecondaryIndexSessionBroker indexSessionBroker = new SecondaryIndexSessionBroker(); internal ReadOnlyObserver(SecondaryIndexBroker sib) => this.secondaryIndexBroker = sib; public void OnCompleted() { - // Called when AllocatorBase is Disposed; nothing to do here. + // Called when AllocatorBase is Disposed + indexSessionBroker.Dispose(); } public void OnError(Exception error) @@ -24,9 +27,9 @@ public void OnError(Exception error) public void OnNext(IFasterScanIterator iter) { - while (iter.GetNext(out _, out TKVKey key, out TKVValue value)) + while (iter.GetNext(out var recordInfo, out TKVKey key, out TKVValue value)) { - secondaryIndexBroker.UpsertReadOnly(ref key, ref value, iter.CurrentAddress, indexSessionBroker); + secondaryIndexBroker.UpsertReadOnly(ref key, ref value, new RecordId(iter.CurrentAddress, recordInfo), indexSessionBroker); } } } diff --git a/cs/src/core/SecondaryIndex/RecordId.cs b/cs/src/core/SecondaryIndex/RecordId.cs new file mode 100644 index 000000000..d9a845564 --- /dev/null +++ b/cs/src/core/SecondaryIndex/RecordId.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace FASTER.core +{ + /// + /// Encapsulates the address and version of a record in the log + /// + public struct RecordId + { + private long word; + + internal RecordId(long address, RecordInfo recordInfo) : this(address, recordInfo.Version) { } + + internal RecordId(long address, int version) + { + this.word = default; + this.Address = address; + this.Version = version; + } + + /// + /// The version of the record + /// + public int Version + { + get + { + return (int)(((word & RecordInfo.kVersionMaskInWord) >> RecordInfo.kVersionShiftInWord) & RecordInfo.kVersionMaskInInteger); + } + set + { + word &= ~RecordInfo.kVersionMaskInWord; + word |= ((value & RecordInfo.kVersionMaskInInteger) << RecordInfo.kVersionShiftInWord); + } + } + + /// + /// The logical address of the record + /// + public long Address + { + get + { + return (word & RecordInfo.kPreviousAddressMask); + } + set + { + word &= ~RecordInfo.kPreviousAddressMask; + word |= (value & RecordInfo.kPreviousAddressMask); + } + } + + /// + /// Whether this is a default instance of RecordId + /// + public bool IsDefault => this.Address == Constants.kInvalidAddress; + } +} diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs index d658493c0..eaf475956 100644 --- a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs +++ b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs @@ -92,33 +92,33 @@ bool addSpecific(ref TIndex[] allVec, ref TIndex[] mutableVec, TIndex id /// Inserts a mutable key into all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Insert(ref TKVKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Insert(ref TKVKey key, SecondaryIndexSessionBroker indexSessionBroker) { var mki = this.mutableKeyIndexes; foreach (var keyIndex in mki) - keyIndex.Insert(ref key, recordId, indexSessionBroker); + keyIndex.Insert(ref key, indexSessionBroker); } /// /// Upserts a mutable key into all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Upsert(ref TKVKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Upsert(ref TKVKey key, SecondaryIndexSessionBroker indexSessionBroker) { var mki = this.mutableKeyIndexes; foreach (var keyIndex in mki) - keyIndex.Upsert(ref key, recordId, isMutableRecord: true, indexSessionBroker); + keyIndex.Upsert(ref key, isMutableRecord: true, indexSessionBroker); } /// /// Deletes recordId for a key from all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Delete(ref TKVKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Delete(ref TKVKey key, SecondaryIndexSessionBroker indexSessionBroker) { var mki = this.mutableKeyIndexes; foreach (var keyIndex in mki) - keyIndex.Delete(ref key, recordId, indexSessionBroker); + keyIndex.Delete(ref key, indexSessionBroker); } #endregion Mutable KeyIndexes @@ -127,7 +127,7 @@ public void Delete(ref TKVKey key, long recordId, SecondaryIndexSessionBroker in /// Inserts a recordId keyed by a mutable value into all mutable secondary value indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Insert(ref TKVValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Insert(ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mvi = this.mutableValueIndexes; foreach (var valueIndex in mvi) @@ -138,7 +138,7 @@ public void Insert(ref TKVValue value, long recordId, SecondaryIndexSessionBroke /// Upserts a recordId keyed by a mutable value into all mutable secondary value indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Upsert(ref TKVValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Upsert(ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mvi = this.mutableValueIndexes; foreach (var valueIndex in mvi) @@ -149,7 +149,7 @@ public void Upsert(ref TKVValue value, long recordId, SecondaryIndexSessionBroke /// Deletes a recordId keyed by a mutable value from all mutable secondary value indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Delete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mvi = this.mutableValueIndexes; foreach (var valueIndex in mvi) @@ -160,14 +160,14 @@ public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker /// /// Upserts a readonly key into all secondary key indexes and readonly values into secondary value indexes. /// - public void UpsertReadOnly(ref TKVKey key, ref TKVValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void UpsertReadOnly(ref TKVKey key, ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var ki = this.allKeyIndexes; var vi = this.allValueIndexes; if (ki is { }) { foreach (var keyIndex in ki) - keyIndex.Upsert(ref key, recordId, isMutableRecord: false, indexSessionBroker); + keyIndex.Upsert(ref key, isMutableRecord: false, indexSessionBroker); } if (vi is { }) { diff --git a/cs/test/ReadAddressTests.cs b/cs/test/ReadAddressTests.cs index ad9162a74..fe3e67375 100644 --- a/cs/test/ReadAddressTests.cs +++ b/cs/test/ReadAddressTests.cs @@ -70,11 +70,11 @@ private class InsertValueIndex : ISecondaryValueIndex public void SetSessionSlot(long slot) { } - public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Delete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Insert(ref Value value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) => lastWriteAddress = recordId; + public void Insert(ref Value value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) => lastWriteAddress = recordId.Address; - public void Upsert(ref Value value, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Upsert(ref Value value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } } private static long SetReadOutput(long key, long value) => (key << 32) | value; diff --git a/cs/test/SimpleIndexBase.cs b/cs/test/SimpleIndexBase.cs index 734ae90fc..100f03784 100644 --- a/cs/test/SimpleIndexBase.cs +++ b/cs/test/SimpleIndexBase.cs @@ -12,23 +12,15 @@ namespace FASTER.test { class SimpleIndexBase { - protected readonly internal Dictionary> MutableRecords = new Dictionary>(); - protected readonly internal Dictionary> ImmutableRecords = new Dictionary>(); - private readonly Dictionary reverseLookup = new Dictionary(); - protected internal long sessionSlot = 0; - private readonly bool isKeyIndex; - private readonly string indexType; + protected readonly string indexType; private Guid sessionId = Guid.Empty; - readonly Func indexKeyFunc; - protected SimpleIndexBase(string name, bool isKeyIndex, Func indexKeyFunc, bool isMutableIndex) + protected SimpleIndexBase(string name, bool isKeyIndex, bool isMutableIndex) { this.Name = name; this.IsMutable = isMutableIndex; - this.isKeyIndex = isKeyIndex; this.indexType = isKeyIndex ? "KeyIndex" : "ValueIndex"; - this.indexKeyFunc = indexKeyFunc; } public string Name { get; private set; } @@ -37,24 +29,132 @@ protected SimpleIndexBase(string name, bool isKeyIndex, Func indexKe public void SetSessionSlot(long slot) => this.sessionSlot = slot; + protected void VerifySession(SecondaryIndexSessionBroker indexSessionBroker, bool isMutableRecord = true) + { + Assert.IsNotNull(indexSessionBroker); + var sessionObject = indexSessionBroker.GetSessionObject(this.sessionSlot); + + // For these tests, we will always do all mutable inserts before checkpointing and getting readonly inserts. + // The readonly inserts come from a different SecondaryIndexSessionBroker (owned by the SecondaryIndexBroker + // for the ReadOnlyObserver), so we expect not to find a session there on the first call. + if (isMutableRecord) + Assert.AreEqual(sessionObject is null, this.sessionId == Guid.Empty); + + if (!(sessionObject is SimpleIndexSession session)) + { + if (sessionObject is { }) + Assert.Fail($"Unexpected session object type {sessionObject.GetType().Name} for {indexType}"); + + if (this.sessionId == Guid.Empty) + this.sessionId = Guid.NewGuid(); + session = new SimpleIndexSession() { Id = this.sessionId }; + indexSessionBroker.SetSessionObject(this.sessionSlot, session); + } + Assert.AreEqual(this.sessionId, session.Id); + } + } + + class SimpleKeyIndexBase : SimpleIndexBase + where TKey : IComparable + { + // Value is IsMutable + protected internal readonly Dictionary keys = new Dictionary(); + + protected SimpleKeyIndexBase(string name, bool isMutableIndex) + : base(name, isKeyIndex: true, isMutableIndex) + { + } + // Name methods "BaseXxx" instead of using virtuals so we don't get unwanted implementations, e.g. of Delete taking a key for the ValueIndex. - internal void BaseDelete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) + internal void BaseDelete(ref TKey key, SecondaryIndexSessionBroker indexSessionBroker) { - Assert.IsFalse(this.isKeyIndex); - if (!reverseLookup.TryGetValue(recordId, out TKey key)) - Assert.Fail($"RecordId {recordId} not found in revserse lookup for {indexType}"); - DoDelete(ref key, recordId, indexSessionBroker); + VerifySession(indexSessionBroker); + + Assert.IsTrue(this.keys.TryGetValue(key, out bool isMutable)); + Assert.IsFalse(isMutable); + this.keys.Remove(key); + } + + private void UpdateKey(ref TKey key, bool isMutable, SecondaryIndexSessionBroker indexSessionBroker) + { + VerifySession(indexSessionBroker, isMutable); + + // Key indexes just track the key, and a mutable record for a key may be added after the page for its previous record goes immutable. + this.keys[key] = isMutable; } - internal void BaseDelete(ref TKey rawKey, long recordId, SecondaryIndexSessionBroker indexSessionBroker) - => DoDelete(ref rawKey, recordId, indexSessionBroker); + internal void BaseInsert(ref TKey key, SecondaryIndexSessionBroker indexSessionBroker) + => UpdateKey(ref key, isMutable: true, indexSessionBroker); - private void DoDelete(ref TKey rawKey, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + internal void BaseUpsert(ref TKey key, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) + => UpdateKey(ref key, isMutableRecord, indexSessionBroker); + + // Pseudo-range index + internal void Query(TKey from, TKey to, int expectedCount) { - var key = this.indexKeyFunc(rawKey); - VerifyNotImmutable(ref key, recordId); + var keysFound = this.keys.Keys.Where(k => k.CompareTo(from) >= 0 && k.CompareTo(to) <= 0).OrderBy(k => k).ToArray(); + Assert.AreEqual(expectedCount, keysFound.Length); + } + + internal static void VerifyIntIndex(ISecondaryIndex index, PrimaryFasterKV store) + { + const int from = 42, to = 101; + var indexBase = index as SimpleKeyIndexBase; + Assert.AreEqual(indexBase.IsMutable ? SimpleIndexUtils.NumKeys : 0, indexBase.keys.Count); + + if (store is null) + { + Assert.IsFalse(indexBase.keys.Values.Any(isMutable => !isMutable)); + indexBase.Query(from, to, to - from + 1); + return; + } + + store.Checkpoint(); + Assert.IsFalse(indexBase.keys.Values.Any(isMutable => isMutable)); + indexBase.Query(from, to, to - from + 1); + } + + internal static void VerifyMixedIntIndexes(ISecondaryIndex mutableIndex, ISecondaryIndex immutableIndex, PrimaryFasterKV store) + { + const int from = 42, to = 101; + var mutableIndexBase = mutableIndex as SimpleKeyIndexBase; + var immutableIndexBase = immutableIndex as SimpleKeyIndexBase; + Assert.AreEqual(SimpleIndexUtils.NumKeys, mutableIndexBase.keys.Count); + Assert.AreEqual(0, immutableIndexBase.keys.Count); + + store.Checkpoint(); + Assert.AreEqual(SimpleIndexUtils.NumKeys, mutableIndexBase.keys.Count); + Assert.AreEqual(SimpleIndexUtils.NumKeys, immutableIndexBase.keys.Count); + + mutableIndexBase.Query(from, to, to - from + 1); + immutableIndexBase.Query(from, to, to - from + 1); + } + } + + class SimpleValueIndexBase : SimpleIndexBase + { + protected internal readonly Dictionary> MutableRecords = new Dictionary>(); + protected internal readonly Dictionary> ImmutableRecords = new Dictionary>(); + private readonly Dictionary reverseLookup = new Dictionary(); + readonly Func valueIndexKeyFunc; + + protected SimpleValueIndexBase(string name, Func indexKeyFunc, bool isMutableIndex) + : base(name, isKeyIndex: false, isMutableIndex) + { + this.valueIndexKeyFunc = indexKeyFunc; + } + + // Name methods "BaseXxx" instead of using virtuals so we don't get unwanted implementations, e.g. of Delete taking a key for the ValueIndex. + + internal void BaseDelete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + { + Assert.IsFalse(recordId.IsDefault); VerifySession(indexSessionBroker); + if (!reverseLookup.TryGetValue(recordId, out TValue key)) + Assert.Fail($"RecordId {recordId} not found in revserse lookup for {indexType}"); + + VerifyNotImmutable(ref key, recordId); if (!MutableRecords.ContainsKey(key)) Assert.Fail($"{indexType} '{key}' not found as index key"); MutableRecords.Remove(key); @@ -63,27 +163,28 @@ private void DoDelete(ref TKey rawKey, long recordId, SecondaryIndexSessionBroke reverseLookup.Remove(recordId); } - internal void BaseInsert(ref TKey rawKey, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + internal void BaseInsert(ref TValue rawKey, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { - var key = this.indexKeyFunc(rawKey); - VerifyNotImmutable(ref key, recordId); VerifySession(indexSessionBroker); + Assert.IsFalse(recordId.IsDefault); + var key = this.valueIndexKeyFunc(rawKey); + VerifyNotImmutable(ref key, recordId); if (!MutableRecords.TryGetValue(key, out var recordIds)) { - recordIds = new List(); + recordIds = new List(); MutableRecords[key] = recordIds; } else if (recordIds.Contains(recordId)) { return; } - recordIds.Add(recordId); AddToReverseLookup(ref key, recordId); } - internal void BaseUpsert(ref TKey rawKey, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) + internal void BaseUpsert(ref TValue rawKey, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { + VerifySession(indexSessionBroker, isMutableRecord); if (isMutableRecord) { BaseInsert(ref rawKey, recordId, indexSessionBroker); @@ -91,7 +192,7 @@ internal void BaseUpsert(ref TKey rawKey, long recordId, bool isMutableRecord, S } // Move from mutable to immutable - var key = this.indexKeyFunc(rawKey); + var key = this.valueIndexKeyFunc(rawKey); VerifyNotImmutable(ref key, recordId); VerifySession(indexSessionBroker, isMutableRecord); if (MutableRecords.TryGetValue(key, out var recordIds) && recordIds.Contains(recordId)) @@ -103,42 +204,18 @@ internal void BaseUpsert(ref TKey rawKey, long recordId, bool isMutableRecord, S if (!ImmutableRecords.TryGetValue(key, out recordIds)) { - recordIds = new List(); + recordIds = new List(); ImmutableRecords[key] = recordIds; } recordIds.Add(recordId); AddToReverseLookup(ref key, recordId); } - private void VerifySession(SecondaryIndexSessionBroker indexSessionBroker, bool isMutableRecord = true) - { - Assert.IsNotNull(indexSessionBroker); - var sessionObject = indexSessionBroker.GetSessionObject(this.sessionSlot); - - // For these tests, we will always do all mutable inserts before checkpointing and getting readonly inserts. - // The readonly inserts come from a different SecondaryIndexSessionBroker (owned by the SecondaryIndexBroker - // for the ReadOnlyObserver), so we expect not to find a session there on the first call. - if (isMutableRecord) - Assert.AreEqual(sessionObject is null, this.sessionId == Guid.Empty); + private readonly List emptyRecordList = new List(); - if (!(sessionObject is SimpleIndexSession session)) - { - if (sessionObject is { }) - Assert.Fail($"Unexpected session object type {sessionObject.GetType().Name} for {indexType}"); - - if (this.sessionId == Guid.Empty) - this.sessionId = Guid.NewGuid(); - session = new SimpleIndexSession() { Id = this.sessionId }; - indexSessionBroker.SetSessionObject(this.sessionSlot, session); - } - Assert.AreEqual(this.sessionId, session.Id); - } - - private readonly List emptyRecordList = new List(); - - internal long[] Query(TKey rawKey) + internal RecordId[] Query(TValue rawKey) { - var key = this.indexKeyFunc(rawKey); + var key = this.valueIndexKeyFunc(rawKey); if (!MutableRecords.TryGetValue(key, out var mutableRecordList)) mutableRecordList = emptyRecordList; if (!ImmutableRecords.TryGetValue(key, out var immutableRecordList)) @@ -147,15 +224,15 @@ internal long[] Query(TKey rawKey) return mutableRecordList.Concat(immutableRecordList).ToArray(); } - private void VerifyNotImmutable(ref TKey key, long recordId) + private void VerifyNotImmutable(ref TValue key, RecordId recordId) { if (ImmutableRecords.TryGetValue(key, out var recordIds) && recordIds.Contains(recordId)) Assert.Fail($"Unexpected update of recordId {recordId} for {indexType} '{key}'"); } - private void AddToReverseLookup(ref TKey key, long recordId) + private void AddToReverseLookup(ref TValue key, RecordId recordId) { - if (reverseLookup.TryGetValue(recordId, out TKey existingKey)) + if (reverseLookup.TryGetValue(recordId, out TValue existingKey)) { if (!existingKey.Equals(key)) Assert.Fail($"Unexpected update of recordId {recordId} for {indexType} '{key}'"); @@ -163,6 +240,52 @@ private void AddToReverseLookup(ref TKey key, long recordId) } reverseLookup[recordId] = key; } + + internal static void VerifyMutableIntIndex(ISecondaryIndex secondaryIndex, int indexKeyDivisor, int queryKeyOffset) + { + var indexBase = secondaryIndex as SimpleValueIndexBase; + Assert.AreEqual(SimpleIndexUtils.NumKeys / indexKeyDivisor, indexBase.MutableRecords.Count); + Assert.AreEqual(0, indexBase.ImmutableRecords.Count); + + var records = indexBase.Query(42 + queryKeyOffset); + Assert.AreEqual(indexKeyDivisor, records.Length); + } + + internal static void VerifyImmutableIntIndex(ISecondaryIndex secondaryIndex, int indexKeyDivisor, int queryKeyOffset, PrimaryFasterKV store) + { + var indexBase = secondaryIndex as SimpleValueIndexBase; + Assert.AreEqual(0, indexBase.MutableRecords.Count); + Assert.AreEqual(0, indexBase.ImmutableRecords.Count); + + store.Checkpoint(); + Assert.AreEqual(0, indexBase.MutableRecords.Count); + Assert.AreEqual(SimpleIndexUtils.NumKeys / indexKeyDivisor, indexBase.ImmutableRecords.Count); + + var records = indexBase.Query(42 + queryKeyOffset); + Assert.AreEqual(indexKeyDivisor, records.Length); + } + + internal static void VerifyMixedIntIndexes(ISecondaryIndex mutableIndex, ISecondaryIndex immutableIndex, int indexKeyDivisor, int queryKeyOffset, PrimaryFasterKV store) + { + var mutableIndexBase = mutableIndex as SimpleValueIndexBase; + var immutableIndexBase = immutableIndex as SimpleValueIndexBase; + Assert.AreEqual(SimpleIndexUtils.NumKeys / indexKeyDivisor, mutableIndexBase.MutableRecords.Count); + Assert.AreEqual(0, mutableIndexBase.ImmutableRecords.Count); + Assert.AreEqual(0, immutableIndexBase.MutableRecords.Count); + Assert.AreEqual(0, immutableIndexBase.ImmutableRecords.Count); + + store.Checkpoint(); + Assert.AreEqual(0, mutableIndexBase.MutableRecords.Count); + Assert.AreEqual(SimpleIndexUtils.NumKeys / indexKeyDivisor, mutableIndexBase.ImmutableRecords.Count); + Assert.AreEqual(0, immutableIndexBase.MutableRecords.Count); + Assert.AreEqual(SimpleIndexUtils.NumKeys / indexKeyDivisor, immutableIndexBase.ImmutableRecords.Count); + + var records = mutableIndexBase.Query(42 + queryKeyOffset); + Assert.AreEqual(indexKeyDivisor, records.Length); + + records = immutableIndexBase.Query(42 + queryKeyOffset); + Assert.AreEqual(indexKeyDivisor, records.Length); + } } internal class SimpleIndexSession @@ -236,11 +359,10 @@ internal static void PopulateInts(FasterKV fkv, bool useRMW, bool isAs } else { - // TODO: UpsertAsync - //if (isAsync) - // session.UpsertAsync(ref key, ref value).GetAwaiter().GetResult().Complete(); - //else - session.Upsert(ref key, ref value); + if (isAsync) + session.UpsertAsync(ref key, ref value).GetAwaiter().GetResult().Complete(); + else + session.Upsert(ref key, ref value); } } @@ -265,62 +387,15 @@ internal static void PopulateIntsWithAdvancedFunctions(FasterKV fkv, b } else { - // TODO: UpsertAsync - //if (isAsync) - // session.UpsertAsync(ref key, ref value).GetAwaiter().GetResult().Complete(); - //else - session.Upsert(ref key, ref value); + if (isAsync) + session.UpsertAsync(ref key, ref value).GetAwaiter().GetResult().Complete(); + else + session.Upsert(ref key, ref value); } } // Make sure operations are completed session.CompletePending(true); } - - internal static void VerifyMutableIndex(ISecondaryIndex secondaryIndex, int indexKeyDivisor, int queryKeyOffset) - { - var indexBase = secondaryIndex as SimpleIndexBase; - Assert.AreEqual(NumKeys / indexKeyDivisor, indexBase.MutableRecords.Count); - Assert.AreEqual(0, indexBase.ImmutableRecords.Count); - - var records = indexBase.Query(42 + queryKeyOffset); - Assert.AreEqual(indexKeyDivisor, records.Length); - } - - internal static void VerifyImmutableIndex(ISecondaryIndex secondaryIndex, int indexKeyDivisor, int queryKeyOffset, PrimaryFasterKV store) - { - var indexBase = secondaryIndex as SimpleIndexBase; - Assert.AreEqual(0, indexBase.MutableRecords.Count); - Assert.AreEqual(0, indexBase.ImmutableRecords.Count); - - store.Checkpoint(); - Assert.AreEqual(0, indexBase.MutableRecords.Count); - Assert.AreEqual(NumKeys / indexKeyDivisor, indexBase.ImmutableRecords.Count); - - var records = indexBase.Query(42 + queryKeyOffset); - Assert.AreEqual(indexKeyDivisor, records.Length); - } - - internal static void VerifyMixedIndexes(ISecondaryIndex mutableIndex, ISecondaryIndex immutableIndex, int indexKeyDivisor, int queryKeyOffset, PrimaryFasterKV store) - { - var mutableIndexBase = mutableIndex as SimpleIndexBase; - var immutableIndexBase = immutableIndex as SimpleIndexBase; - Assert.AreEqual(NumKeys / indexKeyDivisor, mutableIndexBase.MutableRecords.Count); - Assert.AreEqual(0, mutableIndexBase.ImmutableRecords.Count); - Assert.AreEqual(0, immutableIndexBase.MutableRecords.Count); - Assert.AreEqual(0, immutableIndexBase.ImmutableRecords.Count); - - store.Checkpoint(); - Assert.AreEqual(0, mutableIndexBase.MutableRecords.Count); - Assert.AreEqual(NumKeys / indexKeyDivisor, mutableIndexBase.ImmutableRecords.Count); - Assert.AreEqual(0, immutableIndexBase.MutableRecords.Count); - Assert.AreEqual(NumKeys / indexKeyDivisor, immutableIndexBase.ImmutableRecords.Count); - - var records = mutableIndexBase.Query(42 + queryKeyOffset); - Assert.AreEqual(indexKeyDivisor, records.Length); - - records = immutableIndexBase.Query(42 + queryKeyOffset); - Assert.AreEqual(indexKeyDivisor, records.Length); - } } } diff --git a/cs/test/SimpleKeyIndexTests.cs b/cs/test/SimpleKeyIndexTests.cs index 4468677b9..d289eb363 100644 --- a/cs/test/SimpleKeyIndexTests.cs +++ b/cs/test/SimpleKeyIndexTests.cs @@ -9,21 +9,21 @@ namespace FASTER.test.SubsetIndex.SimpleIndexTests { class SimpleKeyIndexTests { - class SimpleKeyIndex : SimpleIndexBase, ISecondaryKeyIndex + class SimpleKeyIndex : SimpleKeyIndexBase, ISecondaryKeyIndex + where TKey : IComparable { - internal SimpleKeyIndex(string name, Func indexKeyFunc, bool isMutableIndex) : base(name, isKeyIndex: true, indexKeyFunc, isMutableIndex: isMutableIndex) { } + internal SimpleKeyIndex(string name, bool isMutableIndex) : base(name, isMutableIndex: isMutableIndex) { } - public void Delete(ref TKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) - => BaseDelete(ref key, recordId, indexSessionBroker); + public void Delete(ref TKey key, SecondaryIndexSessionBroker indexSessionBroker) + => BaseDelete(ref key, indexSessionBroker); - public void Insert(ref TKey key, long recordId, SecondaryIndexSessionBroker indexSessionBroker) - => BaseInsert(ref key, recordId, indexSessionBroker); + public void Insert(ref TKey key, SecondaryIndexSessionBroker indexSessionBroker) + => BaseInsert(ref key, indexSessionBroker); - public void Upsert(ref TKey key, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) - => BaseUpsert(ref key, recordId, isMutableRecord, indexSessionBroker); + public void Upsert(ref TKey key, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) + => BaseUpsert(ref key, isMutableRecord, indexSessionBroker); } - private const int keyDivisor = 20; readonly PrimaryFasterKV store = new PrimaryFasterKV(); [SetUp] @@ -32,39 +32,39 @@ public void Upsert(ref TKey key, long recordId, bool isMutableRecord, SecondaryI [TearDown] public void TearDown() => store.TearDown(); - private ISecondaryIndex CreateIndex(bool isMutable, bool isAsync, Func indexKeyFunc) - => new SimpleKeyIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc, isMutable); + private ISecondaryIndex CreateIndex(bool isMutable, bool isAsync) + => new SimpleKeyIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", isMutable); [Test] - [Category("FasterKV")] + [Category("FasterKV"), Category("Index")] public void MutableInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { - var secondaryIndex = CreateIndex(isMutable: true, isAsync, rawKey => rawKey / keyDivisor); + var secondaryIndex = CreateIndex(isMutable: true, isAsync); store.fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); store.Populate(useAdvancedFunctions, useRMW, isAsync); - SimpleIndexUtils.VerifyMutableIndex(secondaryIndex, keyDivisor, 0); + SimpleKeyIndexBase.VerifyIntIndex(secondaryIndex, store: null); } [Test] - [Category("FasterKV")] + [Category("FasterKV"), Category("Index")] public void ImmutableInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { - var secondaryIndex = CreateIndex(isMutable: false, isAsync, rawKey => rawKey / keyDivisor); + var secondaryIndex = CreateIndex(isMutable: false, isAsync); store.fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); store.Populate(useAdvancedFunctions, useRMW, isAsync); - SimpleIndexUtils.VerifyImmutableIndex(secondaryIndex, keyDivisor, 0, store); + SimpleKeyIndexBase.VerifyIntIndex(secondaryIndex, store); } [Test] - [Category("FasterKV")] + [Category("FasterKV"), Category("Index")] public void MixedInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { - var mutableIndex = CreateIndex(isMutable: true, isAsync, rawKey => rawKey / keyDivisor); - var immutableIndex = CreateIndex(isMutable: false, isAsync, rawKey => rawKey / keyDivisor); + var mutableIndex = CreateIndex(isMutable: true, isAsync); + var immutableIndex = CreateIndex(isMutable: false, isAsync); store.fkv.SecondaryIndexBroker.AddIndex(mutableIndex); store.fkv.SecondaryIndexBroker.AddIndex(immutableIndex); store.Populate(useAdvancedFunctions, useRMW, isAsync); - SimpleIndexUtils.VerifyMixedIndexes(mutableIndex, immutableIndex, keyDivisor, 0, store); + SimpleKeyIndexBase.VerifyMixedIntIndexes(mutableIndex, immutableIndex, store); } } } diff --git a/cs/test/SimpleValueIndexTests.cs b/cs/test/SimpleValueIndexTests.cs index 234bee518..1b1fb7cb7 100644 --- a/cs/test/SimpleValueIndexTests.cs +++ b/cs/test/SimpleValueIndexTests.cs @@ -9,17 +9,17 @@ namespace FASTER.test.SubsetIndex.SimpleIndexTests { class SimpleValueIndexTests { - class SimpleValueIndex : SimpleIndexBase, ISecondaryValueIndex + class SimpleValueIndex : SimpleValueIndexBase, ISecondaryValueIndex { - internal SimpleValueIndex(string name, Func indexKeyFunc, bool isMutableIndex) : base(name, isKeyIndex: false, indexKeyFunc, isMutableIndex: isMutableIndex) { } + internal SimpleValueIndex(string name, Func indexKeyFunc, bool isMutableIndex) : base(name, indexKeyFunc, isMutableIndex: isMutableIndex) { } - public void Delete(long recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Delete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) => BaseDelete(recordId, indexSessionBroker); - public void Insert(ref TValue value, long recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Insert(ref TValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) => BaseInsert(ref value, recordId, indexSessionBroker); - public void Upsert(ref TValue value, long recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) + public void Upsert(ref TValue value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) => BaseUpsert(ref value, recordId, isMutableRecord, indexSessionBroker); } @@ -36,27 +36,27 @@ private ISecondaryIndex CreateIndex(bool isMutable, bool isAsync, Func => new SimpleValueIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc, isMutable); [Test] - [Category("FasterKV")] + [Category("FasterKV"), Category("Index")] public void MutableInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { var secondaryIndex = CreateIndex(isMutable: true, isAsync, rawValue => (rawValue - SimpleIndexUtils.ValueStart) / valueDivisor); store.fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); store.Populate(useAdvancedFunctions, useRMW, isAsync); - SimpleIndexUtils.VerifyMutableIndex(secondaryIndex, valueDivisor, SimpleIndexUtils.ValueStart); + SimpleValueIndexBase.VerifyMutableIntIndex(secondaryIndex, valueDivisor, SimpleIndexUtils.ValueStart); } [Test] - [Category("FasterKV")] + [Category("FasterKV"), Category("Index")] public void ImmutableInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { var secondaryIndex = CreateIndex(isMutable: false, isAsync, rawValue => (rawValue - SimpleIndexUtils.ValueStart) / valueDivisor); store.fkv.SecondaryIndexBroker.AddIndex(secondaryIndex); store.Populate(useAdvancedFunctions, useRMW, isAsync); - SimpleIndexUtils.VerifyImmutableIndex(secondaryIndex, valueDivisor, SimpleIndexUtils.ValueStart, store); + SimpleValueIndexBase.VerifyImmutableIntIndex(secondaryIndex, valueDivisor, SimpleIndexUtils.ValueStart, store); } [Test] - [Category("FasterKV")] + [Category("FasterKV"), Category("Index")] public void MixedInsertTest([Values] bool useAdvancedFunctions, [Values] bool useRMW, [Values] bool isAsync) { var mutableIndex = CreateIndex(isMutable: true, isAsync, rawValue => (rawValue - SimpleIndexUtils.ValueStart) / valueDivisor); @@ -64,7 +64,7 @@ public void MixedInsertTest([Values] bool useAdvancedFunctions, [Values] bool us store.fkv.SecondaryIndexBroker.AddIndex(mutableIndex); store.fkv.SecondaryIndexBroker.AddIndex(immutableIndex); store.Populate(useAdvancedFunctions, useRMW, isAsync); - SimpleIndexUtils.VerifyMixedIndexes(mutableIndex, immutableIndex, valueDivisor, SimpleIndexUtils.ValueStart, store); + SimpleValueIndexBase.VerifyMixedIntIndexes(mutableIndex, immutableIndex, valueDivisor, SimpleIndexUtils.ValueStart, store); } } } From 8c88c1a1c34dd6938347ab6d2beed136f799124b Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Sat, 17 Apr 2021 00:09:21 -0700 Subject: [PATCH 27/37] Add QueryRecord.RecordId --- cs/src/core/SecondaryIndex/QueryRecord.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cs/src/core/SecondaryIndex/QueryRecord.cs b/cs/src/core/SecondaryIndex/QueryRecord.cs index 7e4532233..5bc012f3b 100644 --- a/cs/src/core/SecondaryIndex/QueryRecord.cs +++ b/cs/src/core/SecondaryIndex/QueryRecord.cs @@ -18,10 +18,11 @@ public class QueryRecord : IDisposable internal IHeapContainer keyContainer; internal IHeapContainer valueContainer; - internal QueryRecord(IHeapContainer keyContainer, IHeapContainer valueContainer) + internal QueryRecord(IHeapContainer keyContainer, IHeapContainer valueContainer, RecordId recordId) { this.keyContainer = keyContainer; this.valueContainer = valueContainer; + this.RecordId = recordId; } /// @@ -34,6 +35,11 @@ internal QueryRecord(IHeapContainer keyContainer, IHeapContainer public ref TKVValue Value => ref this.valueContainer.Get(); + /// + /// The ID of the record with this key and value; useful for post-query processing across multiple indexes. + /// + public RecordId RecordId { get; } + /// public void Dispose() { From 3b57ef14c893c785f80d16b76f1ce8c195cfb805 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Sat, 17 Apr 2021 00:57:08 -0700 Subject: [PATCH 28/37] RecordId changes from HVI --- cs/src/core/SecondaryIndex/RecordId.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cs/src/core/SecondaryIndex/RecordId.cs b/cs/src/core/SecondaryIndex/RecordId.cs index d9a845564..97faf3294 100644 --- a/cs/src/core/SecondaryIndex/RecordId.cs +++ b/cs/src/core/SecondaryIndex/RecordId.cs @@ -51,9 +51,17 @@ public long Address } } + /// + /// Check that the passed record address and version matches this RecordInfo + /// + public bool Equals(long address, int version) => this.Address == address && this.Version == version; + /// /// Whether this is a default instance of RecordId /// public bool IsDefault => this.Address == Constants.kInvalidAddress; + + /// + public override string ToString() => $"address {this.Address}, version {this.Version}"; } } From f024f3ab2456153fb5e0af0d9046c2b572c738e1 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Sun, 18 Apr 2021 00:16:21 -0700 Subject: [PATCH 29/37] QueryRecord changes from HVI --- cs/src/core/SecondaryIndex/QueryRecord.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cs/src/core/SecondaryIndex/QueryRecord.cs b/cs/src/core/SecondaryIndex/QueryRecord.cs index 5bc012f3b..89890760f 100644 --- a/cs/src/core/SecondaryIndex/QueryRecord.cs +++ b/cs/src/core/SecondaryIndex/QueryRecord.cs @@ -3,7 +3,7 @@ using System; -namespace FASTER.core.SecondaryIndex +namespace FASTER.core { /// /// The wrapper around the provider data stored in the primary faster instance. @@ -28,12 +28,12 @@ internal QueryRecord(IHeapContainer keyContainer, IHeapContainer /// A reference to the record key /// - public ref TKVKey Key => ref this.keyContainer.Get(); + public ref TKVKey KeyRef => ref this.keyContainer.Get(); /// /// A reference to the record value /// - public ref TKVValue Value => ref this.valueContainer.Get(); + public ref TKVValue ValueRef => ref this.valueContainer.Get(); /// /// The ID of the record with this key and value; useful for post-query processing across multiple indexes. From 074028b2afa3fb856b764515002c0c569953310c Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Wed, 5 May 2021 00:04:03 -0700 Subject: [PATCH 30/37] Fixes to multi-observer cleanup; fix recordInfo setting in ReadAsyncInternal; Fix QueryRecord.Dispose(); make RecordId.IsDefault() a method so it's not serialized --- cs/src/core/Allocator/AllocatorBase.cs | 10 +++++----- cs/src/core/ClientSession/FASTERAsync.cs | 2 +- cs/src/core/Index/FASTER/LogAccessor.cs | 2 -- cs/src/core/SecondaryIndex/QueryRecord.cs | 6 ++++-- cs/src/core/SecondaryIndex/RecordId.cs | 3 ++- cs/test/SimpleIndexBase.cs | 4 ++-- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/cs/src/core/Allocator/AllocatorBase.cs b/cs/src/core/Allocator/AllocatorBase.cs index f749b4e0b..fddeee9c7 100644 --- a/cs/src/core/Allocator/AllocatorBase.cs +++ b/cs/src/core/Allocator/AllocatorBase.cs @@ -778,15 +778,15 @@ internal static void RemoveObserver(ref IObserver>[] observers) + internal static void OnCompleted(ref IObserver>[] observers) { - if (observers is null) - return; var localObservers = observers; + if (localObservers is null || Interlocked.CompareExchange(ref observers, null, localObservers) != localObservers) + return; foreach (var observer in localObservers) observer.OnCompleted(); } diff --git a/cs/src/core/ClientSession/FASTERAsync.cs b/cs/src/core/ClientSession/FASTERAsync.cs index bcba9ac3b..0ea3a0d9d 100644 --- a/cs/src/core/ClientSession/FASTERAsync.cs +++ b/cs/src/core/ClientSession/FASTERAsync.cs @@ -144,7 +144,7 @@ internal ReadAsyncInternal(FasterKV fasterKV, IFasterSession /// Subscribe to records (in batches) as they become read-only in the log - /// Currently, we support only one subscriber to the log (easy to extend) /// Subscriber only receives new log updates from the time of subscription onwards /// To scan the historical part of the log, use the Scan(...) method /// @@ -143,7 +142,6 @@ public IDisposable Subscribe(IObserver> readOnly /// /// Subscribe to records (in batches) as they get evicted from main memory. - /// Currently, we support only one subscriber to the log (easy to extend) /// Subscriber only receives eviction updates from the time of subscription onwards /// To scan the historical part of the log, use the Scan(...) method /// diff --git a/cs/src/core/SecondaryIndex/QueryRecord.cs b/cs/src/core/SecondaryIndex/QueryRecord.cs index 89890760f..28013271b 100644 --- a/cs/src/core/SecondaryIndex/QueryRecord.cs +++ b/cs/src/core/SecondaryIndex/QueryRecord.cs @@ -43,8 +43,10 @@ internal QueryRecord(IHeapContainer keyContainer, IHeapContainer public void Dispose() { - this.keyContainer.Dispose(); - this.valueContainer.Dispose(); + this.keyContainer?.Dispose(); + this.keyContainer = null; + this.valueContainer?.Dispose(); + this.valueContainer = null; } /// diff --git a/cs/src/core/SecondaryIndex/RecordId.cs b/cs/src/core/SecondaryIndex/RecordId.cs index 97faf3294..4e16b3c36 100644 --- a/cs/src/core/SecondaryIndex/RecordId.cs +++ b/cs/src/core/SecondaryIndex/RecordId.cs @@ -59,7 +59,8 @@ public long Address /// /// Whether this is a default instance of RecordId /// - public bool IsDefault => this.Address == Constants.kInvalidAddress; + /// This is a method instead of property so it will not be serialized + public bool IsDefault() => this.Address == Constants.kInvalidAddress; /// public override string ToString() => $"address {this.Address}, version {this.Version}"; diff --git a/cs/test/SimpleIndexBase.cs b/cs/test/SimpleIndexBase.cs index 100f03784..c013e0d8c 100644 --- a/cs/test/SimpleIndexBase.cs +++ b/cs/test/SimpleIndexBase.cs @@ -149,7 +149,7 @@ protected SimpleValueIndexBase(string name, Func indexKeyFunc, b internal void BaseDelete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { - Assert.IsFalse(recordId.IsDefault); + Assert.IsFalse(recordId.IsDefault()); VerifySession(indexSessionBroker); if (!reverseLookup.TryGetValue(recordId, out TValue key)) Assert.Fail($"RecordId {recordId} not found in revserse lookup for {indexType}"); @@ -166,7 +166,7 @@ internal void BaseDelete(RecordId recordId, SecondaryIndexSessionBroker indexSes internal void BaseInsert(ref TValue rawKey, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { VerifySession(indexSessionBroker); - Assert.IsFalse(recordId.IsDefault); + Assert.IsFalse(recordId.IsDefault()); var key = this.valueIndexKeyFunc(rawKey); VerifyNotImmutable(ref key, recordId); if (!MutableRecords.TryGetValue(key, out var recordIds)) From 44586161007a4a42b657d4e6ea2f8f24a408d475 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Wed, 5 May 2021 01:18:23 -0700 Subject: [PATCH 31/37] Modify Secondary Index interfaces: - Add RIDs to Key indexes - Add TKVKEey to Value indexes - Add Checkpoint/Restore API - Add Truncation API --- cs/benchmark/FasterSpanByteYcsbBenchmark.cs | 2 +- cs/benchmark/FasterYcsbBenchmark.cs | 2 +- cs/benchmark/SecondaryIndexes.cs | 34 +++++++++--- .../ClientSession/AdvancedClientSession.cs | 4 +- cs/src/core/ClientSession/ClientSession.cs | 4 +- cs/src/core/Index/FASTER/FASTER.cs | 9 ++-- cs/src/core/Index/FASTER/FASTERImpl.cs | 9 ++-- cs/src/core/SecondaryIndex/ISecondaryIndex.cs | 44 ++++++++++++--- .../SecondaryIndex/SecondaryIndexBroker.cs | 53 +++++++++---------- cs/test/ReadAddressTests.cs | 18 +++++-- cs/test/SimpleKeyIndexTests.cs | 16 ++++-- cs/test/SimpleValueIndexTests.cs | 20 +++++-- 12 files changed, 145 insertions(+), 70 deletions(-) diff --git a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs index cfb9abec3..0156b3109 100644 --- a/cs/benchmark/FasterSpanByteYcsbBenchmark.cs +++ b/cs/benchmark/FasterSpanByteYcsbBenchmark.cs @@ -85,7 +85,7 @@ internal FasterSpanByteYcsbBenchmark(KeySpanByte[] i_keys_, KeySpanByte[] t_keys if (testLoader.SecondaryIndexType.HasFlag(SecondaryIndexType.Key)) store.SecondaryIndexBroker.AddIndex(new NullKeyIndex()); if (testLoader.SecondaryIndexType.HasFlag(SecondaryIndexType.Value)) - store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); + store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); } internal void Dispose() diff --git a/cs/benchmark/FasterYcsbBenchmark.cs b/cs/benchmark/FasterYcsbBenchmark.cs index 66e222e13..cd78db7ef 100644 --- a/cs/benchmark/FasterYcsbBenchmark.cs +++ b/cs/benchmark/FasterYcsbBenchmark.cs @@ -82,7 +82,7 @@ internal FASTER_YcsbBenchmark(Key[] i_keys_, Key[] t_keys_, TestLoader testLoade if (testLoader.SecondaryIndexType.HasFlag(SecondaryIndexType.Key)) store.SecondaryIndexBroker.AddIndex(new NullKeyIndex()); if (testLoader.SecondaryIndexType.HasFlag(SecondaryIndexType.Value)) - store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); + store.SecondaryIndexBroker.AddIndex(new NullValueIndex()); } internal void Dispose() diff --git a/cs/benchmark/SecondaryIndexes.cs b/cs/benchmark/SecondaryIndexes.cs index 0bb81240b..b499c947e 100644 --- a/cs/benchmark/SecondaryIndexes.cs +++ b/cs/benchmark/SecondaryIndexes.cs @@ -13,14 +13,24 @@ class NullKeyIndex : ISecondaryKeyIndex public void SetSessionSlot(long slot) { } - public void Delete(ref Key key, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Delete(ref Key key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Insert(ref Key key, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Insert(ref Key key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Upsert(ref Key key, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Upsert(ref Key key, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } + + public void OnPrimaryCheckpoint(int version, long flushedUntilAddress) { } + + public void OnPrimaryRecover(int version, long flushedUntilAddress, out int recoveredToVersion, out long recoveredToAddress) + { + recoveredToVersion = default; + recoveredToAddress = default; + } + + public void OnPrimaryTruncate(long newBeginAddress) { } } - class NullValueIndex : ISecondaryValueIndex + class NullValueIndex : ISecondaryValueIndex { public string Name => "ValueIndex"; @@ -28,10 +38,20 @@ class NullValueIndex : ISecondaryValueIndex public void SetSessionSlot(long slot) { } - public void Delete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Delete(ref Key key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { } + + public void Insert(ref Key key, ref Value value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { } + + public void Upsert(ref Key key, ref Value value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } + + public void OnPrimaryCheckpoint(int version, long flushedUntilAddress) { } - public void Insert(ref Value value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { } + public void OnPrimaryRecover(int version, long flushedUntilAddress, out int recoveredToVersion, out long recoveredToAddress) + { + recoveredToVersion = default; + recoveredToAddress = default; + } - public void Upsert(ref Value value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } + public void OnPrimaryTruncate(long newBeginAddress) { } } } diff --git a/cs/src/core/ClientSession/AdvancedClientSession.cs b/cs/src/core/ClientSession/AdvancedClientSession.cs index 16f14092c..c219e6824 100644 --- a/cs/src/core/ClientSession/AdvancedClientSession.cs +++ b/cs/src/core/ClientSession/AdvancedClientSession.cs @@ -886,7 +886,7 @@ private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, r { recordInfo.Version = _clientSession.ctx.version; return _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst, ref recordInfo, address) - && _clientSession.fht.UpdateSIForIPU(ref dst, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); + && _clientSession.fht.UpdateSIForIPU(ref key, ref dst, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); } private bool ConcurrentWriterLock(ref Key key, ref Value src, ref Value dst, ref RecordInfo recordInfo, long address) @@ -974,7 +974,7 @@ private bool InPlaceUpdaterNoLock(ref Key key, ref Input input, ref Value value, { recordInfo.Version = _clientSession.ctx.version; return _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value, ref recordInfo, address) - && _clientSession.fht.UpdateSIForIPU(ref value, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); + && _clientSession.fht.UpdateSIForIPU(ref key, ref value, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); } private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index c856febbc..5c37a3a50 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -894,7 +894,7 @@ private bool ConcurrentWriterNoLock(ref Key key, ref Value src, ref Value dst, r { recordInfo.Version = _clientSession.ctx.version; return _clientSession.functions.ConcurrentWriter(ref key, ref src, ref dst) - && _clientSession.fht.UpdateSIForIPU(ref dst, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); + && _clientSession.fht.UpdateSIForIPU(ref key, ref dst, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -983,7 +983,7 @@ private bool InPlaceUpdaterNoLock(ref Key key, ref Input input, ref Value value, { recordInfo.Version = _clientSession.ctx.version; return _clientSession.functions.InPlaceUpdater(ref key, ref input, ref value) - && _clientSession.fht.UpdateSIForIPU(ref value, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); + && _clientSession.fht.UpdateSIForIPU(ref key, ref value, new RecordId(address, recordInfo), this.SecondaryIndexSessionBroker); } private bool InPlaceUpdaterLock(ref Key key, ref Input input, ref Value value, ref RecordInfo recordInfo, long address) diff --git a/cs/src/core/Index/FASTER/FASTER.cs b/cs/src/core/Index/FASTER/FASTER.cs index 7ce8a73ec..db66d0975 100644 --- a/cs/src/core/Index/FASTER/FASTER.cs +++ b/cs/src/core/Index/FASTER/FASTER.cs @@ -616,11 +616,11 @@ internal Status ContextUpsert(ref Key key } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool UpdateSIForIPU(ref Value value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + internal bool UpdateSIForIPU(ref Key key, ref Value value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { // KeyIndexes do not need notification of in-place updates because the key does not change. if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Upsert(ref value, recordId, indexSessionBroker); + this.SecondaryIndexBroker.Upsert(ref key, ref value, recordId, indexSessionBroker); return true; } @@ -639,10 +639,11 @@ private void UpdateSIForInsertNoLock(ref Key key, ref Value value, ref RecordInf { if (!recordInfo.Invalid && !recordInfo.Tombstone) { + var recordId = new RecordId(address, recordInfo); if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) - this.SecondaryIndexBroker.Insert(ref key, indexSessionBroker); + this.SecondaryIndexBroker.Insert(ref key, recordId, indexSessionBroker); if (this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Insert(ref value, new RecordId(address, recordInfo), indexSessionBroker); + this.SecondaryIndexBroker.Insert(ref key, ref value, recordId, indexSessionBroker); } } diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index c5832bfed..27aadc19b 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -1222,10 +1222,11 @@ internal OperationStatus InternalDelete( [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool UpdateSIForDelete(ref Key key, RecordId recordId, bool isNewRecord, SecondaryIndexSessionBroker indexSessionBroker) { - if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0) - this.SecondaryIndexBroker.Delete(ref key, indexSessionBroker); - if (!isNewRecord && this.SecondaryIndexBroker.MutableValueIndexCount > 0) - this.SecondaryIndexBroker.Delete(recordId, indexSessionBroker); + // TODO: if isNewRecord, we've added a new record to mark a delete, but the index operation won't have the correct recordId here. Should we read it? + if (!isNewRecord) + recordId = default; + if (this.SecondaryIndexBroker.MutableKeyIndexCount > 0 || this.SecondaryIndexBroker.MutableValueIndexCount > 0) + this.SecondaryIndexBroker.Delete(ref key, recordId, indexSessionBroker); return true; } diff --git a/cs/src/core/SecondaryIndex/ISecondaryIndex.cs b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs index ea0bd4fbf..8b51e5889 100644 --- a/cs/src/core/SecondaryIndex/ISecondaryIndex.cs +++ b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs @@ -22,6 +22,28 @@ public interface ISecondaryIndex /// The slot id to be passed to ; called by . /// void SetSessionSlot(long slot); + + /// + /// Called when the Primary FKV has checkpointed the log (either by itself or as part of a full checkpoint). + /// + /// The version that has been checkpointed by the Primary FKV + /// The highest address that has been flushed by the Primary FKV checkpoint + void OnPrimaryCheckpoint(int version, long flushedUntilAddress); + + /// + /// Called when the Primary FKV has restored itself. + /// + /// The version that was restored by the Primary FKV + /// The highest address that was restored by the Primary FKV checkpoint + /// The primary version the SecondaryIndex recovered to + /// The highest Primary FKV address the SecondaryIndex recovered to + void OnPrimaryRecover(int version, long flushedUntilAddress, out int recoveredToVersion, out long recoveredToAddress); + + /// + /// Called when the Primary FKV has set the new BeginAddress. + /// + /// + void OnPrimaryTruncate(long newBeginAddress); } /// @@ -34,43 +56,47 @@ public interface ISecondaryKeyIndex : ISecondaryIndex /// because they reflect the current value of the primary FasterKV Key. /// /// The key to be inserted; always mutable + /// The identifier of the record containing the ; may be used to generate a list /// The for the primary FasterKV session making this call /// /// If the index is mutable and the is already there, this call should be ignored, because it is the result /// of a race in which the record in the primary FasterKV was updated after the initial insert but before this method /// was called. /// - void Insert(ref TKVKey key, SecondaryIndexSessionBroker indexSessionBroker); + void Insert(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); /// /// Upserts a key into the secondary index. This may be called either immediately during a FasterKV operation, or when the page containing a record goes ReadOnly. /// - /// The key to be inserted + /// The key to be upserted + /// The identifier of the record containing the /// Whether the recordId was in the mutable region of FASTER. If true, the record may subsequently be Upserted or Deleted. /// The for the primary FasterKV session making this call /// /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. /// In this case, is false, and the index may move the to an immutable storage area. /// - void Upsert(ref TKVKey key, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); + void Upsert(ref TKVKey key, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); /// /// Removes a key from the secondary index. Called only for mutable indexes. /// /// The key to be removed + /// The identifier of the record to be removed for the /// The for the primary FasterKV session making this call - void Delete(ref TKVKey key, SecondaryIndexSessionBroker indexSessionBroker); + void Delete(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); } /// /// Interface for a FASTER SecondaryIndex that is derived from the FasterKV Value generic parameter. /// - public interface ISecondaryValueIndex : ISecondaryIndex + public interface ISecondaryValueIndex : ISecondaryIndex { /// /// Inserts a recordId into the secondary index, with the associated value from which the index derives its key(s). /// Called only for mutable indexes, on the initial insert of a Key. /// + /// The key for the being inserted /// The value to be inserted; always mutable /// The identifier of the record containing the /// The for the primary FasterKV session making this call @@ -80,12 +106,13 @@ public interface ISecondaryValueIndex : ISecondaryIndex /// updated after the initial insert but before this method was called, so the on this call /// would overwrite it with an obsolete value. /// - void Insert(ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); + void Insert(ref TKVKey key, ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); /// /// Upserts a recordId into the secondary index, with the associated value from which the index derives its key(s). /// This may be called either immediately during a FasterKV operation, or when the page containing a record goes ReadOnly. /// + /// The key for the being upserted /// The value to be upserted /// The identifier of the record containing the /// Whether the recordId was in the mutable region of FASTER; if so, it may subsequently be Upserted or Deleted. @@ -94,13 +121,14 @@ public interface ISecondaryValueIndex : ISecondaryIndex /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. /// In this case, is false, and the index may move the to an immutable storage area. /// - void Upsert(ref TKVValue value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); + void Upsert(ref TKVKey key, ref TKVValue value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); /// /// Removes a recordId from the secondary index. Called only for mutable indexes. /// + /// The key for the being deleted /// The recordId to be removed /// The for the primary FasterKV session making this call - void Delete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); + void Delete(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); } } \ No newline at end of file diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs index eaf475956..76366aac7 100644 --- a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs +++ b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; #pragma warning disable IDE0056 // Use index operator (^ is not supported on .NET Framework or NETCORE pre-3.0) @@ -15,13 +14,13 @@ namespace FASTER.core public class SecondaryIndexBroker { private ISecondaryKeyIndex[] allKeyIndexes; - private ISecondaryValueIndex[] allValueIndexes; + private ISecondaryValueIndex[] allValueIndexes; // Use arrays for faster traversal. private ISecondaryKeyIndex[] mutableKeyIndexes = Array.Empty>(); internal int MutableKeyIndexCount => mutableKeyIndexes.Length; - private ISecondaryValueIndex[] mutableValueIndexes = Array.Empty>(); + private ISecondaryValueIndex[] mutableValueIndexes = Array.Empty>(); internal int MutableValueIndexCount => mutableValueIndexes.Length; readonly object membershipLock = new object(); @@ -65,7 +64,7 @@ bool addSpecific(ref TIndex[] allVec, ref TIndex[] mutableVec, TIndex id lock (membershipLock) { if (!addSpecific(ref allKeyIndexes, ref mutableKeyIndexes, index as ISecondaryKeyIndex) - && !addSpecific(ref allValueIndexes, ref mutableValueIndexes, index as ISecondaryValueIndex)) + && !addSpecific(ref allValueIndexes, ref mutableValueIndexes, index as ISecondaryValueIndex)) throw new SecondaryIndexException("Object is not a KeyIndex or ValueIndex"); this.HasMutableIndexes |= isMutable; // Note: removing indexes will have to recalculate this index.SetSessionSlot(SecondaryIndexSessionBroker.NextSessionSlot++); @@ -92,33 +91,22 @@ bool addSpecific(ref TIndex[] allVec, ref TIndex[] mutableVec, TIndex id /// Inserts a mutable key into all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Insert(ref TKVKey key, SecondaryIndexSessionBroker indexSessionBroker) + public void Insert(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mki = this.mutableKeyIndexes; foreach (var keyIndex in mki) - keyIndex.Insert(ref key, indexSessionBroker); + keyIndex.Insert(ref key, recordId, indexSessionBroker); } /// /// Upserts a mutable key into all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Upsert(ref TKVKey key, SecondaryIndexSessionBroker indexSessionBroker) + public void Upsert(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mki = this.mutableKeyIndexes; foreach (var keyIndex in mki) - keyIndex.Upsert(ref key, isMutableRecord: true, indexSessionBroker); - } - - /// - /// Deletes recordId for a key from all mutable secondary key indexes. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Delete(ref TKVKey key, SecondaryIndexSessionBroker indexSessionBroker) - { - var mki = this.mutableKeyIndexes; - foreach (var keyIndex in mki) - keyIndex.Delete(ref key, indexSessionBroker); + keyIndex.Upsert(ref key, recordId, isMutableRecord: true, indexSessionBroker); } #endregion Mutable KeyIndexes @@ -127,35 +115,42 @@ public void Delete(ref TKVKey key, SecondaryIndexSessionBroker indexSessionBroke /// Inserts a recordId keyed by a mutable value into all mutable secondary value indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Insert(ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Insert(ref TKVKey key, ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mvi = this.mutableValueIndexes; foreach (var valueIndex in mvi) - valueIndex.Insert(ref value, recordId, indexSessionBroker); + valueIndex.Insert(ref key, ref value, recordId, indexSessionBroker); } /// /// Upserts a recordId keyed by a mutable value into all mutable secondary value indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Upsert(ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Upsert(ref TKVKey key, ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mvi = this.mutableValueIndexes; foreach (var valueIndex in mvi) - valueIndex.Upsert(ref value, recordId, isMutableRecord:false, indexSessionBroker); + valueIndex.Upsert(ref key, ref value, recordId, isMutableRecord:false, indexSessionBroker); } + #endregion Mutable ValueIndexes + + #region Mutable Key and Value Indexes /// - /// Deletes a recordId keyed by a mutable value from all mutable secondary value indexes. + /// Deletes recordId for a key from all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Delete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Delete(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { + var mki = this.mutableKeyIndexes; + foreach (var keyIndex in mki) + keyIndex.Delete(ref key, recordId, indexSessionBroker); + var mvi = this.mutableValueIndexes; foreach (var valueIndex in mvi) - valueIndex.Delete(recordId, indexSessionBroker); + valueIndex.Delete(ref key, recordId, indexSessionBroker); } - #endregion Mutable ValueIndexes + #endregion Mutable Key and Value Indexes /// /// Upserts a readonly key into all secondary key indexes and readonly values into secondary value indexes. @@ -167,12 +162,12 @@ public void UpsertReadOnly(ref TKVKey key, ref TKVValue value, RecordId recordId if (ki is { }) { foreach (var keyIndex in ki) - keyIndex.Upsert(ref key, isMutableRecord: false, indexSessionBroker); + keyIndex.Upsert(ref key, recordId, isMutableRecord: false, indexSessionBroker); } if (vi is { }) { foreach (var valueIndex in vi) - valueIndex.Upsert(ref value, recordId, isMutableRecord: false, indexSessionBroker); + valueIndex.Upsert(ref key, ref value, recordId, isMutableRecord: false, indexSessionBroker); } } } diff --git a/cs/test/ReadAddressTests.cs b/cs/test/ReadAddressTests.cs index fe3e67375..9c9a2a64f 100644 --- a/cs/test/ReadAddressTests.cs +++ b/cs/test/ReadAddressTests.cs @@ -60,7 +60,7 @@ public void Reset() } } - private class InsertValueIndex : ISecondaryValueIndex + private class InsertValueIndex : ISecondaryValueIndex { public long lastWriteAddress; @@ -70,11 +70,21 @@ private class InsertValueIndex : ISecondaryValueIndex public void SetSessionSlot(long slot) { } - public void Delete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Delete(ref Key key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { } - public void Insert(ref Value value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) => lastWriteAddress = recordId.Address; + public void Insert(ref Key key, ref Value value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) => lastWriteAddress = recordId.Address; - public void Upsert(ref Value value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } + public void Upsert(ref Key key, ref Value value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) { } + + public void OnPrimaryCheckpoint(int version, long flushedUntilAddress) { } + + public void OnPrimaryRecover(int version, long flushedUntilAddress, out int recoveredToVersion, out long recoveredToAddress) + { + recoveredToVersion = default; + recoveredToAddress = default; + } + + public void OnPrimaryTruncate(long newBeginAddress) { } } private static long SetReadOutput(long key, long value) => (key << 32) | value; diff --git a/cs/test/SimpleKeyIndexTests.cs b/cs/test/SimpleKeyIndexTests.cs index d289eb363..d70261099 100644 --- a/cs/test/SimpleKeyIndexTests.cs +++ b/cs/test/SimpleKeyIndexTests.cs @@ -14,14 +14,24 @@ class SimpleKeyIndex : SimpleKeyIndexBase, ISecondaryKeyIndex { internal SimpleKeyIndex(string name, bool isMutableIndex) : base(name, isMutableIndex: isMutableIndex) { } - public void Delete(ref TKey key, SecondaryIndexSessionBroker indexSessionBroker) + public void Delete(ref TKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) => BaseDelete(ref key, indexSessionBroker); - public void Insert(ref TKey key, SecondaryIndexSessionBroker indexSessionBroker) + public void Insert(ref TKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) => BaseInsert(ref key, indexSessionBroker); - public void Upsert(ref TKey key, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) + public void Upsert(ref TKey key, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) => BaseUpsert(ref key, isMutableRecord, indexSessionBroker); + + public void OnPrimaryCheckpoint(int version, long flushedUntilAddress) { } + + public void OnPrimaryRecover(int version, long flushedUntilAddress, out int recoveredToVersion, out long recoveredToAddress) + { + recoveredToVersion = default; + recoveredToAddress = default; + } + + public void OnPrimaryTruncate(long newBeginAddress) { } } readonly PrimaryFasterKV store = new PrimaryFasterKV(); diff --git a/cs/test/SimpleValueIndexTests.cs b/cs/test/SimpleValueIndexTests.cs index 1b1fb7cb7..b285a1502 100644 --- a/cs/test/SimpleValueIndexTests.cs +++ b/cs/test/SimpleValueIndexTests.cs @@ -9,18 +9,28 @@ namespace FASTER.test.SubsetIndex.SimpleIndexTests { class SimpleValueIndexTests { - class SimpleValueIndex : SimpleValueIndexBase, ISecondaryValueIndex + class SimpleValueIndex : SimpleValueIndexBase, ISecondaryValueIndex { internal SimpleValueIndex(string name, Func indexKeyFunc, bool isMutableIndex) : base(name, indexKeyFunc, isMutableIndex: isMutableIndex) { } - public void Delete(RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Delete(ref TKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) => BaseDelete(recordId, indexSessionBroker); - public void Insert(ref TValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + public void Insert(ref TKey key, ref TValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) => BaseInsert(ref value, recordId, indexSessionBroker); - public void Upsert(ref TValue value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) + public void Upsert(ref TKey key, ref TValue value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker) => BaseUpsert(ref value, recordId, isMutableRecord, indexSessionBroker); + + public void OnPrimaryCheckpoint(int version, long flushedUntilAddress) { } + + public void OnPrimaryRecover(int version, long flushedUntilAddress, out int recoveredToVersion, out long recoveredToAddress) + { + recoveredToVersion = default; + recoveredToAddress = default; + } + + public void OnPrimaryTruncate(long newBeginAddress) { } } private const int valueDivisor = 50; @@ -33,7 +43,7 @@ public void Upsert(ref TValue value, RecordId recordId, bool isMutableRecord, Se public void TearDown() => store.TearDown(); private ISecondaryIndex CreateIndex(bool isMutable, bool isAsync, Func indexKeyFunc) - => new SimpleValueIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc, isMutable); + => new SimpleValueIndex($"{TestContext.CurrentContext.Test.Name}_mutable_{(isAsync ? "async" : "sync")}", indexKeyFunc, isMutable); [Test] [Category("FasterKV"), Category("Index")] From 101aff65d927cb3541d9d2656dc403c054393996 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Mon, 14 Jun 2021 22:25:40 -0700 Subject: [PATCH 32/37] allow deltaFileDevice == null --- cs/src/core/Index/Common/Contexts.cs | 19 ++++++++++--------- cs/test/ReadAddressTests.cs | 8 +++----- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/cs/src/core/Index/Common/Contexts.cs b/cs/src/core/Index/Common/Contexts.cs index d997aa577..300b4608d 100644 --- a/cs/src/core/Index/Common/Contexts.cs +++ b/cs/src/core/Index/Common/Contexts.cs @@ -526,17 +526,18 @@ public void Initialize(Guid token, int _version, ICheckpointManager checkpointMa public void Recover(Guid token, ICheckpointManager checkpointManager, int deltaLogPageSizeBits) { deltaFileDevice = checkpointManager.GetDeltaLogDevice(token); - deltaFileDevice.Initialize(-1); - if (deltaFileDevice.GetFileSize(0) > 0) + if (!(deltaFileDevice is null)) { - deltaLog = new DeltaLog(deltaFileDevice, deltaLogPageSizeBits, -1); - deltaLog.InitializeForReads(); - info.Recover(token, checkpointManager, deltaLog); - } - else - { - info.Recover(token, checkpointManager, null); + deltaFileDevice.Initialize(-1); + if (deltaFileDevice.GetFileSize(0) > 0) + { + deltaLog = new DeltaLog(deltaFileDevice, deltaLogPageSizeBits, -1); + deltaLog.InitializeForReads(); + info.Recover(token, checkpointManager, deltaLog); + return; + } } + info.Recover(token, checkpointManager, null); } public void Reset() diff --git a/cs/test/ReadAddressTests.cs b/cs/test/ReadAddressTests.cs index e00aae107..f7b42a7af 100644 --- a/cs/test/ReadAddressTests.cs +++ b/cs/test/ReadAddressTests.cs @@ -429,14 +429,13 @@ public async Task ReadAtAddressAsyncTests(bool useReadCache, CopyReadsToTail cop } } - // Test is similar to others but tests the Overload where RadFlag.none is set -- probably don't need all combinations of test but doesn't hurt + // Test is similar to others but tests the Overload where ReadFlag.none is set -- probably don't need all combinations of test but doesn't hurt [TestCase(false, CopyReadsToTail.None, false, false)] [TestCase(false, CopyReadsToTail.FromStorage, true, true)] [TestCase(true, CopyReadsToTail.None, false, true)] [Category("FasterKV")] public async Task ReadAtAddressAsyncReadFlagsNoneTests(bool useReadCache, CopyReadsToTail copyReadsToTail, bool useRMW, bool flush) { - CancellationToken cancellationToken; using var testStore = new TestStore(useReadCache, copyReadsToTail, flush); await testStore.Populate(useRMW, useAsync: true); using var session = testStore.fkv.For(new Functions()).NewSession(); @@ -463,7 +462,7 @@ public async Task ReadAtAddressAsyncReadFlagsNoneTests(bool useReadCache, CopyRe var saveOutput = output; var saveRecordInfo = recordInfo; - readAsyncResult = await session.ReadAtAddressAsync(readAtAddress, ref input, ReadFlags.None, default, serialNo: maxLap + 1, cancellationToken); + readAsyncResult = await session.ReadAtAddressAsync(readAtAddress, ref input, ReadFlags.None, default, serialNo: maxLap + 1); (status, output) = readAsyncResult.Complete(out recordInfo); Assert.AreEqual(saveOutput, output); @@ -480,7 +479,6 @@ public async Task ReadAtAddressAsyncReadFlagsNoneTests(bool useReadCache, CopyRe [Category("FasterKV")] public async Task ReadAtAddressAsyncReadFlagsSkipCacheTests(bool useReadCache, CopyReadsToTail copyReadsToTail, bool useRMW, bool flush) { - CancellationToken cancellationToken; using var testStore = new TestStore(useReadCache, copyReadsToTail, flush); await testStore.Populate(useRMW, useAsync: true); using var session = testStore.fkv.For(new Functions()).NewSession(); @@ -507,7 +505,7 @@ public async Task ReadAtAddressAsyncReadFlagsSkipCacheTests(bool useReadCache, C var saveOutput = output; var saveRecordInfo = recordInfo; - readAsyncResult = await session.ReadAtAddressAsync(readAtAddress, ref input, ReadFlags.SkipReadCache, default, maxLap + 1, cancellationToken); + readAsyncResult = await session.ReadAtAddressAsync(readAtAddress, ref input, ReadFlags.SkipReadCache, default, maxLap + 1); (status, output) = readAsyncResult.Complete(out recordInfo); Assert.AreEqual(saveOutput, output); From 8a321d9c430f8d0a561b009cf7ae52449ca52e3f Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Mon, 14 Jun 2021 23:26:17 -0700 Subject: [PATCH 33/37] Additional changes for C#9 --- cs/samples/ReadAddress/Types.cs | 2 +- .../ClientSession/AdvancedClientSession.cs | 6 ++--- cs/src/core/ClientSession/ClientSession.cs | 6 ++--- cs/src/core/Index/Common/Contexts.cs | 2 +- .../SecondaryIndex/SecondaryIndexBroker.cs | 24 +++++++++---------- .../SecondaryIndexSessionBroker.cs | 4 ++-- cs/test/SimpleIndexBase.cs | 4 ++-- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cs/samples/ReadAddress/Types.cs b/cs/samples/ReadAddress/Types.cs index 59ccc644f..fa746ef1a 100644 --- a/cs/samples/ReadAddress/Types.cs +++ b/cs/samples/ReadAddress/Types.cs @@ -50,7 +50,7 @@ public class Functions : AdvancedSimpleFunctions // Track the recordInfo for its PreviousAddress. public override void ReadCompletionCallback(ref Key key, ref Value input, ref Value output, Context ctx, Status status, RecordInfo recordInfo) { - if (ctx is { }) + if (ctx is not null) { ctx.recordInfo = recordInfo; ctx.status = status; diff --git a/cs/src/core/ClientSession/AdvancedClientSession.cs b/cs/src/core/ClientSession/AdvancedClientSession.cs index f8bafeebc..348c21ca9 100644 --- a/cs/src/core/ClientSession/AdvancedClientSession.cs +++ b/cs/src/core/ClientSession/AdvancedClientSession.cs @@ -39,7 +39,7 @@ public sealed class AdvancedClientSession>.NotAsyncSessionErr; @@ -70,7 +70,7 @@ internal AdvancedClientSession( } else { - if (!(fht.hlog is VariableLengthBlittableAllocator)) + if (fht.hlog is not VariableLengthBlittableAllocator) Debug.WriteLine("Warning: Session param of variableLengthStruct provided for non-varlen allocator"); } @@ -103,7 +103,7 @@ internal AdvancedClientSession( private void UpdateVarlen(ref IVariableLengthStruct variableLengthStruct) { - if (!(fht.hlog is VariableLengthBlittableAllocator)) + if (fht.hlog is not VariableLengthBlittableAllocator) return; if (typeof(Value) == typeof(SpanByte) && typeof(Input) == typeof(SpanByte)) diff --git a/cs/src/core/ClientSession/ClientSession.cs b/cs/src/core/ClientSession/ClientSession.cs index 62b07633e..103b318ad 100644 --- a/cs/src/core/ClientSession/ClientSession.cs +++ b/cs/src/core/ClientSession/ClientSession.cs @@ -39,7 +39,7 @@ public sealed class ClientSession internal readonly InternalFasterSession FasterSession; - internal readonly SecondaryIndexSessionBroker SecondaryIndexSessionBroker = new SecondaryIndexSessionBroker(); + internal readonly SecondaryIndexSessionBroker SecondaryIndexSessionBroker = new(); internal const string NotAsyncSessionErr = "Session does not support async operations"; @@ -70,7 +70,7 @@ internal ClientSession( } else { - if (!(fht.hlog is VariableLengthBlittableAllocator)) + if (fht.hlog is not VariableLengthBlittableAllocator) Debug.WriteLine("Warning: Session param of variableLengthStruct provided for non-varlen allocator"); } @@ -103,7 +103,7 @@ internal ClientSession( private void UpdateVarlen(ref IVariableLengthStruct variableLengthStruct) { - if (!(fht.hlog is VariableLengthBlittableAllocator)) + if (fht.hlog is not VariableLengthBlittableAllocator) return; if (typeof(Value) == typeof(SpanByte) && typeof(Input) == typeof(SpanByte)) diff --git a/cs/src/core/Index/Common/Contexts.cs b/cs/src/core/Index/Common/Contexts.cs index 300b4608d..28518da11 100644 --- a/cs/src/core/Index/Common/Contexts.cs +++ b/cs/src/core/Index/Common/Contexts.cs @@ -526,7 +526,7 @@ public void Initialize(Guid token, int _version, ICheckpointManager checkpointMa public void Recover(Guid token, ICheckpointManager checkpointManager, int deltaLogPageSizeBits) { deltaFileDevice = checkpointManager.GetDeltaLogDevice(token); - if (!(deltaFileDevice is null)) + if (deltaFileDevice is not null) { deltaFileDevice.Initialize(-1); if (deltaFileDevice.GetFileSize(0) > 0) diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs index c30091a80..751dee917 100644 --- a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs +++ b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs @@ -44,7 +44,7 @@ public void AddIndex(ISecondaryIndex index) void AppendToArray(ref TIndex[] vec, TIndex idx) { var resizedVec = new TIndex[vec is null ? 1 : vec.Length + 1]; - if (vec is { }) + if (vec is not null) Array.Copy(vec, resizedVec, vec.Length); resizedVec[resizedVec.Length - 1] = idx; vec = resizedVec; @@ -177,7 +177,7 @@ void ReleaseIter(IFasterScanIterator localIter) // TODO: Parallelize ScanReadOnlyPages var ki = this.allKeyIndexes; - if (ki is { }) + if (ki is not null) { foreach (var keyIndex in ki) { @@ -187,7 +187,7 @@ void ReleaseIter(IFasterScanIterator localIter) } } var vi = this.allValueIndexes; - if (vi is { }) + if (vi is not null) { foreach (var valueIndex in vi) { @@ -201,13 +201,13 @@ void ReleaseIter(IFasterScanIterator localIter) internal void OnPrimaryCheckpointInitiated(PrimaryCheckpointInfo currentPci) { var ki = this.allKeyIndexes; - if (ki is { }) + if (ki is not null) { foreach (var keyIndex in ki) keyIndex.OnPrimaryCheckpointInitiated(currentPci); } var vi = this.allValueIndexes; - if (vi is { }) + if (vi is not null) { foreach (var valueIndex in vi) valueIndex.OnPrimaryCheckpointInitiated(currentPci); @@ -217,13 +217,13 @@ internal void OnPrimaryCheckpointInitiated(PrimaryCheckpointInfo currentPci) internal void OnPrimaryCheckpointCompleted(PrimaryCheckpointInfo completedPci) { var ki = this.allKeyIndexes; - if (ki is { }) + if (ki is not null) { foreach (var keyIndex in ki) keyIndex.OnPrimaryCheckpointCompleted(completedPci); } var vi = this.allValueIndexes; - if (vi is { }) + if (vi is not null) { foreach (var valueIndex in vi) valueIndex.OnPrimaryCheckpointCompleted(completedPci); @@ -239,13 +239,13 @@ internal void Recover(PrimaryCheckpointInfo primaryRecoveredPci, bool undoNextVe var tasks = new List(); var ki = this.allKeyIndexes; - if (ki is { }) + if (ki is not null) { foreach (var keyIndex in ki) tasks.Add(Task.Run(() => RecoverIndex(keyIndex, default, primaryRecoveredPci, undoNextVersion, indexSessionBroker))); } var vi = this.allValueIndexes; - if (vi is { }) + if (vi is not null) { foreach (var valueIndex in vi) tasks.Add(Task.Run(() => RecoverIndex(default, valueIndex, primaryRecoveredPci, undoNextVersion, indexSessionBroker))); @@ -263,13 +263,13 @@ internal async Task RecoverAsync(PrimaryCheckpointInfo primaryRecoveredPci, bool var tasks = new List(); var ki = this.allKeyIndexes; - if (ki is { }) + if (ki is not null) { foreach (var keyIndex in ki) tasks.Add(RecoverIndexAsync(keyIndex, default, primaryRecoveredPci, undoNextVersion, indexSessionBroker)); } var vi = this.allValueIndexes; - if (vi is { }) + if (vi is not null) { foreach (var valueIndex in vi) tasks.Add(RecoverIndexAsync(default, valueIndex, primaryRecoveredPci, undoNextVersion, indexSessionBroker)); @@ -299,7 +299,7 @@ private void RollIndexForward(ISecondaryKeyIndex keyIndex, ISecondaryVal { var startAddress = Math.Max(pci.FlushedUntilAddress, primaryFkv.Log.BeginAddress); using var iter = primaryFkv.Log.Scan(startAddress, endAddress); - if (keyIndex is { }) + if (keyIndex is not null) keyIndex.RecoveryReplay(iter, indexSessionBroker); else valueIndex.RecoveryReplay(iter, indexSessionBroker); diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs index c006f9703..fb96db930 100644 --- a/cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs +++ b/cs/src/core/SecondaryIndex/SecondaryIndexSessionBroker.cs @@ -56,11 +56,11 @@ public object SetSessionObject(long slot, object sessionObject) public void Dispose() { var sessions = this.indexSessions; - if (sessions == null) + if (sessions is null) return; sessions = Interlocked.CompareExchange(ref this.indexSessions, null, sessions); - if (sessions == null) + if (sessions is null) return; foreach (var session in sessions) diff --git a/cs/test/SimpleIndexBase.cs b/cs/test/SimpleIndexBase.cs index 1d71401bf..8e3ce6111 100644 --- a/cs/test/SimpleIndexBase.cs +++ b/cs/test/SimpleIndexBase.cs @@ -42,9 +42,9 @@ protected void VerifySession(SecondaryIndexSessionBroker indexSessionBroker, boo if (isMutableRecord) Assert.AreEqual(sessionObject is null, this.sessionId == Guid.Empty); - if (!(sessionObject is SimpleIndexSession session)) + if (sessionObject is not SimpleIndexSession session) { - if (sessionObject is { }) + if (sessionObject is not null) Assert.Fail($"Unexpected session object type {sessionObject.GetType().Name} for {indexType}"); if (this.sessionId == Guid.Empty) From d380af898aa8c09ee4c57284fe17108485811b4f Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Wed, 21 Jul 2021 12:09:54 -0700 Subject: [PATCH 34/37] Backport a few changes from HVI in preparation to merge master --- cs/src/core/Async/ReadAsync.cs | 2 +- cs/src/core/Index/FASTER/FASTER.cs | 6 +++++- cs/src/core/Index/FASTER/FASTERImpl.cs | 2 +- cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cs/src/core/Async/ReadAsync.cs b/cs/src/core/Async/ReadAsync.cs index 9df14986e..a9bade329 100644 --- a/cs/src/core/Async/ReadAsync.cs +++ b/cs/src/core/Async/ReadAsync.cs @@ -189,7 +189,7 @@ internal ValueTask> ReadAsync> SlowReadAsync( + internal static async ValueTask> SlowReadAsync( FasterKV @this, IFasterSession fasterSession, FasterExecutionContext currentCtx, diff --git a/cs/src/core/Index/FASTER/FASTER.cs b/cs/src/core/Index/FASTER/FASTER.cs index a6e05188d..512debe26 100644 --- a/cs/src/core/Index/FASTER/FASTER.cs +++ b/cs/src/core/Index/FASTER/FASTER.cs @@ -30,7 +30,11 @@ public partial class FasterKV : FasterBase, { internal readonly AllocatorBase hlog; private readonly AllocatorBase readcache; - private readonly IFasterEqualityComparer comparer; + + /// + /// Compares two keys + /// + protected readonly IFasterEqualityComparer comparer; internal readonly bool UseReadCache; private readonly CopyReadsToTail CopyReadsToTail; diff --git a/cs/src/core/Index/FASTER/FASTERImpl.cs b/cs/src/core/Index/FASTER/FASTERImpl.cs index 8f4883c58..471e47660 100644 --- a/cs/src/core/Index/FASTER/FASTERImpl.cs +++ b/cs/src/core/Index/FASTER/FASTERImpl.cs @@ -188,7 +188,7 @@ internal OperationStatus InternalRead( if (!pendingContext.recordInfo.Tombstone) { fasterSession.SingleReader(ref key, ref input, ref hlog.GetValue(physicalAddress), ref output, logicalAddress); - if (CopyReadsToTail == CopyReadsToTail.FromReadOnly && !pendingContext.SkipReadCache) + if (CopyReadsToTail == CopyReadsToTail.FromReadOnly && !pendingContext.SkipCopyReadsToTail) { var container = hlog.GetValueContainer(ref hlog.GetValue(physicalAddress)); InternalUpsert(ref key, ref container.Get(), ref userContext, ref pendingContext, fasterSession, sessionCtx, lsn); diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs index 751dee917..2fb8c246b 100644 --- a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs +++ b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs @@ -26,7 +26,7 @@ public class SecondaryIndexBroker private ISecondaryValueIndex[] mutableValueIndexes = Array.Empty>(); internal int MutableValueIndexCount => mutableValueIndexes.Length; - readonly object membershipLock = new object(); + readonly object membershipLock = new(); readonly FasterKV primaryFkv; IDisposable logSubscribeDisposable; // Used if we implement index removal, if we go to zero indexes; Dispose() and null this then. From 7917538c4ebbe00c0ca4eb7f487f0ada4e886424 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Fri, 24 Sep 2021 00:17:01 -0700 Subject: [PATCH 35/37] add missing OnRecovery to InternalRecoverAsync --- cs/src/core/Index/Recovery/Recovery.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cs/src/core/Index/Recovery/Recovery.cs b/cs/src/core/Index/Recovery/Recovery.cs index 47a52d536..9cf447e10 100644 --- a/cs/src/core/Index/Recovery/Recovery.cs +++ b/cs/src/core/Index/Recovery/Recovery.cs @@ -304,6 +304,7 @@ private void InternalRecover(IndexCheckpointInfo recoveredICInfo, HybridLogCheck // Recover session information hlog.RecoveryReset(tailAddress, headAddress, recoveredHLCInfo.info.beginAddress, readOnlyAddress); _recoveredSessions = recoveredHLCInfo.info.continueTokens; + checkpointManager.OnRecovery(recoveredICInfo.info.token, recoveredHLCInfo.info.guid); recoveredHLCInfo.Dispose(); } @@ -348,6 +349,7 @@ await RecoverHybridLogFromSnapshotFileAsync(recoveredHLCInfo.info.flushedLogical hlog.RecoveryReset(tailAddress, headAddress, recoveredHLCInfo.info.beginAddress, readOnlyAddress); _recoveredSessions = recoveredHLCInfo.info.continueTokens; + checkpointManager.OnRecovery(recoveredICInfo.info.token, recoveredHLCInfo.info.guid); recoveredHLCInfo.Dispose(); } From 25f310fcbf8ad825acff220a9e46fabebf3be999 Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Fri, 24 Sep 2021 13:54:25 -0700 Subject: [PATCH 36/37] Add default values for scanDelta, recoverTo args --- cs/src/core/Index/Common/Contexts.cs | 2 +- cs/src/core/Index/Recovery/ICheckpointManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cs/src/core/Index/Common/Contexts.cs b/cs/src/core/Index/Common/Contexts.cs index 249ccfb4c..47b8df441 100644 --- a/cs/src/core/Index/Common/Contexts.cs +++ b/cs/src/core/Index/Common/Contexts.cs @@ -594,7 +594,7 @@ public HybridLogCheckpointInfo Transfer() } public void Recover(Guid token, ICheckpointManager checkpointManager, int deltaLogPageSizeBits, - bool scanDelta, long recoverTo) + bool scanDelta = false, long recoverTo = -1) { deltaFileDevice = checkpointManager.GetDeltaLogDevice(token); if (deltaFileDevice is not null) diff --git a/cs/src/core/Index/Recovery/ICheckpointManager.cs b/cs/src/core/Index/Recovery/ICheckpointManager.cs index a2ccfc506..291e73123 100644 --- a/cs/src/core/Index/Recovery/ICheckpointManager.cs +++ b/cs/src/core/Index/Recovery/ICheckpointManager.cs @@ -82,7 +82,7 @@ public interface ICheckpointManager : IDisposable /// whether or not to scan through the delta log to acquire latest entry /// version upper bound to scan for in the delta log. Function will return the largest version metadata no greater than the given version. /// Metadata, or null if invalid - byte[] GetLogCheckpointMetadata(Guid logToken, DeltaLog deltaLog, bool scanDelta, long recoverTo); + byte[] GetLogCheckpointMetadata(Guid logToken, DeltaLog deltaLog, bool scanDelta = false, long recoverTo = -1); /// /// Get list of index checkpoint tokens, in order of usage preference From c4b0781af3d9eefee892569ab8f417619b78a09e Mon Sep 17 00:00:00 2001 From: TedHartMS <15467143+TedHartMS@users.noreply.github.com> Date: Wed, 29 Sep 2021 10:07:09 -0700 Subject: [PATCH 37/37] backport mostly doc-related changes from HVI --- cs/src/core/SecondaryIndex/ISecondaryIndex.cs | 117 ------------------ .../core/SecondaryIndex/ISecondaryKeyIndex.cs | 64 ++++++++++ .../SecondaryIndex/ISecondaryValueIndex.cs | 66 ++++++++++ cs/src/core/SecondaryIndex/QuerySegment.cs | 8 +- .../SecondaryIndex/SecondaryIndexBroker.cs | 28 +++-- cs/test/SimpleRecoveryTest.cs | 2 +- 6 files changed, 156 insertions(+), 129 deletions(-) create mode 100644 cs/src/core/SecondaryIndex/ISecondaryKeyIndex.cs create mode 100644 cs/src/core/SecondaryIndex/ISecondaryValueIndex.cs diff --git a/cs/src/core/SecondaryIndex/ISecondaryIndex.cs b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs index 157f0d8ad..730c60e6f 100644 --- a/cs/src/core/SecondaryIndex/ISecondaryIndex.cs +++ b/cs/src/core/SecondaryIndex/ISecondaryIndex.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; using System.Threading; using System.Threading.Tasks; @@ -70,120 +69,4 @@ public interface ISecondaryIndex /// void OnPrimaryTruncate(long newBeginAddress); } - - /// - /// Interface for a FASTER SecondaryIndex that is derived from the FasterKV Key generic parameter. - /// - public interface ISecondaryKeyIndex : ISecondaryIndex - { - /// - /// Inserts a key into the secondary index. Called only for mutable indexes, on the initial insert of a Key. KeyIndexes do not take RecordIds - /// because they reflect the current value of the primary FasterKV Key. - /// - /// The key to be inserted; always mutable - /// The identifier of the record containing the ; may be used to generate a list - /// The for the primary FasterKV session making this call - /// - /// If the index is mutable and the is already there, this call should be ignored, because it is the result - /// of a race in which the record in the primary FasterKV was updated after the initial insert but before this method - /// was called. - /// - void Insert(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); - - /// - /// Upserts a key into the secondary index. This may be called either immediately during a FasterKV operation, or during recovery. - /// - /// The key to be upserted - /// The identifier of the record containing the - /// Whether the recordId was in the mutable region of FASTER. If true, the record may subsequently be Upserted or Deleted. - /// The for the primary FasterKV session making this call - /// - /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. - /// In this case, is false, and the index may move the to an immutable storage area. - /// - void Upsert(ref TKVKey key, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); - - /// - /// Removes a key from the secondary index. Called only for mutable indexes. - /// - /// The key to be removed - /// The identifier of the record to be removed for the - /// The for the primary FasterKV session making this call - void Delete(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); - - /// - /// Scans a range from a page that has gone readonly. - /// - /// The iterator over the region that is going readonly - /// The for the primary FasterKV session making this call - /// The value type for the - void ScanReadOnlyPages(IFasterScanIterator iter, SecondaryIndexSessionBroker indexSessionBroker); - - /// - /// Scans a range of pages after or has completed. - /// - /// The iterator over the records past the extent of recovery that are being replayed - /// The for the primary FasterKV session making this call - /// The value type for the - void RecoveryReplay(IFasterScanIterator iter, SecondaryIndexSessionBroker indexSessionBroker); - } - - /// - /// Interface for a FASTER SecondaryIndex that is derived from the FasterKV Value generic parameter. - /// - public interface ISecondaryValueIndex : ISecondaryIndex - { - /// - /// Inserts a recordId into the secondary index, with the associated value from which the index derives its key(s). - /// Called only for mutable indexes, on the initial insert of a Key. - /// - /// The key for the being inserted - /// The value to be inserted; always mutable - /// The identifier of the record containing the - /// The for the primary FasterKV session making this call - /// - /// If the index is mutable and the is already there for this , - /// this call should be ignored, because it is the result of a race in which the record in the primary FasterKV was - /// updated after the initial insert but before this method was called, so the on this call - /// would overwrite it with an obsolete value. - /// - void Insert(ref TKVKey key, ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); - - /// - /// Upserts a recordId into the secondary index, with the associated value from which the index derives its key(s). - /// This may be called either immediately during a FasterKV operation, or during recovery. - /// - /// The key for the being upserted - /// The value to be upserted - /// The identifier of the record containing the - /// Whether the recordId was in the mutable region of FASTER; if so, it may subsequently be Upserted or Deleted. - /// The for the primary FasterKV session making this call - /// - /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. - /// In this case, is false, and the index may move the to an immutable storage area. - /// - void Upsert(ref TKVKey key, ref TKVValue value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); - - /// - /// Removes a recordId from the secondary index. Called only for mutable indexes. - /// - /// The key for the being deleted - /// The recordId to be removed - /// The for the primary FasterKV session making this call - void Delete(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); - - /// - /// Scans a range from a page that has gone readonly. - /// - /// The iterator over the region that is going readonly - /// The for the primary FasterKV session making this call - void ScanReadOnlyPages(IFasterScanIterator iter, SecondaryIndexSessionBroker indexSessionBroker); - - /// - /// Scans a range of pages after or has completed. - /// - /// The iterator over the records past the extent of recovery that are being replayed - /// The for the primary FasterKV session making this call - void RecoveryReplay(IFasterScanIterator iter, SecondaryIndexSessionBroker indexSessionBroker); - } } \ No newline at end of file diff --git a/cs/src/core/SecondaryIndex/ISecondaryKeyIndex.cs b/cs/src/core/SecondaryIndex/ISecondaryKeyIndex.cs new file mode 100644 index 000000000..5533cf4df --- /dev/null +++ b/cs/src/core/SecondaryIndex/ISecondaryKeyIndex.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Threading; + +namespace FASTER.core +{ + /// + /// Interface for a FASTER SecondaryIndex that is derived from the FasterKV Key generic parameter. + /// + public interface ISecondaryKeyIndex : ISecondaryIndex + { + /// + /// Inserts a key into the secondary index. Called only for mutable indexes, on the initial insert of a Key. KeyIndexes do not take RecordIds + /// because they reflect the current value of the primary FasterKV Key. + /// + /// The key to be inserted; always mutable + /// The identifier of the record containing the ; may be used to generate a list + /// The for the primary FasterKV session making this call + /// + /// If the index is mutable and the is already there, this call should be ignored, because it is the result + /// of a race in which the record in the primary FasterKV was updated after the initial insert but before this method + /// was called. + /// + void Insert(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); + + /// + /// Upserts a key into the secondary index. This may be called either immediately during a FasterKV operation, or during recovery. + /// + /// The key to be upserted + /// The identifier of the record containing the + /// Whether the recordId was in the mutable region of FASTER. If true, the record may subsequently be Upserted or Deleted. + /// The for the primary FasterKV session making this call + /// + /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. + /// In this case, is false, and the index may move the to an immutable storage area. + /// + void Upsert(ref TKVKey key, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); + + /// + /// Removes a key from the secondary index. Called only for mutable indexes. + /// + /// The key to be removed + /// The identifier of the record to be removed for the + /// The for the primary FasterKV session making this call + void Delete(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); + + /// + /// Scans a range from a page that has gone readonly. + /// + /// The iterator over the region that is going readonly + /// The for the primary FasterKV session making this call + /// The value type for the + void ScanReadOnlyPages(IFasterScanIterator iter, SecondaryIndexSessionBroker indexSessionBroker); + + /// + /// Scans a range of pages after or has completed. + /// + /// The iterator over the records past the extent of recovery that are being replayed + /// The for the primary FasterKV session making this call + /// The value type for the + void RecoveryReplay(IFasterScanIterator iter, SecondaryIndexSessionBroker indexSessionBroker); + } +} \ No newline at end of file diff --git a/cs/src/core/SecondaryIndex/ISecondaryValueIndex.cs b/cs/src/core/SecondaryIndex/ISecondaryValueIndex.cs new file mode 100644 index 000000000..8e5cb2c99 --- /dev/null +++ b/cs/src/core/SecondaryIndex/ISecondaryValueIndex.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Threading; + +namespace FASTER.core +{ + /// + /// Interface for a FASTER SecondaryIndex that is derived from the FasterKV Value generic parameter. + /// + public interface ISecondaryValueIndex : ISecondaryIndex + { + /// + /// Inserts a recordId into the secondary index, with the associated value from which the index derives its key(s). + /// Called only for mutable indexes, on the initial insert of a Key. + /// + /// The key for the being inserted + /// The value to be inserted; always mutable + /// The identifier of the record containing the + /// The for the primary FasterKV session making this call + /// + /// If the index is mutable and the is already there for this , + /// this call should be ignored, because it is the result of a race in which the record in the primary FasterKV was + /// updated after the initial insert but before this method was called, so the on this call + /// would overwrite it with an obsolete value. + /// + void Insert(ref TKVKey key, ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); + + /// + /// Upserts a recordId into the secondary index, with the associated value from which the index derives its key(s). + /// This may be called either immediately during a FasterKV operation, or during recovery. + /// + /// The key for the being upserted + /// The value to be upserted + /// The identifier of the record containing the + /// Whether the recordId was in the mutable region of FASTER; if so, it may subsequently be Upserted or Deleted. + /// The for the primary FasterKV session making this call + /// + /// For an immutable index, this is the only call made on the interface, when the page containing the has moved to ReadOnly. + /// In this case, is false, and the index may move the to an immutable storage area. + /// + void Upsert(ref TKVKey key, ref TKVValue value, RecordId recordId, bool isMutableRecord, SecondaryIndexSessionBroker indexSessionBroker); + + /// + /// Removes a recordId from the secondary index. Called only for mutable indexes. + /// + /// The key for the being deleted + /// The recordId to be removed + /// The for the primary FasterKV session making this call + void Delete(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker); + + /// + /// Scans a range from a page that has gone readonly. + /// + /// The iterator over the region that is going readonly + /// The for the primary FasterKV session making this call + void ScanReadOnlyPages(IFasterScanIterator iter, SecondaryIndexSessionBroker indexSessionBroker); + + /// + /// Scans a range of pages after or has completed. + /// + /// The iterator over the records past the extent of recovery that are being replayed + /// The for the primary FasterKV session making this call + void RecoveryReplay(IFasterScanIterator iter, SecondaryIndexSessionBroker indexSessionBroker); + } +} \ No newline at end of file diff --git a/cs/src/core/SecondaryIndex/QuerySegment.cs b/cs/src/core/SecondaryIndex/QuerySegment.cs index 0acaacf19..6e7308ff5 100644 --- a/cs/src/core/SecondaryIndex/QuerySegment.cs +++ b/cs/src/core/SecondaryIndex/QuerySegment.cs @@ -24,12 +24,18 @@ public class QuerySegment : IEnumerable public string ContinuationToken { get; } + /// + /// Indicates whether the query has returned all records. + /// + public bool IsQueryComplete { get; } + /// /// Constructor /// - public QuerySegment(List> results, string continuationToken) + public QuerySegment(List> results, bool isComplete, string continuationToken) { this.Results = results; + this.IsQueryComplete = isComplete; this.ContinuationToken = continuationToken; } diff --git a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs index 2fb8c246b..103c8e860 100644 --- a/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs +++ b/cs/src/core/SecondaryIndex/SecondaryIndexBroker.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -31,6 +30,9 @@ public class SecondaryIndexBroker readonly FasterKV primaryFkv; IDisposable logSubscribeDisposable; // Used if we implement index removal, if we go to zero indexes; Dispose() and null this then. + // Rather than compare to null, use this flag. + internal bool IsSecondaryFkv = false; + internal SecondaryIndexBroker(FasterKV pFkv) => this.primaryFkv = pFkv; /// @@ -80,12 +82,12 @@ bool addSpecific(ref TIndex[] allVec, ref TIndex[] mutableVec, TIndex id /// /// The number of indexes registered. /// - public int Count => allKeyIndexes.Length + allValueIndexes.Length; + internal int Count => (allKeyIndexes?.Length ?? 0) + (allValueIndexes?.Length ?? 0); /// /// The number of indexes registered. /// - public bool HasMutableIndexes { get; private set; } + internal bool HasMutableIndexes { get; private set; } // On failure of an operation, a SecondaryIndexException is thrown by the Index @@ -94,7 +96,7 @@ bool addSpecific(ref TIndex[] allVec, ref TIndex[] mutableVec, TIndex id /// Inserts a mutable key into all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Insert(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + internal void Insert(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mki = this.mutableKeyIndexes; foreach (var keyIndex in mki) @@ -105,7 +107,7 @@ public void Insert(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroke /// Upserts a mutable key into all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Upsert(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + internal void Upsert(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mki = this.mutableKeyIndexes; foreach (var keyIndex in mki) @@ -118,7 +120,7 @@ public void Upsert(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroke /// Inserts a recordId keyed by a mutable value into all mutable secondary value indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Insert(ref TKVKey key, ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + internal void Insert(ref TKVKey key, ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mvi = this.mutableValueIndexes; foreach (var valueIndex in mvi) @@ -129,7 +131,7 @@ public void Insert(ref TKVKey key, ref TKVValue value, RecordId recordId, Second /// Upserts a recordId keyed by a mutable value into all mutable secondary value indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Upsert(ref TKVKey key, ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + internal void Upsert(ref TKVKey key, ref TKVValue value, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mvi = this.mutableValueIndexes; foreach (var valueIndex in mvi) @@ -143,7 +145,7 @@ public void Upsert(ref TKVKey key, ref TKVValue value, RecordId recordId, Second /// Deletes recordId for a key from all mutable secondary key indexes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Delete(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) + internal void Delete(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroker indexSessionBroker) { var mki = this.mutableKeyIndexes; foreach (var keyIndex in mki) @@ -160,6 +162,7 @@ public void Delete(ref TKVKey key, RecordId recordId, SecondaryIndexSessionBroke /// internal void ScanReadOnlyPages(IFasterScanIterator iter, SecondaryIndexSessionBroker indexSessionBroker) { + // Use 'iter' for the first index; open others only if we have more than one index. var inputIter = iter; IFasterScanIterator GetIter() @@ -175,7 +178,6 @@ void ReleaseIter(IFasterScanIterator localIter) localIter.Dispose(); } - // TODO: Parallelize ScanReadOnlyPages var ki = this.allKeyIndexes; if (ki is not null) { @@ -232,7 +234,10 @@ internal void OnPrimaryCheckpointCompleted(PrimaryCheckpointInfo completedPci) internal void Recover(PrimaryCheckpointInfo primaryRecoveredPci, bool undoNextVersion) { - // This is called during recovery, before the PrimaryFKV is open for operations, so we do not have to worry about things changing + if (this.IsSecondaryFkv) + return; + + // This is called during recovery, before the PrimaryFKV is open for operations, so we do not have to worry about things changing. // We're not operating in the context of a FasterKV session, so we need our own sessionBroker. using var indexSessionBroker = new SecondaryIndexSessionBroker(); @@ -256,6 +261,9 @@ internal void Recover(PrimaryCheckpointInfo primaryRecoveredPci, bool undoNextVe internal async Task RecoverAsync(PrimaryCheckpointInfo primaryRecoveredPci, bool undoNextVersion) { + if (this.IsSecondaryFkv) + return; + // This is called during recovery, before the PrimaryFKV is open for operations, so we do not have to worry about things changing // We're not operating in the context of a FasterKV session, so we need our own sessionBroker. using var indexSessionBroker = new SecondaryIndexSessionBroker(); diff --git a/cs/test/SimpleRecoveryTest.cs b/cs/test/SimpleRecoveryTest.cs index 277ad785f..1ae2cd2c8 100644 --- a/cs/test/SimpleRecoveryTest.cs +++ b/cs/test/SimpleRecoveryTest.cs @@ -72,7 +72,7 @@ public async ValueTask LocalDeviceSimpleRecoveryTest([Values] CheckpointType che { checkpointManager = new DeviceLogCommitCheckpointManager( new LocalStorageNamedDeviceFactory(), - new DefaultCheckpointNamingScheme(TestUtils.MethodTestDir)); + new DefaultCheckpointNamingScheme($"{TestUtils.MethodTestDir}/chkpt")); await SimpleRecoveryTest1_Worker(checkpointType, isAsync, testCommitCookie); checkpointManager.PurgeAll(); }