mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-10-08 14:05:02 -04:00
feat(cache): sample metadata access for lru
This commit is contained in:
3 files changed
+68
No files matched your search
@@ -43,6 +43,23 @@ public sealed class DatabaseApplicationCacheTests : IAsyncLifetime
|
||||
Assert.Equal(6, entry.PayloadBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadsFlushOneSampledAccessTimestamp()
|
||||
{
|
||||
await _cache.SetStringAsync("search:touched", "value", TimeSpan.FromHours(1));
|
||||
_clock.UtcNow = _clock.UtcNow.AddMinutes(5);
|
||||
|
||||
Assert.Equal("value", await _cache.GetStringAsync("search:touched"));
|
||||
Assert.Equal("value", await _cache.GetStringAsync("search:touched"));
|
||||
Assert.Equal(1, await _cache.FlushAccessesAsync());
|
||||
|
||||
await using var database = await _factory.CreateDbContextAsync();
|
||||
Assert.Equal(
|
||||
_clock.UtcNow,
|
||||
(await database.ApplicationCacheEntries.SingleAsync()).UpdatedAt);
|
||||
Assert.Equal(0, await _cache.FlushAccessesAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExpiredEntry_IsAColdMissAndIsRemoved()
|
||||
{
|
||||
|
||||
@@ -39,6 +39,7 @@ public sealed class BoundedHotApplicationCache : IApplicationCache, IDisposable
|
||||
{
|
||||
if (_memory.TryGetValue(key, out HotEntry? entry))
|
||||
{
|
||||
_database.RecordAccess(key);
|
||||
Interlocked.Increment(ref _hits);
|
||||
return entry?.Value;
|
||||
}
|
||||
@@ -160,6 +161,7 @@ public sealed class BoundedHotApplicationCache : IApplicationCache, IDisposable
|
||||
{
|
||||
_memory.Clear();
|
||||
Interlocked.Add(ref _evictions, ClearResidents());
|
||||
await _database.FlushAccessesAsync(cancellationToken: cancellationToken);
|
||||
var deleted = await _database.CleanupExpiredAsync(
|
||||
cancellationToken: cancellationToken);
|
||||
deleted += await _database.CleanupInvalidOwnershipAsync(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Collections.Concurrent;
|
||||
using allstarr.Core.Operations;
|
||||
using allstarr.Core.Storage;
|
||||
using allstarr.Models.Settings;
|
||||
@@ -25,6 +26,7 @@ public sealed class DatabaseApplicationCache(
|
||||
private long _misses;
|
||||
private long _writes;
|
||||
private long _evictions;
|
||||
private readonly ConcurrentDictionary<string, byte> _accessed = new(StringComparer.Ordinal);
|
||||
private readonly CacheSettings _settings = configuredSettings?.Value ?? new CacheSettings();
|
||||
|
||||
public bool IsEnabled => true;
|
||||
@@ -137,6 +139,7 @@ public sealed class DatabaseApplicationCache(
|
||||
|
||||
if (entry.ExpiresAt is null || entry.ExpiresAt > clock.UtcNow)
|
||||
{
|
||||
RecordAccess(key);
|
||||
Interlocked.Increment(ref _hits);
|
||||
return entry.Value;
|
||||
}
|
||||
@@ -395,6 +398,49 @@ public sealed class DatabaseApplicationCache(
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordAccess(string key)
|
||||
{
|
||||
if (IsValidKey(key))
|
||||
{
|
||||
_accessed[key] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> FlushAccessesAsync(
|
||||
int batchSize = DefaultCleanupBatchSize,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var keys = _accessed.Keys
|
||||
.Order(StringComparer.Ordinal)
|
||||
.Take(Math.Clamp(batchSize, 1, DefaultCleanupBatchSize))
|
||||
.ToArray();
|
||||
if (keys.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var database = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
var updated = await database.Set<ApplicationCacheEntryRecord>()
|
||||
.Where(item => keys.Contains(item.Key))
|
||||
.ExecuteUpdateAsync(
|
||||
setters => setters.SetProperty(item => item.UpdatedAt, clock.UtcNow),
|
||||
cancellationToken);
|
||||
foreach (var key in keys)
|
||||
{
|
||||
_accessed.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogWarning(exception, "Database cache access flush failed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DatabaseCacheMaintenancePreview> PreviewMaintenanceAsync(
|
||||
int batchSize = DefaultCleanupBatchSize,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -769,6 +815,9 @@ public sealed class DatabaseApplicationCacheCleanupService(
|
||||
using var timer = new PeriodicTimer(CleanupInterval);
|
||||
while (await timer.WaitForNextTickAsync(stoppingToken))
|
||||
{
|
||||
await cache.FlushAccessesAsync(
|
||||
DatabaseApplicationCache.DefaultCleanupBatchSize,
|
||||
stoppingToken);
|
||||
var deleted = await cache.CleanupExpiredAsync(
|
||||
DatabaseApplicationCache.DefaultCleanupBatchSize,
|
||||
stoppingToken);
|
||||
|
||||
Reference in new issue
Block a user