Basic redis object cache implementation

This commit is contained in:
Laura Hausmann 2024-01-28 02:26:41 +01:00
parent a0425aaf4c
commit 3e4410f52c
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
2 changed files with 48 additions and 4 deletions

View file

@ -0,0 +1,38 @@
using System.Text.Json;
using Microsoft.Extensions.Caching.Distributed;
namespace Iceshrimp.Backend.Core.Extensions;
public static class DistributedCacheExtensions {
//TODO: named caches, CacheService?
//TODO: thread-safe locks to prevent fetching data more than once
public static async Task<T?> Get<T>(this IDistributedCache cache, string key) {
var buffer = await cache.GetAsync(key);
if (buffer == null) return default;
var stream = new MemoryStream(buffer);
var data = await JsonSerializer.DeserializeAsync<T>(stream);
return data != null ? (T)data : default;
}
public static async Task<T> Fetch<T>(this IDistributedCache cache, string key, TimeSpan ttl,
Func<Task<T>> fetcher) {
var hit = await cache.Get<T>(key);
if (hit != null) return hit;
var fetched = await fetcher();
await cache.Set(key, fetched, ttl);
return fetched;
}
public static async Task Set<T>(this IDistributedCache cache, string key, T data, TimeSpan ttl) {
using var ms = new MemoryStream();
await JsonSerializer.SerializeAsync(ms, data);
var buffer = new Memory<byte>();
_ = await ms.ReadAsync(buffer);
var options = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = ttl };
await cache.SetAsync(key, buffer.ToArray(), options);
}
}

View file

@ -9,6 +9,7 @@ using Iceshrimp.Backend.Core.Federation.ActivityPub;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
@ -19,7 +20,9 @@ public class UserService(
IOptions<Config.InstanceSection> instance, IOptions<Config.InstanceSection> instance,
ILogger<UserService> logger, ILogger<UserService> logger,
DatabaseContext db, DatabaseContext db,
ActivityFetcherService fetchSvc) { ActivityFetcherService fetchSvc,
IDistributedCache cache
) {
private (string Username, string? Host) AcctToTuple(string acct) { private (string Username, string? Host) AcctToTuple(string acct) {
if (!acct.StartsWith("acct:")) throw new GracefulException(HttpStatusCode.BadRequest, "Invalid query"); if (!acct.StartsWith("acct:")) throw new GracefulException(HttpStatusCode.BadRequest, "Invalid query");
@ -161,10 +164,13 @@ public class UserService(
return await GetOrCreateSystemUser("relay.actor"); return await GetOrCreateSystemUser("relay.actor");
} }
//TODO: cache in redis
private async Task<User> GetOrCreateSystemUser(string username) { private async Task<User> GetOrCreateSystemUser(string username) {
return await db.Users.FirstOrDefaultAsync(p => p.UsernameLower == username && p.Host == null) ?? return await cache.Fetch($"systemUser:{username}", TimeSpan.FromHours(24), async () => {
await CreateSystemUser(username); logger.LogTrace("GetOrCreateSystemUser delegate method called for user {username}", username);
return await db.Users.FirstOrDefaultAsync(p => p.UsernameLower == username &&
p.Host == null) ??
await CreateSystemUser(username);
});
} }
private async Task<User> CreateSystemUser(string username) { private async Task<User> CreateSystemUser(string username) {