[backend/core] Improve MetaService (ISH-217)

This commit is contained in:
Laura Hausmann 2024-03-24 14:46:19 +01:00
parent 6d49eb07ca
commit b9582681d4
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
2 changed files with 15 additions and 52 deletions

View file

@ -66,7 +66,8 @@ public static class ServiceExtensions
.AddScoped<InstanceService>()
.AddScoped<MfmConverter>()
.AddScoped<UserProfileRenderer>()
.AddScoped<CacheService>();
.AddScoped<CacheService>()
.AddScoped<MetaService>();
// Singleton = instantiated once across application lifetime
services
@ -80,8 +81,7 @@ public static class ServiceExtensions
.AddSingleton<AuthorizationMiddleware>()
.AddSingleton<RequestVerificationMiddleware>()
.AddSingleton<RequestDurationMiddleware>()
.AddSingleton<PushService>()
.AddSingleton<MetaService>();
.AddSingleton<PushService>();
// Hosted services = long running background tasks
// Note: These need to be added as a singleton as well to ensure data consistency

View file

@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Core.Services;
public class MetaService(IServiceScopeFactory scopeFactory)
public class MetaService([FromKeyedServices("cache")] DatabaseContext db)
{
public async Task<T> Get<T>(Meta<T> meta) => meta.ConvertGet(await Fetch(meta.Key));
@ -31,9 +31,6 @@ public class MetaService(IServiceScopeFactory scopeFactory)
public async Task EnsureSet<T>(IReadOnlyList<Meta<T>> metas, Func<List<T>> values)
{
await using var scope = GetScope();
await using var db = GetDbContext(scope);
if (await db.MetaStore.CountAsync(p => metas.Select(m => m.Key).Contains(p.Key)) == metas.Count)
return;
@ -48,37 +45,18 @@ public class MetaService(IServiceScopeFactory scopeFactory)
public async Task Set<T>(Meta<T> meta, T value) => await Set(meta.Key, meta.ConvertSet(value));
// Ensures the table is in memory (we could use pg_prewarm for this but that extension requires superuser privileges to install)
public async Task WarmupCache()
{
await using var scope = GetScope();
await using var db = GetDbContext(scope);
public async Task WarmupCache() => await db.MetaStore.ToListAsync();
await db.MetaStore.ToListAsync();
}
private async Task<string?> Fetch(string key) =>
await db.MetaStore.Where(p => p.Key == key).Select(p => p.Value).FirstOrDefaultAsync();
private async Task<string?> Fetch(string key)
{
await using var scope = GetScope();
await using var db = GetDbContext(scope);
return await db.MetaStore.Where(p => p.Key == key).Select(p => p.Value).FirstOrDefaultAsync();
}
private async Task<Dictionary<string, string?>> FetchMany(IEnumerable<string> keys)
{
await using var scope = GetScope();
await using var db = GetDbContext(scope);
return await db
.MetaStore.Where(p => keys.Contains(p.Key))
.ToDictionaryAsync(p => p.Key, p => p.Value);
}
private async Task<Dictionary<string, string?>> FetchMany(IEnumerable<string> keys) =>
await db.MetaStore.Where(p => keys.Contains(p.Key))
.ToDictionaryAsync(p => p.Key, p => p.Value);
private async Task Set(string key, string? value)
{
await using var scope = GetScope();
await using var db = GetDbContext(scope);
var entity = await db.MetaStore.FirstOrDefaultAsync(p => p.Key == key);
var entity = await db.MetaStore.FirstOrDefaultAsync(p => p.Key == key);
if (entity != null)
{
entity.Value = value;
@ -86,27 +64,12 @@ public class MetaService(IServiceScopeFactory scopeFactory)
}
else
{
entity = new MetaStoreEntry { Key = key, Value = value };
db.Add(entity);
try
{
await db.SaveChangesAsync();
}
catch (UniqueConstraintException)
{
db.Remove(entity);
entity = await db.MetaStore.FirstOrDefaultAsync(p => p.Key == key) ??
throw new Exception("Failed to fetch entity after UniqueConstraintException");
entity.Value = value;
await db.SaveChangesAsync();
}
await db.MetaStore.Upsert(new MetaStoreEntry { Key = key, Value = value })
.On(p => p.Key)
.WhenMatched((_, orig) => new MetaStoreEntry { Value = orig.Value, })
.RunAsync();
}
}
private static DatabaseContext GetDbContext(IServiceScope scope) =>
scope.ServiceProvider.GetRequiredService<DatabaseContext>();
private AsyncServiceScope GetScope() => scopeFactory.CreateAsyncScope();
}
public static class MetaEntity