From d06ab4d78294bd31974282a591b2d355d67dcb63 Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Tue, 21 Oct 2025 14:27:00 +0200 Subject: [PATCH 01/17] added trashDetector --- HzMemoryCache/HzMemoryCache.cs | 57 ++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 6ee70f8..21b762c 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -1,4 +1,8 @@ #nullable enable + +using HzCache.Diagnostics; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; using System; using System.Collections; using System.Collections.Concurrent; @@ -10,8 +14,6 @@ using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; -using HzCache.Diagnostics; -using Microsoft.Extensions.Logging; namespace HzCache { @@ -27,6 +29,8 @@ public partial class HzMemoryCache : IEnumerable>, private readonly ConcurrentDictionary dictionary = new(); private readonly HzCacheMemoryLocker memoryLocker = new(new HzCacheMemoryLockerOptions()); private readonly HzCacheOptions options; + private readonly MemoryCache trashDetectorCache = new(new MemoryCacheOptions()); + private readonly ILogger? logger; //IDispisable members private bool _disposedValue; @@ -35,8 +39,9 @@ public partial class HzMemoryCache : IEnumerable>, /// Initializes a new empty instance of /// /// Options for the cache - public HzMemoryCache(HzCacheOptions? options = null) + public HzMemoryCache(HzCacheOptions? options = null, ILogger? logger = null) { + this.logger = logger; this.options = options ?? new HzCacheOptions(); cleanUpTimer = new Timer(s => { _ = ProcessExpiredEviction(); }, null, this.options.cleanupJobInterval, this.options.cleanupJobInterval); StartUpdateChecksumAndNotify(); @@ -244,7 +249,7 @@ public bool Remove(string key, bool sendBackplaneNotification, Func @@ -292,7 +297,7 @@ public static IPropagatorBlock> CreateBuffer(TimeSpan timeS private void StartUpdateChecksumAndNotify() { - var options = new DataflowLinkOptions {PropagateCompletion = true}; + var options = new DataflowLinkOptions { PropagateCompletion = true }; var actionBlock = new ActionBlock>(ttlValues => { try @@ -325,6 +330,8 @@ private bool RemoveItem(string key, CacheItemChangeType changeType, bool sendNot if (result) { + DetectCacheTrashing(key, ttlValue?.checksum); + result = dictionary.TryRemove(key, out ttlValue); if (result) { @@ -340,6 +347,38 @@ private bool RemoveItem(string key, CacheItemChangeType changeType, bool sendNot return result; } + //Remember the value we are removing from the local cache, if the same value is being removed again and again in a short time frame, we are likely experiencing cache trashing. + private void DetectCacheTrashing(string key, string ttlValueChecksum) + { + if (ttlValueChecksum == null || logger == null) + return; + + TrashDetector trashDetector; + if (trashDetectorCache.TryGetValue(key, out trashDetector)) + { + trashDetectorCache.Set(key, new TrashDetector + { + Checksum = ttlValueChecksum, + Counter = 0 + }, TimeSpan.FromSeconds(60)); + return; + } + + if (trashDetector.Checksum == ttlValueChecksum) + { + trashDetector.Counter++; + } + else + { + trashDetectorCache.Remove(key); + } + + if (trashDetector.Counter == 5) + { + logger.LogWarning($"Cache Trashing Detected: {key} has been removed from local cache 5 times last 60s. Checksum of existing value:{ttlValueChecksum}", key, ttlValueChecksum); + } + } + private void NotifyItemChange(string key, CacheItemChangeType changeType, TTLValue ttlValue, byte[]? objectData = null, bool isPattern = false) { options.valueChangeListener(key, changeType, ttlValue, objectData, isPattern); @@ -405,4 +444,10 @@ public static bool IsNullOrDefault(T argument) return false; } } -} + + public class TrashDetector + { + public string Checksum { get; set; } + public int Counter { get; set; } + } +} \ No newline at end of file From 0e2cd3b9b143092945a77cb79899ba595d2484db Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Tue, 21 Oct 2025 14:49:15 +0200 Subject: [PATCH 02/17] removed logger as parameter. --- HzMemoryCache/HzMemoryCache.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 21b762c..5b133a6 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -30,7 +30,6 @@ public partial class HzMemoryCache : IEnumerable>, private readonly HzCacheMemoryLocker memoryLocker = new(new HzCacheMemoryLockerOptions()); private readonly HzCacheOptions options; private readonly MemoryCache trashDetectorCache = new(new MemoryCacheOptions()); - private readonly ILogger? logger; //IDispisable members private bool _disposedValue; @@ -39,9 +38,8 @@ public partial class HzMemoryCache : IEnumerable>, /// Initializes a new empty instance of /// /// Options for the cache - public HzMemoryCache(HzCacheOptions? options = null, ILogger? logger = null) + public HzMemoryCache(HzCacheOptions? options = null) { - this.logger = logger; this.options = options ?? new HzCacheOptions(); cleanUpTimer = new Timer(s => { _ = ProcessExpiredEviction(); }, null, this.options.cleanupJobInterval, this.options.cleanupJobInterval); StartUpdateChecksumAndNotify(); @@ -350,7 +348,7 @@ private bool RemoveItem(string key, CacheItemChangeType changeType, bool sendNot //Remember the value we are removing from the local cache, if the same value is being removed again and again in a short time frame, we are likely experiencing cache trashing. private void DetectCacheTrashing(string key, string ttlValueChecksum) { - if (ttlValueChecksum == null || logger == null) + if (ttlValueChecksum == null || options.logger == null) return; TrashDetector trashDetector; @@ -375,7 +373,7 @@ private void DetectCacheTrashing(string key, string ttlValueChecksum) if (trashDetector.Counter == 5) { - logger.LogWarning($"Cache Trashing Detected: {key} has been removed from local cache 5 times last 60s. Checksum of existing value:{ttlValueChecksum}", key, ttlValueChecksum); + options.logger.LogWarning($"Cache Trashing Detected: {key} has been removed from local cache 5 times last 60s. Checksum of existing value:{ttlValueChecksum}", key, ttlValueChecksum); } } From 462ea3a75a8efad744f05eb78322bc68c801aec1 Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Tue, 21 Oct 2025 15:28:52 +0200 Subject: [PATCH 03/17] added logging to test --- HzMemoryCache/HzMemoryCache.cs | 54 +++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 5b133a6..91887db 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -348,32 +348,44 @@ private bool RemoveItem(string key, CacheItemChangeType changeType, bool sendNot //Remember the value we are removing from the local cache, if the same value is being removed again and again in a short time frame, we are likely experiencing cache trashing. private void DetectCacheTrashing(string key, string ttlValueChecksum) { - if (ttlValueChecksum == null || options.logger == null) - return; - - TrashDetector trashDetector; - if (trashDetectorCache.TryGetValue(key, out trashDetector)) + try { - trashDetectorCache.Set(key, new TrashDetector + if (ttlValueChecksum == null || options.logger == null) + return; + options.logger.LogWarning($"Test DetectCacheTrashing {key}:{ttlValueChecksum}"); + TrashDetector trashDetector; + if (trashDetectorCache.TryGetValue(key, out trashDetector)) { - Checksum = ttlValueChecksum, - Counter = 0 - }, TimeSpan.FromSeconds(60)); - return; - } + trashDetectorCache.Set(key, new TrashDetector + { + Checksum = ttlValueChecksum, + Counter = 0 + }, TimeSpan.FromSeconds(60)); + options.logger.LogWarning($"Set {key}"); + return; + } - if (trashDetector.Checksum == ttlValueChecksum) - { - trashDetector.Counter++; - } - else - { - trashDetectorCache.Remove(key); - } + if (trashDetector.Checksum == ttlValueChecksum) + { + options.logger.LogWarning($"Add {key}:{trashDetector.Counter}"); + trashDetector.Counter++; + } + else + { + options.logger.LogWarning($"Remove {key}"); + trashDetectorCache.Remove(key); + } - if (trashDetector.Counter == 5) + if (trashDetector.Counter == 5) + { + options.logger.LogWarning( + $"Cache Trashing Detected: {key} has been removed from local cache 5 times last 60s. Checksum of existing value:{ttlValueChecksum}", + key, ttlValueChecksum); + } + } + catch (Exception e) { - options.logger.LogWarning($"Cache Trashing Detected: {key} has been removed from local cache 5 times last 60s. Checksum of existing value:{ttlValueChecksum}", key, ttlValueChecksum); + options.logger?.LogError(e, $"Error in DetectCacheTrashing {key}", key); } } From 58049a8acc26ac3a81502ace295e2aa2e6eb827d Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Tue, 21 Oct 2025 15:32:54 +0200 Subject: [PATCH 04/17] fixed bug --- HzMemoryCache/HzMemoryCache.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 91887db..c62af76 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -352,31 +352,27 @@ private void DetectCacheTrashing(string key, string ttlValueChecksum) { if (ttlValueChecksum == null || options.logger == null) return; - options.logger.LogWarning($"Test DetectCacheTrashing {key}:{ttlValueChecksum}"); TrashDetector trashDetector; - if (trashDetectorCache.TryGetValue(key, out trashDetector)) + if (!trashDetectorCache.TryGetValue(key, out trashDetector)) { trashDetectorCache.Set(key, new TrashDetector { Checksum = ttlValueChecksum, Counter = 0 }, TimeSpan.FromSeconds(60)); - options.logger.LogWarning($"Set {key}"); return; } if (trashDetector.Checksum == ttlValueChecksum) { - options.logger.LogWarning($"Add {key}:{trashDetector.Counter}"); trashDetector.Counter++; } else { - options.logger.LogWarning($"Remove {key}"); trashDetectorCache.Remove(key); } - if (trashDetector.Counter == 5) + if (trashDetector.Counter > 3) { options.logger.LogWarning( $"Cache Trashing Detected: {key} has been removed from local cache 5 times last 60s. Checksum of existing value:{ttlValueChecksum}", From 4513f6a0293d6052c6804d5ebc3716465baa2cc7 Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Tue, 21 Oct 2025 16:11:04 +0200 Subject: [PATCH 05/17] tidy --- HzMemoryCache/HzMemoryCache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index c62af76..97877a6 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -372,10 +372,10 @@ private void DetectCacheTrashing(string key, string ttlValueChecksum) trashDetectorCache.Remove(key); } - if (trashDetector.Counter > 3) + if (trashDetector.Counter == 3) { options.logger.LogWarning( - $"Cache Trashing Detected: {key} has been removed from local cache 5 times last 60s. Checksum of existing value:{ttlValueChecksum}", + $"Cache Trashing Detected: {key} has been removed from local cache 3 times last 60s. Checksum of existing value:{ttlValueChecksum}", key, ttlValueChecksum); } } From 20ae4334eea0c0a533e3626115469f6642707f09 Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Wed, 22 Oct 2025 10:20:18 +0200 Subject: [PATCH 06/17] fixed pr comments --- HzMemoryCache/Diagnostics/TrashingDetector.cs | 8 +++++ HzMemoryCache/HzMemoryCache.cs | 35 ++++++++++--------- HzMemoryCache/IHzCache.cs | 8 +++-- 3 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 HzMemoryCache/Diagnostics/TrashingDetector.cs diff --git a/HzMemoryCache/Diagnostics/TrashingDetector.cs b/HzMemoryCache/Diagnostics/TrashingDetector.cs new file mode 100644 index 0000000..be9141a --- /dev/null +++ b/HzMemoryCache/Diagnostics/TrashingDetector.cs @@ -0,0 +1,8 @@ +namespace HzCache.Diagnostics +{ + internal class TrashingDetector + { + public string Checksum { get; set; } + public int Counter { get; set; } + } +} \ No newline at end of file diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 97877a6..6dd65aa 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -3,6 +3,7 @@ using HzCache.Diagnostics; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using System; using System.Collections; using System.Collections.Concurrent; @@ -29,7 +30,7 @@ public partial class HzMemoryCache : IEnumerable>, private readonly ConcurrentDictionary dictionary = new(); private readonly HzCacheMemoryLocker memoryLocker = new(new HzCacheMemoryLockerOptions()); private readonly HzCacheOptions options; - private readonly MemoryCache trashDetectorCache = new(new MemoryCacheOptions()); + private readonly MemoryCache trashingDetectorCache = new(new MemoryCacheOptions()); //IDispisable members private bool _disposedValue; @@ -350,38 +351,43 @@ private void DetectCacheTrashing(string key, string ttlValueChecksum) { try { + if (!options.LogCacheTrashing) + { + return; + } + if (ttlValueChecksum == null || options.logger == null) return; - TrashDetector trashDetector; - if (!trashDetectorCache.TryGetValue(key, out trashDetector)) + TrashingDetector? trashingDetector; + if (!trashingDetectorCache.TryGetValue(key, out trashingDetector)) { - trashDetectorCache.Set(key, new TrashDetector + trashingDetectorCache.Set(key, new TrashingDetector { Checksum = ttlValueChecksum, Counter = 0 - }, TimeSpan.FromSeconds(60)); + }, options.TrashingWindow); return; } - if (trashDetector.Checksum == ttlValueChecksum) + if (trashingDetector.Checksum == ttlValueChecksum) { - trashDetector.Counter++; + trashingDetector.Counter++; } else { - trashDetectorCache.Remove(key); + trashingDetectorCache.Remove(key); } - if (trashDetector.Counter == 3) + if (trashingDetector.Counter >= options.TrashingLimit) { options.logger.LogWarning( - $"Cache Trashing Detected: {key} has been removed from local cache 3 times last 60s. Checksum of existing value:{ttlValueChecksum}", + "Cache Trashing Detected: {Key} has been removed from local cache 3 times last 60s. Checksum of existing value:{Checksum}", key, ttlValueChecksum); } } catch (Exception e) { - options.logger?.LogError(e, $"Error in DetectCacheTrashing {key}", key); + options.logger?.LogError(e, "Error in DetectCacheTrashing {Key}", key); } } @@ -402,6 +408,7 @@ private void Dispose(bool disposing) if (disposing) { cleanUpTimer.Dispose(); + trashingDetectorCache.Dispose(); } _disposedValue = true; @@ -450,10 +457,4 @@ public static bool IsNullOrDefault(T argument) return false; } } - - public class TrashDetector - { - public string Checksum { get; set; } - public int Counter { get; set; } - } } \ No newline at end of file diff --git a/HzMemoryCache/IHzCache.cs b/HzMemoryCache/IHzCache.cs index 85f6389..05503bf 100644 --- a/HzMemoryCache/IHzCache.cs +++ b/HzMemoryCache/IHzCache.cs @@ -1,4 +1,5 @@ #nullable enable + using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -29,7 +30,6 @@ public enum NotificationType Async, Sync, None } - /// /// The eviction policy to use for the cache. /// LRU is "Least Recently Used" and FIFO is "First In First Out". Which is almost true. @@ -92,6 +92,10 @@ public class HzCacheOptions /// benefit of compression. /// public long compressionThreshold { get; set; } = Int64.MaxValue; + + public bool LogCacheTrashing { get; set; } = false; + public TimeSpan TrashingWindow { get; set; } = TimeSpan.FromSeconds(60); + public int TrashingLimit { get; set; } = 3; } public interface IHzCache @@ -182,6 +186,7 @@ public interface IHzCache bool Remove(string key); Task ClearAsync(); + Task RemoveAsync(string key); } @@ -198,7 +203,6 @@ public interface IDetailedHzCache : IHzCache /// void Clear(); - /// /// Tries to remove item with the specified key, also returns the object removed in an "out" var /// From 8f4c7ab3e0f74a3f1330c6e6e1099e108f269813 Mon Sep 17 00:00:00 2001 From: Joel Nygren <127515376+JoelNygren-Norce@users.noreply.github.com> Date: Wed, 22 Oct 2025 10:24:59 +0200 Subject: [PATCH 07/17] Update HzMemoryCache/IHzCache.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- HzMemoryCache/IHzCache.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/HzMemoryCache/IHzCache.cs b/HzMemoryCache/IHzCache.cs index 05503bf..97a6d68 100644 --- a/HzMemoryCache/IHzCache.cs +++ b/HzMemoryCache/IHzCache.cs @@ -93,8 +93,19 @@ public class HzCacheOptions /// public long compressionThreshold { get; set; } = Int64.MaxValue; + /// + /// Enables logging of cache trashing events, which occur when keys are removed frequently. + /// public bool LogCacheTrashing { get; set; } = false; + + /// + /// The time window during which cache removals are counted for trashing detection. + /// public TimeSpan TrashingWindow { get; set; } = TimeSpan.FromSeconds(60); + + /// + /// The number of removals within that triggers a cache trashing warning. + /// public int TrashingLimit { get; set; } = 3; } From 8feee29458fea7a1c427a9caf8b2233069572016 Mon Sep 17 00:00:00 2001 From: Joel Nygren <127515376+JoelNygren-Norce@users.noreply.github.com> Date: Wed, 22 Oct 2025 10:25:28 +0200 Subject: [PATCH 08/17] Update HzMemoryCache/HzMemoryCache.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- HzMemoryCache/HzMemoryCache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 6dd65aa..6304b52 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -381,8 +381,8 @@ private void DetectCacheTrashing(string key, string ttlValueChecksum) if (trashingDetector.Counter >= options.TrashingLimit) { options.logger.LogWarning( - "Cache Trashing Detected: {Key} has been removed from local cache 3 times last 60s. Checksum of existing value:{Checksum}", - key, ttlValueChecksum); + "Cache Trashing Detected: {Key} has been removed from local cache {TrashingLimit} times in the last {TrashingWindow}s. Checksum of existing value:{Checksum}", + key, options.TrashingLimit, options.TrashingWindow.TotalSeconds, ttlValueChecksum); } } catch (Exception e) From c4e62d1e69548dda7d8538d751ef97c854148cea Mon Sep 17 00:00:00 2001 From: Joel Nygren <127515376+JoelNygren-Norce@users.noreply.github.com> Date: Wed, 22 Oct 2025 10:26:23 +0200 Subject: [PATCH 09/17] Update HzMemoryCache/Diagnostics/TrashingDetector.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- HzMemoryCache/Diagnostics/TrashingDetector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HzMemoryCache/Diagnostics/TrashingDetector.cs b/HzMemoryCache/Diagnostics/TrashingDetector.cs index be9141a..2c9076a 100644 --- a/HzMemoryCache/Diagnostics/TrashingDetector.cs +++ b/HzMemoryCache/Diagnostics/TrashingDetector.cs @@ -2,7 +2,7 @@ namespace HzCache.Diagnostics { internal class TrashingDetector { - public string Checksum { get; set; } - public int Counter { get; set; } + public string? Checksum { get; set; } = null; + public int Counter { get; set; } = 0; } } \ No newline at end of file From b22cb06f552e5c7af82fc2d571d329b9ba503d9e Mon Sep 17 00:00:00 2001 From: Joel Nygren <127515376+JoelNygren-Norce@users.noreply.github.com> Date: Wed, 22 Oct 2025 10:26:29 +0200 Subject: [PATCH 10/17] Update HzMemoryCache/HzMemoryCache.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- HzMemoryCache/HzMemoryCache.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 6304b52..47b2bd0 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -358,8 +358,7 @@ private void DetectCacheTrashing(string key, string ttlValueChecksum) if (ttlValueChecksum == null || options.logger == null) return; - TrashingDetector? trashingDetector; - if (!trashingDetectorCache.TryGetValue(key, out trashingDetector)) + if (!trashingDetectorCache.TryGetValue(key, out TrashingDetector? trashingDetector)) { trashingDetectorCache.Set(key, new TrashingDetector { From 6aaec0ddfcfd1af284172984c715453484b54793 Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Wed, 22 Oct 2025 10:30:38 +0200 Subject: [PATCH 11/17] tidy --- HzMemoryCache/HzMemoryCache.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 47b2bd0..4266e27 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -362,8 +362,7 @@ private void DetectCacheTrashing(string key, string ttlValueChecksum) { trashingDetectorCache.Set(key, new TrashingDetector { - Checksum = ttlValueChecksum, - Counter = 0 + Checksum = ttlValueChecksum }, options.TrashingWindow); return; } @@ -377,7 +376,7 @@ private void DetectCacheTrashing(string key, string ttlValueChecksum) trashingDetectorCache.Remove(key); } - if (trashingDetector.Counter >= options.TrashingLimit) + if (trashingDetector.Counter == options.TrashingLimit) { options.logger.LogWarning( "Cache Trashing Detected: {Key} has been removed from local cache {TrashingLimit} times in the last {TrashingWindow}s. Checksum of existing value:{Checksum}", From 86d957e24a72afd2b483d9f12c3495e2c98e545d Mon Sep 17 00:00:00 2001 From: Joel Nygren <127515376+JoelNygren-Norce@users.noreply.github.com> Date: Wed, 22 Oct 2025 10:36:28 +0200 Subject: [PATCH 12/17] Update HzMemoryCache/HzMemoryCache.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- HzMemoryCache/HzMemoryCache.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 4266e27..b6cb568 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -346,7 +346,9 @@ private bool RemoveItem(string key, CacheItemChangeType changeType, bool sendNot return result; } - //Remember the value we are removing from the local cache, if the same value is being removed again and again in a short time frame, we are likely experiencing cache trashing. + // Remember the value we are removing from the local cache. + // If the same value is being removed again and again in a short time frame, + // we are likely experiencing cache trashing. private void DetectCacheTrashing(string key, string ttlValueChecksum) { try From c337e803e0bcf3ecf003bfd3768487d234b4351a Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Wed, 22 Oct 2025 10:36:59 +0200 Subject: [PATCH 13/17] moved checksum to constructor --- HzMemoryCache/Diagnostics/TrashingDetector.cs | 4 ++-- HzMemoryCache/HzMemoryCache.cs | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/HzMemoryCache/Diagnostics/TrashingDetector.cs b/HzMemoryCache/Diagnostics/TrashingDetector.cs index 2c9076a..00e2ab2 100644 --- a/HzMemoryCache/Diagnostics/TrashingDetector.cs +++ b/HzMemoryCache/Diagnostics/TrashingDetector.cs @@ -1,8 +1,8 @@ namespace HzCache.Diagnostics { - internal class TrashingDetector + internal class TrashingDetector(string checksum) { - public string? Checksum { get; set; } = null; public int Counter { get; set; } = 0; + public string Checksum => checksum; } } \ No newline at end of file diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 4266e27..2fa6a92 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -347,7 +347,7 @@ private bool RemoveItem(string key, CacheItemChangeType changeType, bool sendNot } //Remember the value we are removing from the local cache, if the same value is being removed again and again in a short time frame, we are likely experiencing cache trashing. - private void DetectCacheTrashing(string key, string ttlValueChecksum) + private void DetectCacheTrashing(string key, string? ttlValueChecksum) { try { @@ -360,10 +360,7 @@ private void DetectCacheTrashing(string key, string ttlValueChecksum) return; if (!trashingDetectorCache.TryGetValue(key, out TrashingDetector? trashingDetector)) { - trashingDetectorCache.Set(key, new TrashingDetector - { - Checksum = ttlValueChecksum - }, options.TrashingWindow); + trashingDetectorCache.Set(key, new TrashingDetector(ttlValueChecksum), options.TrashingWindow); return; } From c8e3c0e98163722f5dd40498d45ae541665e0e35 Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Wed, 22 Oct 2025 10:44:54 +0200 Subject: [PATCH 14/17] fixed spelling --- ...ashingDetector.cs => ThrashingDetector.cs} | 2 +- HzMemoryCache/HzMemoryCache.cs | 24 +++++++++---------- HzMemoryCache/IHzCache.cs | 12 +++++----- 3 files changed, 19 insertions(+), 19 deletions(-) rename HzMemoryCache/Diagnostics/{TrashingDetector.cs => ThrashingDetector.cs} (71%) diff --git a/HzMemoryCache/Diagnostics/TrashingDetector.cs b/HzMemoryCache/Diagnostics/ThrashingDetector.cs similarity index 71% rename from HzMemoryCache/Diagnostics/TrashingDetector.cs rename to HzMemoryCache/Diagnostics/ThrashingDetector.cs index 00e2ab2..4f553db 100644 --- a/HzMemoryCache/Diagnostics/TrashingDetector.cs +++ b/HzMemoryCache/Diagnostics/ThrashingDetector.cs @@ -1,6 +1,6 @@ namespace HzCache.Diagnostics { - internal class TrashingDetector(string checksum) + internal class ThrashingDetector(string checksum) { public int Counter { get; set; } = 0; public string Checksum => checksum; diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 2fa6a92..40a1add 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -30,7 +30,7 @@ public partial class HzMemoryCache : IEnumerable>, private readonly ConcurrentDictionary dictionary = new(); private readonly HzCacheMemoryLocker memoryLocker = new(new HzCacheMemoryLockerOptions()); private readonly HzCacheOptions options; - private readonly MemoryCache trashingDetectorCache = new(new MemoryCacheOptions()); + private readonly MemoryCache thrashingDetectorCache = new(new MemoryCacheOptions()); //IDispisable members private bool _disposedValue; @@ -346,38 +346,38 @@ private bool RemoveItem(string key, CacheItemChangeType changeType, bool sendNot return result; } - //Remember the value we are removing from the local cache, if the same value is being removed again and again in a short time frame, we are likely experiencing cache trashing. + //Remember the value we are removing from the local cache, if the same value is being removed again and again in a short time frame, we are likely experiencing cache thrashing. private void DetectCacheTrashing(string key, string? ttlValueChecksum) { try { - if (!options.LogCacheTrashing) + if (!options.LogCacheThrashing) { return; } if (ttlValueChecksum == null || options.logger == null) return; - if (!trashingDetectorCache.TryGetValue(key, out TrashingDetector? trashingDetector)) + if (!thrashingDetectorCache.TryGetValue(key, out ThrashingDetector? thrashingDetector)) { - trashingDetectorCache.Set(key, new TrashingDetector(ttlValueChecksum), options.TrashingWindow); + thrashingDetectorCache.Set(key, new ThrashingDetector(ttlValueChecksum), options.ThrashingWindow); return; } - if (trashingDetector.Checksum == ttlValueChecksum) + if (thrashingDetector.Checksum == ttlValueChecksum) { - trashingDetector.Counter++; + thrashingDetector.Counter++; } else { - trashingDetectorCache.Remove(key); + thrashingDetectorCache.Remove(key); } - if (trashingDetector.Counter == options.TrashingLimit) + if (thrashingDetector.Counter == options.ThrashingLimit) { options.logger.LogWarning( - "Cache Trashing Detected: {Key} has been removed from local cache {TrashingLimit} times in the last {TrashingWindow}s. Checksum of existing value:{Checksum}", - key, options.TrashingLimit, options.TrashingWindow.TotalSeconds, ttlValueChecksum); + "Cache Thrashing Detected: {Key} has been removed from local cache {ThrashingLimit} times in the last {ThrashingWindow}s. Checksum of existing value:{Checksum}", + key, options.ThrashingLimit, options.ThrashingWindow.TotalSeconds, ttlValueChecksum); } } catch (Exception e) @@ -403,7 +403,7 @@ private void Dispose(bool disposing) if (disposing) { cleanUpTimer.Dispose(); - trashingDetectorCache.Dispose(); + thrashingDetectorCache.Dispose(); } _disposedValue = true; diff --git a/HzMemoryCache/IHzCache.cs b/HzMemoryCache/IHzCache.cs index 97a6d68..b73a675 100644 --- a/HzMemoryCache/IHzCache.cs +++ b/HzMemoryCache/IHzCache.cs @@ -94,19 +94,19 @@ public class HzCacheOptions public long compressionThreshold { get; set; } = Int64.MaxValue; /// - /// Enables logging of cache trashing events, which occur when keys are removed frequently. + /// Enables logging of cache thrashing events, which occur when keys are removed frequently. /// - public bool LogCacheTrashing { get; set; } = false; + public bool LogCacheThrashing { get; set; } = false; /// - /// The time window during which cache removals are counted for trashing detection. + /// The time window during which cache removals are counted for thrashing detection. /// - public TimeSpan TrashingWindow { get; set; } = TimeSpan.FromSeconds(60); + public TimeSpan ThrashingWindow { get; set; } = TimeSpan.FromSeconds(60); /// - /// The number of removals within that triggers a cache trashing warning. + /// The number of removals within that triggers a cache thrashing warning. /// - public int TrashingLimit { get; set; } = 3; + public int ThrashingLimit { get; set; } = 3; } public interface IHzCache From 41ac00fc2ca23b4bc990302eb684a246f122b64d Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Wed, 22 Oct 2025 10:49:06 +0200 Subject: [PATCH 15/17] fixed spelling error --- HzMemoryCache/HzMemoryCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index 8bb5cd2..a46febe 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -384,7 +384,7 @@ private void DetectCacheThrashing(string key, string? ttlValueChecksum) } catch (Exception e) { - options.logger?.LogError(e, "Error in DetectCacheTrashing {Key}", key); + options.logger?.LogError(e, "Error in DetectCacheThrashing {Key}", key); } } From 5745c11e9c65088159f83858fb73e4320522247d Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Wed, 22 Oct 2025 10:52:07 +0200 Subject: [PATCH 16/17] spelling error --- HzMemoryCache/HzMemoryCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HzMemoryCache/HzMemoryCache.cs b/HzMemoryCache/HzMemoryCache.cs index a46febe..1b92300 100644 --- a/HzMemoryCache/HzMemoryCache.cs +++ b/HzMemoryCache/HzMemoryCache.cs @@ -348,7 +348,7 @@ private bool RemoveItem(string key, CacheItemChangeType changeType, bool sendNot // Remember the value we are removing from the local cache. // If the same value is being removed again and again in a short time frame, - // we are likely experiencing cache trashing. + // we are likely experiencing cache thrashing. private void DetectCacheThrashing(string key, string? ttlValueChecksum) { try From 0a06445569145c012f045e6e69cfffae9958f194 Mon Sep 17 00:00:00 2001 From: Joel Nygren Date: Wed, 22 Oct 2025 14:29:14 +0200 Subject: [PATCH 17/17] copy options from RedisBackedHzCacheOptions to HzCacheOptions --- RedisBackedHzCache/RedisBackedHzCache.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/RedisBackedHzCache/RedisBackedHzCache.cs b/RedisBackedHzCache/RedisBackedHzCache.cs index 3da1ba5..dbd7cd2 100644 --- a/RedisBackedHzCache/RedisBackedHzCache.cs +++ b/RedisBackedHzCache/RedisBackedHzCache.cs @@ -97,7 +97,10 @@ public RedisBackedHzCache(RedisBackedHzCacheOptions options) } } }, - defaultTTL = options.defaultTTL + defaultTTL = options.defaultTTL, + LogCacheThrashing = options.LogCacheThrashing, + ThrashingLimit = options.ThrashingLimit, + ThrashingWindow = options.ThrashingWindow, }); if (string.IsNullOrWhiteSpace(options.redisConnectionString)) @@ -181,7 +184,7 @@ public CacheStatistics GetStatistics() public T Get(string key) { - using var activity = HzActivities.Source.StartActivityWithCommonTags(HzActivities.Names.Get, HzActivities.Area.RedisBackedHzCache, key: key); + using var activity = HzActivities.Source.StartActivityWithCommonTags(HzActivities.Names.Get, HzActivities.Area.RedisBackedHzCache, key: key); var value = hzCache.Get(key); if (value == null && options.useRedisAs2ndLevelCache) { @@ -226,7 +229,7 @@ public IList GetOrSetBatch(IList keys, Func, List GetOrSetBatch(IList keys, Func, List>> valueFactory, TimeSpan ttl) { - using var activity = HzActivities.Source.StartActivityWithCommonTags(HzActivities.Names.GetOrSetBatch, HzActivities.Area.RedisBackedHzCache, key: string.Join(",",keys??new List())); + using var activity = HzActivities.Source.StartActivityWithCommonTags(HzActivities.Names.GetOrSetBatch, HzActivities.Area.RedisBackedHzCache, key: string.Join(",", keys ?? new List())); Func, List>> redisFactory = idList => { // Create a list of redis keys from the list of cache keys @@ -290,4 +293,4 @@ private string CacheKeyFromRedisKey(string redisKey) return redisKey.Substring(options.applicationCachePrefix.Length + 1); } } -} +} \ No newline at end of file