Improve export performance

Refactored ExportSystem to:
- Only fetch message counts once (instead of a query per member)
- Fetch switches using the newly refactored GetTruncatedSwitchList (gets switches and their members in one shot instead of querying for switch members one switch at a time)

- Added a new MessageCountsPerMember method to MemberStore to support the above
- Modified GetTruncatedSwitchList query to retrieve switches with no members (wasn't important for frontpercent, but is if we're reusing that method here)

This doesn't require any index changes beyond those that support GetTruncatedSwitchList, though we can see a small benefit with an index on messages covering the member column (valuable for other reasons outside of these additions).
This commit is contained in:
Noko 2019-10-06 02:03:28 -05:00
parent ef7b825aa6
commit adea7be07c
2 changed files with 46 additions and 26 deletions

View File

@ -26,11 +26,34 @@ namespace PluralKit.Bot
public async Task<DataFileSystem> ExportSystem(PKSystem system) public async Task<DataFileSystem> ExportSystem(PKSystem system)
{ {
// Export members
var members = new List<DataFileMember>(); var members = new List<DataFileMember>();
foreach (var member in await _members.GetBySystem(system)) members.Add(await ExportMember(member)); var pkMembers = await _members.GetBySystem(system); // Read all members in the system
var messageCounts = await _members.MessageCountsPerMember(system); // Count messages proxied by all members in the system
members.AddRange(pkMembers.Select(m => new DataFileMember
{
Id = m.Hid,
Name = m.Name,
DisplayName = m.DisplayName,
Description = m.Description,
Birthday = m.Birthday != null ? Formats.DateExportFormat.Format(m.Birthday.Value) : null,
Pronouns = m.Pronouns,
Color = m.Color,
AvatarUrl = m.AvatarUrl,
Prefix = m.Prefix,
Suffix = m.Suffix,
Created = Formats.TimestampExportFormat.Format(m.Created),
MessageCount = messageCounts.Where(x => x.Member.Equals(m.Id)).Select(x => x.MessageCount).FirstOrDefault()
}));
// Export switches
var switches = new List<DataFileSwitch>(); var switches = new List<DataFileSwitch>();
foreach (var sw in await _switches.GetSwitches(system, 999999)) switches.Add(await ExportSwitch(sw)); var switchList = await _switches.GetTruncatedSwitchList(system, Instant.FromDateTimeUtc(DateTime.MinValue.ToUniversalTime()), SystemClock.Instance.GetCurrentInstant());
switches.AddRange(switchList.Select(x => new DataFileSwitch
{
Timestamp = Formats.TimestampExportFormat.Format(x.TimespanStart),
Members = x.Members.Select(m => m.Hid).ToList() // Look up member's HID using the member export from above
}));
return new DataFileSystem return new DataFileSystem
{ {
@ -47,28 +70,6 @@ namespace PluralKit.Bot
}; };
} }
private async Task<DataFileMember> ExportMember(PKMember member) => new DataFileMember
{
Id = member.Hid,
Name = member.Name,
DisplayName = member.DisplayName,
Description = member.Description,
Birthday = member.Birthday != null ? Formats.DateExportFormat.Format(member.Birthday.Value) : null,
Pronouns = member.Pronouns,
Color = member.Color,
AvatarUrl = member.AvatarUrl,
Prefix = member.Prefix,
Suffix = member.Suffix,
Created = Formats.TimestampExportFormat.Format(member.Created),
MessageCount = await _members.MessageCount(member)
};
private async Task<DataFileSwitch> ExportSwitch(PKSwitch sw) => new DataFileSwitch
{
Members = (await _switches.GetSwitchMembers(sw)).Select(m => m.Hid).ToList(),
Timestamp = Formats.TimestampExportFormat.Format(sw.Timestamp)
};
public async Task<ImportResult> ImportSystem(DataFileSystem data, PKSystem system, ulong accountId) public async Task<ImportResult> ImportSystem(DataFileSystem data, PKSystem system, ulong accountId)
{ {
// TODO: make atomic, somehow - we'd need to obtain one IDbConnection and reuse it // TODO: make atomic, somehow - we'd need to obtain one IDbConnection and reuse it

View File

@ -172,6 +172,25 @@ namespace PluralKit {
return await conn.QuerySingleAsync<int>("select count(*) from messages where member = @Id", member); return await conn.QuerySingleAsync<int>("select count(*) from messages where member = @Id", member);
} }
public struct MessageBreakdownListEntry
{
public int Member;
public int MessageCount;
}
public async Task<IEnumerable<MessageBreakdownListEntry>> MessageCountsPerMember(PKSystem system)
{
using (var conn = await _conn.Obtain())
return await conn.QueryAsync<MessageBreakdownListEntry>(
@"SELECT messages.member, COUNT(messages.member) messagecount
FROM members
JOIN messages
ON members.id = messages.member
WHERE members.system = @System
GROUP BY messages.member",
new { System = system.Id });
}
public async Task<int> MemberCount(PKSystem system) public async Task<int> MemberCount(PKSystem system)
{ {
using (var conn = await _conn.Obtain()) using (var conn = await _conn.Obtain())
@ -362,7 +381,7 @@ namespace PluralKit {
var switchMembersEntries = await conn.QueryAsync<SwitchMembersListEntry>( var switchMembersEntries = await conn.QueryAsync<SwitchMembersListEntry>(
@"SELECT switch_members.member, switches.timestamp @"SELECT switch_members.member, switches.timestamp
FROM switches FROM switches
JOIN switch_members LEFT JOIN switch_members
ON switches.id = switch_members.switch ON switches.id = switch_members.switch
WHERE switches.system = @System WHERE switches.system = @System
AND ( AND (
@ -451,7 +470,7 @@ namespace PluralKit {
select new SwitchListEntry select new SwitchListEntry
{ {
TimespanStart = g.Key, TimespanStart = g.Key,
Members = g.Select(x => memberObjects[x.Member]).ToList() Members = g.Where(x => x.Member != 0).Select(x => memberObjects[x.Member]).ToList()
}; };
// Loop through every switch that overlaps the range and add it to the output list // Loop through every switch that overlaps the range and add it to the output list