[backend/core] Add instance counters (ISH-107)

This commit is contained in:
Laura Hausmann 2024-02-29 19:19:11 +01:00
parent ad21897928
commit a719fc722e
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
6 changed files with 129 additions and 4 deletions

View file

@ -88,10 +88,11 @@ public static class WebApplicationExtensions
if (args.Contains("--recompute-counters")) if (args.Contains("--recompute-counters"))
{ {
app.Logger.LogInformation("Recomputing note & user counters, this will take a while..."); app.Logger.LogInformation("Recomputing note, user & instance counters, this will take a while...");
var maintenanceSvc = provider.GetRequiredService<DatabaseMaintenanceService>(); var maintenanceSvc = provider.GetRequiredService<DatabaseMaintenanceService>();
await maintenanceSvc.RecomputeNoteCountersAsync(); await maintenanceSvc.RecomputeNoteCountersAsync();
await maintenanceSvc.RecomputeUserCountersAsync(); await maintenanceSvc.RecomputeUserCountersAsync();
await maintenanceSvc.RecomputeInstanceCountersAsync();
} }
app.Logger.LogInformation("Verifying redis connection..."); app.Logger.LogInformation("Verifying redis connection...");

View file

@ -335,6 +335,16 @@ public class ActivityHandlerService(
follower.FollowingCount++; follower.FollowingCount++;
followee.FollowersCount++; followee.FollowersCount++;
_ = followupTaskSvc.ExecuteTask("UpdateInstanceFollowingCounter", async provider =>
{
var bgDb = provider.GetRequiredService<DatabaseContext>();
var bgInstanceSvc = provider.GetRequiredService<InstanceService>();
var dbInstance = await bgInstanceSvc.GetUpdatedInstanceMetadataAsync(follower.Host,
new Uri(follower.Uri!).Host);
await bgDb.Instances.Where(p => p.Id == dbInstance.Id)
.ExecuteUpdateAsync(p => p.SetProperty(i => i.FollowingCount, i => i.FollowingCount + 1));
});
await db.AddAsync(following); await db.AddAsync(following);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await notificationSvc.GenerateFollowNotification(follower, followee); await notificationSvc.GenerateFollowNotification(follower, followee);
@ -403,6 +413,16 @@ public class ActivityHandlerService(
actor.FollowersCount++; actor.FollowersCount++;
request.Follower.FollowingCount++; request.Follower.FollowingCount++;
_ = followupTaskSvc.ExecuteTask("UpdateInstanceFollowersCounter", async provider =>
{
var bgDb = provider.GetRequiredService<DatabaseContext>();
var bgInstanceSvc = provider.GetRequiredService<InstanceService>();
var dbInstance = await bgInstanceSvc.GetUpdatedInstanceMetadataAsync(request.Followee.Host!,
new Uri(request.Followee.Uri!).Host);
await bgDb.Instances.Where(p => p.Id == dbInstance.Id)
.ExecuteUpdateAsync(p => p.SetProperty(i => i.FollowersCount, i => i.FollowersCount + 1));
});
db.Remove(request); db.Remove(request);
await db.AddAsync(following); await db.AddAsync(following);
await db.SaveChangesAsync(); await db.SaveChangesAsync();

View file

@ -23,4 +23,18 @@ public class DatabaseMaintenanceService(DatabaseContext db)
.SetProperty(u => u.NotesCount, .SetProperty(u => u.NotesCount,
u => db.Notes.Count(n => n.User == u && !n.IsPureRenote))); u => db.Notes.Count(n => n.User == u && !n.IsPureRenote)));
} }
public async Task RecomputeInstanceCountersAsync()
{
await db.Instances.ExecuteUpdateAsync(p => p.SetProperty(i => i.NotesCount,
i => db.Notes.Count(n => n.UserHost == i.Host))
.SetProperty(i => i.UsersCount,
i => db.Users.Count(u => u.Host == i.Host))
.SetProperty(i => i.FollowersCount,
i => db.Followings
.Count(n => n.FolloweeHost == i.Host))
.SetProperty(i => i.FollowingCount,
i => db.Followings
.Count(n => n.FollowerHost == i.Host)));
}
} }

View file

@ -14,7 +14,7 @@ public class InstanceService(DatabaseContext db, HttpClient httpClient)
o.PoolInitialFill = 5; o.PoolInitialFill = 5;
}); });
private async Task<Instance> GetUpdatedInstanceMetadataAsync(string host, string webDomain) public async Task<Instance> GetUpdatedInstanceMetadataAsync(string host, string webDomain)
{ {
host = host.ToLowerInvariant(); host = host.ToLowerInvariant();
var instance = db.Instances.FirstOrDefault(p => p.Host == host); var instance = db.Instances.FirstOrDefault(p => p.Host == host);

View file

@ -37,7 +37,8 @@ public class NoteService(
NotificationService notificationSvc, NotificationService notificationSvc,
EventService eventSvc, EventService eventSvc,
ActivityPub.ActivityRenderer activityRenderer, ActivityPub.ActivityRenderer activityRenderer,
EmojiService emojiSvc EmojiService emojiSvc,
FollowupTaskService followupTaskSvc
) )
{ {
private readonly List<string> _resolverHistory = []; private readonly List<string> _resolverHistory = [];
@ -111,7 +112,23 @@ public class NoteService(
await notificationSvc.GenerateReplyNotifications(note, mentionedLocalUserIds); await notificationSvc.GenerateReplyNotifications(note, mentionedLocalUserIds);
await notificationSvc.GenerateRenoteNotification(note); await notificationSvc.GenerateRenoteNotification(note);
if (user.Host != null) return note; if (user.Host != null)
{
if (user.Uri != null)
{
_ = followupTaskSvc.ExecuteTask("UpdateInstanceNoteCounter", async provider =>
{
var bgDb = provider.GetRequiredService<DatabaseContext>();
var bgInstanceSvc = provider.GetRequiredService<InstanceService>();
var dbInstance =
await bgInstanceSvc.GetUpdatedInstanceMetadataAsync(user.Host, new Uri(user.Uri).Host);
await bgDb.Instances.Where(p => p.Id == dbInstance.Id)
.ExecuteUpdateAsync(p => p.SetProperty(i => i.NotesCount, i => i.NotesCount + 1));
});
}
return note;
}
var actor = userRenderer.RenderLite(user); var actor = userRenderer.RenderLite(user);
ASActivity activity = note is { IsPureRenote: true, Renote: not null } ASActivity activity = note is { IsPureRenote: true, Renote: not null }
@ -232,7 +249,22 @@ public class NoteService(
await db.SaveChangesAsync(); await db.SaveChangesAsync();
if (note.UserHost != null) if (note.UserHost != null)
{
if (note.User.Uri != null)
{
_ = followupTaskSvc.ExecuteTask("UpdateInstanceNoteCounter", async provider =>
{
var bgDb = provider.GetRequiredService<DatabaseContext>();
var bgInstanceSvc = provider.GetRequiredService<InstanceService>();
var dbInstance =
await bgInstanceSvc.GetUpdatedInstanceMetadataAsync(note.UserHost, new Uri(note.User.Uri).Host);
await bgDb.Instances.Where(p => p.Id == dbInstance.Id)
.ExecuteUpdateAsync(p => p.SetProperty(i => i.NotesCount, i => i.NotesCount - 1));
});
}
return; return;
}
var recipients = await db.Users.Where(p => note.Mentions.Concat(note.VisibleUserIds).Distinct().Contains(p.Id)) var recipients = await db.Users.Where(p => note.Mentions.Concat(note.VisibleUserIds).Distinct().Contains(p.Id))
.Select(p => new User { Host = p.Host, Inbox = p.Inbox }) .Select(p => new User { Host = p.Host, Inbox = p.Inbox })
@ -279,6 +311,21 @@ public class NoteService(
db.Remove(dbNote); db.Remove(dbNote);
eventSvc.RaiseNoteDeleted(this, dbNote); eventSvc.RaiseNoteDeleted(this, dbNote);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
// ReSharper disable once EntityFramework.NPlusOne.IncompleteDataUsage (same reason as above)
if (dbNote.User.Uri != null && dbNote.UserHost != null)
{
_ = followupTaskSvc.ExecuteTask("UpdateInstanceNoteCounter", async provider =>
{
var bgDb = provider.GetRequiredService<DatabaseContext>();
var bgInstanceSvc = provider.GetRequiredService<InstanceService>();
// ReSharper disable once EntityFramework.NPlusOne.IncompleteDataUsage (same reason as above)
var dbInstance =
await bgInstanceSvc.GetUpdatedInstanceMetadataAsync(dbNote.UserHost, new Uri(dbNote.User.Uri).Host);
await bgDb.Instances.Where(p => p.Id == dbInstance.Id)
.ExecuteUpdateAsync(p => p.SetProperty(i => i.NotesCount, i => i.NotesCount - 1));
});
}
} }
public async Task UndoAnnounceAsync(ASNote note, User actor) public async Task UndoAnnounceAsync(ASNote note, User actor)

View file

@ -185,6 +185,14 @@ public class UserService(
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await processPendingDeletes(); await processPendingDeletes();
user = await UpdateProfileMentions(user, actor); user = await UpdateProfileMentions(user, actor);
_ = followupTaskSvc.ExecuteTask("UpdateInstanceUserCounter", async provider =>
{
var bgDb = provider.GetRequiredService<DatabaseContext>();
var bgInstanceSvc = provider.GetRequiredService<InstanceService>();
var dbInstance = await bgInstanceSvc.GetUpdatedInstanceMetadataAsync(host, new Uri(uri).Host);
await bgDb.Instances.Where(p => p.Id == dbInstance.Id)
.ExecuteUpdateAsync(p => p.SetProperty(i => i.UsersCount, i => i.UsersCount + 1));
});
return user; return user;
} }
catch (UniqueConstraintException) catch (UniqueConstraintException)
@ -437,6 +445,15 @@ public class UserService(
db.Remove(user); db.Remove(user);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
_ = followupTaskSvc.ExecuteTask("UpdateInstanceUserCounter", async provider =>
{
var bgDb = provider.GetRequiredService<DatabaseContext>();
var bgInstanceSvc = provider.GetRequiredService<InstanceService>();
var dbInstance = await bgInstanceSvc.GetUpdatedInstanceMetadataAsync(user.Host!, new Uri(user.Uri!).Host);
await bgDb.Instances.Where(p => p.Id == dbInstance.Id)
.ExecuteUpdateAsync(p => p.SetProperty(i => i.UsersCount, i => i.UsersCount - 1));
});
if (user.Avatar != null) if (user.Avatar != null)
await driveSvc.RemoveFile(user.Avatar); await driveSvc.RemoveFile(user.Avatar);
if (user.Banner != null) if (user.Banner != null)
@ -511,6 +528,19 @@ public class UserService(
await db.AddAsync(following); await db.AddAsync(following);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
if (request.Follower is { Host: not null })
{
_ = followupTaskSvc.ExecuteTask("UpdateInstanceFollowingCounter", async provider =>
{
var bgDb = provider.GetRequiredService<DatabaseContext>();
var bgInstanceSvc = provider.GetRequiredService<InstanceService>();
var dbInstance = await bgInstanceSvc.GetUpdatedInstanceMetadataAsync(request.Follower.Host,
new Uri(request.Follower.Uri!).Host);
await bgDb.Instances.Where(p => p.Id == dbInstance.Id)
.ExecuteUpdateAsync(p => p.SetProperty(i => i.FollowingCount, i => i.FollowingCount + 1));
});
}
await notificationSvc.GenerateFollowNotification(request.Follower, request.Followee); await notificationSvc.GenerateFollowNotification(request.Follower, request.Followee);
await notificationSvc.GenerateFollowRequestAcceptedNotification(request); await notificationSvc.GenerateFollowRequestAcceptedNotification(request);
@ -627,6 +657,19 @@ public class UserService(
db.RemoveRange(followings); db.RemoveRange(followings);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
if (followee.Host != null)
{
_ = followupTaskSvc.ExecuteTask("UpdateInstanceFollowersCounter", async provider =>
{
var bgDb = provider.GetRequiredService<DatabaseContext>();
var bgInstanceSvc = provider.GetRequiredService<InstanceService>();
var dbInstance =
await bgInstanceSvc.GetUpdatedInstanceMetadataAsync(followee.Host, new Uri(followee.Uri!).Host);
await bgDb.Instances.Where(p => p.Id == dbInstance.Id)
.ExecuteUpdateAsync(p => p.SetProperty(i => i.FollowersCount, i => i.FollowersCount - 1));
});
}
followee.PrecomputedIsFollowedBy = false; followee.PrecomputedIsFollowedBy = false;
} }