From 608ab37aecbf6dd4dd59cda9ab18102af8ca7c1c Mon Sep 17 00:00:00 2001 From: Noi Date: Thu, 16 Jul 2020 14:48:09 -0700 Subject: [PATCH] Update background services to remove guild cache references Possibly optimized StaleDataCleaner as well by making certain database updates asynchronous --- BackgroundServices/BirthdayRoleUpdate.cs | 24 ++--- BackgroundServices/GuildStatistics.cs | 3 +- BackgroundServices/StaleDataCleaner.cs | 109 +++++++++++------------ 3 files changed, 62 insertions(+), 74 deletions(-) diff --git a/BackgroundServices/BirthdayRoleUpdate.cs b/BackgroundServices/BirthdayRoleUpdate.cs index ae9f7e7..4393f3e 100644 --- a/BackgroundServices/BirthdayRoleUpdate.cs +++ b/BackgroundServices/BirthdayRoleUpdate.cs @@ -66,23 +66,17 @@ namespace BirthdayBot.BackgroundServices { var diag = new PGDiagnostic(); - // Skip processing of guild if local info has not yet been loaded - if (!BotInstance.GuildCache.TryGetValue(guild.Id, out var gs)) - { - diag.FetchCachedGuild = "Server information not yet loaded by the bot. Try again later."; - return diag; - } - diag.FetchCachedGuild = null; + var gc = await GuildConfiguration.LoadAsync(guild.Id); // Check if role settings are correct before continuing with further processing SocketRole role = null; - if (gs.RoleId.HasValue) role = guild.GetRole(gs.RoleId.Value); + if (gc.RoleId.HasValue) role = guild.GetRole(gc.RoleId.Value); diag.RoleCheck = CheckCorrectRoleSettings(guild, role); if (diag.RoleCheck != null) return diag; // Determine who's currently having a birthday - var users = gs.Users; - var tz = gs.TimeZone; + var users = await GuildUserConfiguration.LoadAllAsync(guild.Id); + var tz = gc.TimeZone; var birthdays = GetGuildCurrentBirthdays(users, tz); // Note: Don't quit here if zero people are having birthdays. Roles may still need to be removed by BirthdayApply. diag.CurrentBirthdays = birthdays.Count.ToString(); @@ -103,10 +97,10 @@ namespace BirthdayBot.BackgroundServices diag.RoleApply = null; // Birthday announcement - var announce = gs.AnnounceMessages; - var announceping = gs.AnnouncePing; + var announce = gc.AnnounceMessages; + var announceping = gc.AnnouncePing; SocketTextChannel channel = null; - if (gs.AnnounceChannelId.HasValue) channel = guild.GetTextChannel(gs.AnnounceChannelId.Value); + if (gc.AnnounceChannelId.HasValue) channel = guild.GetTextChannel(gc.AnnounceChannelId.Value); if (announcementList.Count() != 0) { var announceResult = await AnnounceBirthdaysAsync(announce, announceping, channel, announcementList); @@ -145,7 +139,7 @@ namespace BirthdayBot.BackgroundServices /// Gets all known users from the given guild and returns a list including only those who are /// currently experiencing a birthday in the respective time zone. /// - private HashSet GetGuildCurrentBirthdays(IEnumerable guildUsers, string defaultTzStr) + private HashSet GetGuildCurrentBirthdays(IEnumerable guildUsers, string defaultTzStr) { var birthdayUsers = new HashSet(); @@ -271,7 +265,6 @@ namespace BirthdayBot.BackgroundServices { const string DefaultValue = "--"; - public string FetchCachedGuild = DefaultValue; public string RoleCheck = DefaultValue; public string CurrentBirthdays = DefaultValue; public string RoleApply = DefaultValue; @@ -282,7 +275,6 @@ namespace BirthdayBot.BackgroundServices { var result = new StringBuilder(); result.AppendLine("Test result:"); - result.AppendLine("Fetch guild information: " + (FetchCachedGuild ?? ":white_check_mark:")); result.AppendLine("Check role permissions: " + (RoleCheck ?? ":white_check_mark:")); result.AppendLine("Number of known users currently with a birthday: " + CurrentBirthdays); result.AppendLine("Role application process: " + (RoleApply ?? ":white_check_mark:")); diff --git a/BackgroundServices/GuildStatistics.cs b/BackgroundServices/GuildStatistics.cs index 3c93676..2b472fc 100644 --- a/BackgroundServices/GuildStatistics.cs +++ b/BackgroundServices/GuildStatistics.cs @@ -14,8 +14,7 @@ namespace BirthdayBot.BackgroundServices public async override Task OnTick() { var count = BotInstance.DiscordClient.Guilds.Count; - var cacheCount = BotInstance.GuildCache.Count; - Log($"Currently in {count} guilds. Cached guild settings: {cacheCount}."); + Log($"Currently in {count} guilds."); await SendExternalStatistics(count); } diff --git a/BackgroundServices/StaleDataCleaner.cs b/BackgroundServices/StaleDataCleaner.cs index 6a8a5dc..05d42e5 100644 --- a/BackgroundServices/StaleDataCleaner.cs +++ b/BackgroundServices/StaleDataCleaner.cs @@ -17,73 +17,70 @@ namespace BirthdayBot.BackgroundServices { // Build a list of all values to update var updateList = new Dictionary>(); - foreach (var gi in BotInstance.GuildCache) + foreach (var g in BotInstance.DiscordClient.Guilds) { var existingUsers = new List(); - updateList[gi.Key] = existingUsers; + updateList[g.Id] = existingUsers; - var guild = BotInstance.DiscordClient.GetGuild(gi.Key); - if (guild == null) continue; // Have cache without being in guild. Unlikely, but... - - // Get IDs of cached users which are currently in the guild - var cachedUserIds = from cu in gi.Value.Users select cu.UserId; - var guildUserIds = from gu in guild.Users select gu.Id; - var existingCachedIds = cachedUserIds.Intersect(guildUserIds); + // Get list of IDs for all users who exist in the database and currently exist in the guild + var savedUserIds = from cu in await GuildUserConfiguration.LoadAllAsync(g.Id) select cu.UserId; + var guildUserIds = from gu in g.Users select gu.Id; + var existingCachedIds = savedUserIds.Intersect(guildUserIds); } - using (var db = await BotInstance.Config.DatabaseSettings.OpenConnectionAsync()) + using var db = await Database.OpenConnectionAsync(); + + // Statement for updating last_seen in guilds + var cUpdateGuild = db.CreateCommand(); + cUpdateGuild.CommandText = $"update {GuildConfiguration.BackingTable} set last_seen = now() " + + "where guild_id = @Gid"; + var pUpdateG = cUpdateGuild.Parameters.Add("@Gid", NpgsqlDbType.Bigint); + cUpdateGuild.Prepare(); + + // Statement for updating last_seen in guild users + var cUpdateGuildUser = db.CreateCommand(); + cUpdateGuildUser.CommandText = $"update {GuildUserConfiguration.BackingTable} set last_seen = now() " + + "where guild_id = @Gid and user_id = @Uid"; + var pUpdateGU_g = cUpdateGuildUser.Parameters.Add("@Gid", NpgsqlDbType.Bigint); + var pUpdateGU_u = cUpdateGuildUser.Parameters.Add("@Uid", NpgsqlDbType.Bigint); + cUpdateGuildUser.Prepare(); + + // Do actual updates + var updates = new List(); + foreach (var item in updateList) { - // Prepare to update a lot of last-seen values - var cUpdateGuild = db.CreateCommand(); - cUpdateGuild.CommandText = $"update {GuildStateInformation.BackingTable} set last_seen = now() " - + "where guild_id = @Gid"; - var pUpdateG = cUpdateGuild.Parameters.Add("@Gid", NpgsqlDbType.Bigint); - cUpdateGuild.Prepare(); + var guild = item.Key; + var userlist = item.Value; - var cUpdateGuildUser = db.CreateCommand(); - cUpdateGuildUser.CommandText = $"update {GuildUserSettings.BackingTable} set last_seen = now() " - + "where guild_id = @Gid and user_id = @Uid"; - var pUpdateGU_g = cUpdateGuildUser.Parameters.Add("@Gid", NpgsqlDbType.Bigint); - var pUpdateGU_u = cUpdateGuildUser.Parameters.Add("@Uid", NpgsqlDbType.Bigint); - cUpdateGuildUser.Prepare(); + pUpdateG.Value = (long)guild; + updates.Add(cUpdateGuild.ExecuteNonQueryAsync()); - // Do actual updates - foreach (var item in updateList) + pUpdateGU_g.Value = (long)guild; + foreach (var userid in userlist) { - var guild = item.Key; - var userlist = item.Value; - - pUpdateG.Value = (long)guild; - cUpdateGuild.ExecuteNonQuery(); - - pUpdateGU_g.Value = (long)guild; - foreach (var userid in userlist) - { - pUpdateGU_u.Value = (long)userid; - cUpdateGuildUser.ExecuteNonQuery(); - } - } - - // Delete all old values - expects referencing tables to have 'on delete cascade' - using (var t = db.BeginTransaction()) - { - int staleGuilds, staleUsers; - using (var c = db.CreateCommand()) - { - // Delete data for guilds not seen in 4 weeks - c.CommandText = $"delete from {GuildStateInformation.BackingTable} where (now() - interval '28 days') > last_seen"; - staleGuilds = c.ExecuteNonQuery(); - } - using (var c = db.CreateCommand()) - { - // Delete data for users not seen in 8 weeks - c.CommandText = $"delete from {GuildUserSettings.BackingTable} where (now() - interval '56 days') > last_seen"; - staleUsers = c.ExecuteNonQuery(); - } - Log($"Will remove {staleGuilds} guilds, {staleUsers} users."); - t.Commit(); + pUpdateGU_u.Value = (long)userid; + updates.Add(cUpdateGuildUser.ExecuteNonQueryAsync()); } } + await Task.WhenAll(updates); + + // Delete all old values - expects referencing tables to have 'on delete cascade' + using var t = db.BeginTransaction(); + int staleGuilds, staleUsers; + using (var c = db.CreateCommand()) + { + // Delete data for guilds not seen in 4 weeks + c.CommandText = $"delete from {GuildConfiguration.BackingTable} where (now() - interval '28 days') > last_seen"; + staleGuilds = c.ExecuteNonQuery(); + } + using (var c = db.CreateCommand()) + { + // Delete data for users not seen in 8 weeks + c.CommandText = $"delete from {GuildUserConfiguration.BackingTable} where (now() - interval '56 days') > last_seen"; + staleUsers = c.ExecuteNonQuery(); + } + Log($"Will remove {staleGuilds} guilds, {staleUsers} users."); + t.Commit(); } } }