diff options
Diffstat (limited to 'lib/VNLib.Data.Caching.ObjectCache/src')
9 files changed, 242 insertions, 51 deletions
diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs index 440981a..f77587b 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs @@ -23,8 +23,8 @@ */ using System; -using System.Collections.Generic; using System.Diagnostics; +using System.Collections.Generic; using VNLib.Utils.Memory; using VNLib.Utils.Memory.Caching; @@ -35,9 +35,10 @@ namespace VNLib.Data.Caching.ObjectCache /// <summary> /// A general purpose binary data storage /// </summary> - public sealed class BlobCache : LRUCache<string, CacheEntry>, IBlobCache + public sealed class BlobCache : LRUCache<string, CacheEntry>, IBlobCache, IMemoryCacheEntryFactory { private bool disposedValue; + private IPersistantCacheStore? _persistance; ///<inheritdoc/> public override bool IsReadOnly { get; } @@ -48,21 +49,29 @@ namespace VNLib.Data.Caching.ObjectCache ///<inheritdoc/> public IUnmangedHeap CacheHeap { get; } + ///<inheritdoc/> + public uint BucketId { get; } /// <summary> /// Initializes a new <see cref="BlobCache"/> store /// </summary> + /// <param name="bucketId">The id of the bucket that manages this instance</param> /// <param name="maxCapacity">The maximum number of items to keep in memory</param> /// <param name="heap">The unmanaged heap used to allocate cache entry buffers from</param> + /// <param name="store">The optional backing persistant cache storage</param> /// <exception cref="ArgumentException"></exception> - public BlobCache(int maxCapacity, IUnmangedHeap heap) - :base(StringComparer.Ordinal) + public BlobCache(uint bucketId, int maxCapacity, IUnmangedHeap heap, IPersistantCacheStore? store) + :base(maxCapacity, StringComparer.Ordinal) { if(maxCapacity < 1) { throw new ArgumentException("The maxium capacity of the store must be a positive integer larger than 0", nameof(maxCapacity)); } + BucketId = bucketId; + + _persistance = store; + CacheHeap = heap; MaxCapacity = maxCapacity; @@ -74,19 +83,32 @@ namespace VNLib.Data.Caching.ObjectCache ///<inheritdoc/> protected override bool CacheMiss(string key, out CacheEntry value) { - value = default; - return false; + if(_persistance == null) + { + value = default; + return false; + } + //Use the persistant cache + return _persistance.OnCacheMiss(BucketId, key, this, out value); } ///<inheritdoc/> protected override void Evicted(ref KeyValuePair<string, CacheEntry> evicted) { - //Dispose the cache item - evicted.Value.Dispose(); + try + { + //Call persistance store record eviction + _persistance?.OnEntryEvicted(BucketId, evicted.Key, evicted.Value); + } + finally + { + //Dispose the cache item + evicted.Value.Dispose(); + } } ///<inheritdoc/> - public bool TryChangeKey(string objectId, string newId, out CacheEntry blob) + public bool TryChangeKey(string objectId, string newId, out CacheEntry entry) { //Try to get the node at the current key if (LookupTable.Remove(objectId, out LinkedListNode<KeyValuePair<string, CacheEntry>> ? node)) @@ -95,10 +117,10 @@ namespace VNLib.Data.Caching.ObjectCache List.Remove(node); //Get the stored blob - blob = node.ValueRef.Value; + entry = node.ValueRef.Value; //Update the - node.Value = new KeyValuePair<string, CacheEntry>(newId, blob); + node.Value = new KeyValuePair<string, CacheEntry>(newId, entry); //Add to end of list List.AddLast(node); @@ -109,13 +131,16 @@ namespace VNLib.Data.Caching.ObjectCache return true; } - blob = default; + entry = default; return false; } ///<inheritdoc/> public override bool Remove(string key) { + //Remove from persistant store also + _persistance?.OnEntryDeleted(BucketId, key); + //Remove the item from the lookup table and if it exists, remove the node from the list if (!LookupTable.Remove(key, out LinkedListNode<KeyValuePair<string, CacheEntry>>? node)) { @@ -192,5 +217,13 @@ namespace VNLib.Data.Caching.ObjectCache Dispose(disposing: true); GC.SuppressFinalize(this); } + + + ///<inheritdoc/> + CacheEntry IMemoryCacheEntryFactory.CreateEntry(ReadOnlySpan<byte> entryData) + { + //Create entry from the internal heap + return CacheEntry.Create(entryData, CacheHeap); + } } } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs index f79db3f..6af1a20 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs @@ -29,11 +29,18 @@ using VNLib.Utils.Memory; namespace VNLib.Data.Caching.ObjectCache { + + /// <summary> + /// A concrete implementation of an <see cref="IBlobCacheBucket"/> + /// </summary> public sealed class BlobCacheBucket : IBlobCacheBucket { private readonly IBlobCache _cacheTable; private readonly SemaphoreSlim _lock; + ///<inheritdoc/> + public uint Id { get; } + /// <summary> /// Initialzies a new <see cref="BlobCacheBucket"/> and its underlying /// <see cref="IBlobCache"/> @@ -42,11 +49,14 @@ namespace VNLib.Data.Caching.ObjectCache /// The maxium number of entries allowed in the LRU cache /// before LRU overflow happens. /// </param> + /// <param name="bucketId">The unique id of the new bucket</param> /// <param name="heap">The heap to allocate object cache buffers</param> - public BlobCacheBucket(int bucketCapacity, IUnmangedHeap heap) + /// <param name="persistantCache">An optional <see cref="IPersistantCacheStore"/> for cache persistance</param> + public BlobCacheBucket(uint bucketId, int bucketCapacity, IUnmangedHeap heap, IPersistantCacheStore? persistantCache) { + Id = bucketId; _lock = new(1, 1); - _cacheTable = new BlobCache(bucketCapacity, heap); + _cacheTable = new BlobCache(bucketId, bucketCapacity, heap, persistantCache); } ///<inheritdoc/> diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs index 818dfcf..f69c2a4 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Data.Caching.ObjectCache -* File: BlobCacheLIstener.cs +* File: BlobCacheListener.cs * -* BlobCacheLIstener.cs is part of VNLib.Data.Caching.ObjectCache which is part of the larger +* BlobCacheListener.cs is part of VNLib.Data.Caching.ObjectCache which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Data.Caching.ObjectCache is free software: you can redistribute it and/or modify @@ -53,9 +53,9 @@ namespace VNLib.Data.Caching.ObjectCache public delegate ReadOnlySpan<byte> GetBodyDataCallback<T>(T state); /// <summary> - /// A <see cref="FBMListener"/> implementation of a <see cref="CacheListener"/> + /// An <see cref="FBMListener"/> for key-value object data caching servers. /// </summary> - public class BlobCacheLIstener : FBMListenerBase, IDisposable + public class BlobCacheListener : FBMListenerBase, IDisposable { private bool disposedValue; @@ -74,21 +74,22 @@ namespace VNLib.Data.Caching.ObjectCache /// <summary> - /// Initialzies a new <see cref="BlobCacheLIstener"/> + /// Initialzies a new <see cref="BlobCacheListener"/> /// </summary> - /// <param name="cacheMax">The maxium number of items per bucket</param> - /// <param name="buckets">The number of cache store buckets</param> - /// <param name="log"></param> + /// <param name="cache">The cache table to work from</param> + /// <param name="log">Writes error and debug logging information</param> /// <param name="heap">The heap to alloc FBM buffers and <see cref="CacheEntry"/> cache buffers from</param> /// <param name="singleReader">A value that indicates if a single thread is processing events</param> - public BlobCacheLIstener(uint buckets, uint cacheMax, ILogProvider log, IUnmangedHeap heap, bool singleReader) + /// <exception cref="ArgumentNullException"></exception> + public BlobCacheListener(IBlobCacheTable cache, ILogProvider log, IUnmangedHeap heap, bool singleReader) { Log = log; + Cache = cache ?? throw new ArgumentNullException(nameof(cache)); + //Writes may happen from multple threads with bucket design and no lock EventQueue = new(false, singleReader); - - Cache = new BlobCacheTable(buckets, cacheMax, heap); + InitListener(heap); } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs index 270cf1e..f3f1b50 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs @@ -39,6 +39,7 @@ namespace VNLib.Data.Caching.ObjectCache { private readonly uint _tableSize; private readonly IBlobCacheBucket[] _buckets; + private readonly IPersistantCacheStore? _persistant; /// <summary> /// Initializes a new <see cref="BlobCacheTable"/> @@ -46,9 +47,10 @@ namespace VNLib.Data.Caching.ObjectCache /// <param name="bucketSize">The number of elements in each bucket</param> /// <param name="tableSize">The number of buckets within the table</param> /// <param name="heap">The heap used to allocate cache entry buffers from</param> + /// <param name="persistantCache">An optional <see cref="IPersistantCacheStore"/> for persistant cache implementations</param> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentException"></exception> - public BlobCacheTable(uint tableSize, uint bucketSize, IUnmangedHeap heap) + public BlobCacheTable(uint tableSize, uint bucketSize, IUnmangedHeap heap, IPersistantCacheStore? persistantCache) { _ = heap ?? throw new ArgumentNullException(nameof(heap)); @@ -61,16 +63,18 @@ namespace VNLib.Data.Caching.ObjectCache _tableSize = tableSize; _buckets = new IBlobCacheBucket[tableSize]; + _persistant = persistantCache; + //Init buckets - InitBuckets(tableSize, bucketSize, _buckets, heap); + InitBuckets(tableSize, bucketSize, _buckets, heap, persistantCache); } - private static void InitBuckets(uint size, uint bucketSize, IBlobCacheBucket[] table, IUnmangedHeap heap) + private static void InitBuckets(uint size, uint bucketSize, IBlobCacheBucket[] table, IUnmangedHeap heap, IPersistantCacheStore? persistantCache) { - for(int i = 0; i < size; i++) + for(uint i = 0; i < size; i++) { - table[i] = new BlobCacheBucket((int)bucketSize, heap); + table[i] = new BlobCacheBucket(i, (int)bucketSize, heap, persistantCache); } } @@ -118,8 +122,12 @@ namespace VNLib.Data.Caching.ObjectCache ///<inheritdoc/> protected sealed override void Free() { - //Dispose buckets - Array.ForEach(_buckets, static b => b.Dispose()); + //Dispose persistance store + using (_persistant) + { + //Dispose buckets + Array.ForEach(_buckets, static b => b.Dispose()); + } } ///<inheritdoc/> diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs b/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs index 3d61790..e778b30 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.ObjectCache @@ -23,19 +23,21 @@ */ using System; +using System.Buffers; using System.Buffers.Binary; using System.Runtime.CompilerServices; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; + namespace VNLib.Data.Caching { /// <summary> /// A structure that represents an item in cache. It contains the binary content /// of a cache entry by its internal memory handle /// </summary> - public readonly struct CacheEntry : IDisposable, IEquatable<CacheEntry> + public readonly record struct CacheEntry : IDisposable { private const int TIME_SEGMENT_SIZE = sizeof(long); @@ -53,7 +55,7 @@ namespace VNLib.Data.Caching /// </summary> /// <param name="data">The initial data to store</param> /// <param name="heap">The heap to allocate the buffer from</param> - /// <returns>The new <see cref="CacheEntry"/></returns> + /// <returns>The newly initialized and ready to use <see cref="CacheEntry"/></returns> public static CacheEntry Create(ReadOnlySpan<byte> data, IUnmangedHeap heap) { //Calc buffer size @@ -74,6 +76,7 @@ namespace VNLib.Data.Caching return entry; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetRequiredHandleSize(int size) { @@ -202,27 +205,31 @@ namespace VNLib.Data.Caching Span<byte> segment = GetDataSegment(); #if DEBUG - //Test segment length is equvalent to the requested data length + //Test segment length is equivalent to the requested data length System.Diagnostics.Debug.Assert(segment.Length == data.Length); #endif //Copy data segment data.CopyTo(segment); } - - ///<inheritdoc/> - public override bool Equals(object? obj) => obj is CacheEntry entry && Equals(entry); - ///<inheritdoc/> public override int GetHashCode() => _handle.GetHashCode(); - ///<inheritdoc/> - public static bool operator ==(CacheEntry left, CacheEntry right) => left.Equals(right); - - ///<inheritdoc/> - public static bool operator !=(CacheEntry left, CacheEntry right) => !(left == right); - - ///<inheritdoc/> - public bool Equals(CacheEntry other) => other.GetHashCode() == GetHashCode(); + /// <summary> + /// Gets a <see cref="MemoryHandle"/> offset to the start of the + /// internal data segment, and avoids calling the fixed keyword. + /// The handle must be disposed/released to avoid memeory leaks. + /// </summary> + /// <remarks> + /// WARNING: You must respect the <see cref="GetLength"/> return value so + /// as no to overrun the valid data segment. + /// </remarks> + /// <returns>A handle that points to the begining of the data segment</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly MemoryHandle UnsafeGetDataSegmentHandle() + { + //Get the handle offset to the data segment start, the caller must know when the data segment ends + return _handle.Pin(DATA_SEGMENT_START); + } } } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs index 52d53ff..bc3180b 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs @@ -35,6 +35,11 @@ namespace VNLib.Data.Caching.ObjectCache public interface IBlobCache : IEnumerable<KeyValuePair<string, CacheEntry>>, IDisposable { /// <summary> + /// The id of the bucket this memory cache belongs to + /// </summary> + public uint BucketId { get; } + + /// <summary> /// The internal heap used to allocate <see cref="CacheEntry"/> buffers /// </summary> IUnmangedHeap CacheHeap { get; } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs index 4876c5f..dbe095c 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs @@ -3,10 +3,10 @@ * * Library: VNLib * Package: VNLib.Data.Caching.ObjectCache -* File: ObjectCacheStore.cs +* File: IBlobCacheBucket.cs * -* ObjectCacheStore.cs is part of VNLib.Data.Caching.ObjectCache which is part of the larger -* VNLib collection of libraries and utilities. +* IBlobCacheBucket.cs is part of VNLib.Data.Caching.ObjectCache which +* is part of the larger VNLib collection of libraries and utilities. * * VNLib.Data.Caching.ObjectCache is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -35,6 +35,11 @@ namespace VNLib.Data.Caching.ObjectCache public interface IBlobCacheBucket : IDisposable { /// <summary> + /// The unique integer id of a bucket within an <see cref="IBlobCacheTable"/> + /// </summary> + uint Id { get; } + + /// <summary> /// Gets a <see cref="CacheBucketHandle"/> that holds an exclusive lock /// for the current bucekt and holds a referrence to the stored /// <see cref="IBlobCache"/> diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs new file mode 100644 index 0000000..1454fc0 --- /dev/null +++ b/lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Data.Caching.ObjectCache +* File: IMemoryCacheEntryFactory.cs +* +* IMemoryCacheEntryFactory.cs is part of VNLib.Data.Caching.ObjectCache which +* is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Data.Caching.ObjectCache is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* VNLib.Data.Caching.ObjectCache is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Data.Caching.ObjectCache +{ + /// <summary> + /// A factory abstraction that builds <see cref="CacheEntry"/> structures + /// linked to internally configured memory implementations, for cache + /// promotions. + /// </summary> + public interface IMemoryCacheEntryFactory + { + /// <summary> + /// Creates and initalizes a new <see cref="CacheEntry"/> from the desired object data + /// </summary> + /// <param name="entryData">The non-owned memory to copy into the the new <see cref="CacheEntry"/></param> + /// <returns>The newly initalized <see cref="CacheEntry"/></returns> + CacheEntry CreateEntry(ReadOnlySpan<byte> entryData); + } +} diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs new file mode 100644 index 0000000..40f39f2 --- /dev/null +++ b/lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs @@ -0,0 +1,79 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Data.Caching.ObjectCache +* File: IPersistantCacheStore.cs +* +* IPersistantCacheStore.cs is part of VNLib.Data.Caching.ObjectCache which +* is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Data.Caching.ObjectCache is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* VNLib.Data.Caching.ObjectCache is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Data.Caching.ObjectCache +{ + /// <summary> + /// Provides a persitance layer to memory caching. + /// </summary> + public interface IPersistantCacheStore : IDisposable + { + /// <summary> + /// Invoked when an entry has been evicted from main-memory cache + /// and is expected to be stored in a "persistant" storage solution. + /// <para> + /// When this method returns, the <paramref name="entry"/> is no longer valid. + /// </para> + /// <para> + /// This method is called while the bucket lock is held. This call is maded + /// during an <see cref="IBlobCache.Add(string, CacheEntry)"/> method call. + /// </para> + /// </summary> + /// <param name="bucketId">The id of the bucket requesting the operation</param> + /// <param name="key">The key identifying the the entry</param> + /// <param name="entry">The entry containing the object data to store</param> + void OnEntryEvicted(uint bucketId, string key, in CacheEntry entry); + + /// <summary> + /// Called when a cache item does not exist in main memory cache and should + /// be promoted from persistant cache to main memory cache. + /// <para> + /// This method is called while the bucket lock is held. This call is maded + /// during an <see cref="IBlobCache.Add(string, CacheEntry)"/> method call. + /// </para> + /// <para> + /// The <see cref="IMemoryCacheEntryFactory"/> should be used to create the + /// cache entry for the return value. Once this method returns, the caller owns the new <see cref="CacheEntry"/> + /// </para> + /// </summary> + /// <param name="key">The key identifying the entry to promot</param> + /// <param name="factory">The cache entry factory</param> + /// <param name="bucketId">The id of the bucket requesting the operation</param> + /// <param name="entry">The newly created entry when data is found</param> + /// <returns> + /// A value inidcating if the entry was successfully recovered from the persistant storage and + /// was successfully promoted. + /// </returns> + bool OnCacheMiss(uint bucketId, string key, IMemoryCacheEntryFactory factory, out CacheEntry entry); + + /// <summary> + /// Removes an entry from the backing store + /// </summary> + /// <param name="key">The key identifying the entry to remove</param> + /// <param name="bucketId">The id of the bucket requesting the operation</param> + void OnEntryDeleted(uint bucketId, string key); + } +} |