Merge pull request #132 from nephanim/perf/frontpercent

Improve frontpercent performance
This commit is contained in:
Astrid 2019-10-18 13:20:43 +02:00 committed by GitHub
commit 49adcd680a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 77 additions and 24 deletions

View File

@ -337,6 +337,48 @@ namespace PluralKit {
return await conn.QueryAsync<PKSwitch>("select * from switches where system = @System order by timestamp desc limit @Count", new {System = system.Id, Count = count}); return await conn.QueryAsync<PKSwitch>("select * from switches where system = @System order by timestamp desc limit @Count", new {System = system.Id, Count = count});
} }
public struct SwitchMembersListEntry
{
public int Member;
public Instant Timestamp;
}
public async Task<IEnumerable<SwitchMembersListEntry>> GetSwitchMembersList(PKSystem system, Instant start, Instant end)
{
// Wrap multiple commands in a single transaction for performance
using (var conn = await _conn.Obtain())
using (var tx = conn.BeginTransaction())
{
// Find the time of the last switch outside the range as it overlaps the range
// If no prior switch exists, the lower bound of the range remains the start time
var lastSwitch = await conn.QuerySingleOrDefaultAsync<Instant>(
@"SELECT COALESCE(MAX(timestamp), @Start)
FROM switches
WHERE switches.system = @System
AND switches.timestamp < @Start",
new { System = system.Id, Start = start });
// Then collect the time and members of all switches that overlap the range
var switchMembersEntries = await conn.QueryAsync<SwitchMembersListEntry>(
@"SELECT switch_members.member, switches.timestamp
FROM switches
JOIN switch_members
ON switches.id = switch_members.switch
WHERE switches.system = @System
AND (
switches.timestamp >= @Start
OR switches.timestamp = @LastSwitch
)
AND switches.timestamp < @End
ORDER BY switches.timestamp DESC",
new { System = system.Id, Start = start, End = end, LastSwitch = lastSwitch });
// Commit and return the list
tx.Commit();
return switchMembersEntries;
}
}
public async Task<IEnumerable<int>> GetSwitchMemberIds(PKSwitch sw) public async Task<IEnumerable<int>> GetSwitchMemberIds(PKSwitch sw)
{ {
using (var conn = await _conn.Obtain()) using (var conn = await _conn.Obtain())
@ -386,14 +428,8 @@ namespace PluralKit {
public async Task<IEnumerable<SwitchListEntry>> GetTruncatedSwitchList(PKSystem system, Instant periodStart, Instant periodEnd) public async Task<IEnumerable<SwitchListEntry>> GetTruncatedSwitchList(PKSystem system, Instant periodStart, Instant periodEnd)
{ {
// TODO: only fetch the necessary switches here // Returns the timestamps and member IDs of switches overlapping the range, in chronological (newest first) order
// todo: this is in general not very efficient LOL var switchMembers = await GetSwitchMembersList(system, periodStart, periodEnd);
// returns switches in chronological (newest first) order
var switches = await GetSwitches(system);
// we skip all switches that happened later than the range end, and taking all the ones that happened after the range start
// *BUT ALSO INCLUDING* the last switch *before* the range (that partially overlaps the range period)
var switchesInRange = switches.SkipWhile(sw => sw.Timestamp >= periodEnd).TakeWhileIncluding(sw => sw.Timestamp > periodStart).ToList();
// query DB for all members involved in any of the switches above and collect into a dictionary for future use // query DB for all members involved in any of the switches above and collect into a dictionary for future use
// this makes sure the return list has the same instances of PKMember throughout, which is important for the dictionary // this makes sure the return list has the same instances of PKMember throughout, which is important for the dictionary
@ -401,34 +437,43 @@ namespace PluralKit {
Dictionary<int, PKMember> memberObjects; Dictionary<int, PKMember> memberObjects;
using (var conn = await _conn.Obtain()) using (var conn = await _conn.Obtain())
{ {
memberObjects = (await conn.QueryAsync<PKMember>( memberObjects = (
"select distinct members.* from members, switch_members where switch_members.switch = any(@Switches) and switch_members.member = members.id", // lol postgres specific `= any()` syntax await conn.QueryAsync<PKMember>(
new {Switches = switchesInRange.Select(sw => sw.Id).ToList()})) "select * from members where id = any(@Switches)", // lol postgres specific `= any()` syntax
.ToDictionary(m => m.Id); new { Switches = switchMembers.Select(m => m.Member).Distinct().ToList() })
).ToDictionary(m => m.Id);
} }
// Initialize entries - still need to loop to determine the TimespanEnd below
// we create the entry objects var entries =
var outList = new List<SwitchListEntry>(); from item in switchMembers
group item by item.Timestamp into g
// loop through every switch that *occurred* in-range and add it to the list select new SwitchListEntry
// end time is the switch *after*'s timestamp - we cheat and start it out at the range end so the first switch in-range "ends" there instead of the one after's start point
var endTime = periodEnd;
foreach (var switchInRange in switchesInRange)
{ {
// find the start time of the switch, but clamp it to the range (only applicable to the Last Switch Before Range we include in the TakeWhileIncluding call above) TimespanStart = g.Key,
var switchStartClamped = switchInRange.Timestamp; Members = g.Select(x => memberObjects[x.Member]).ToList()
if (switchStartClamped < periodStart) switchStartClamped = periodStart; };
// Loop through every switch that overlaps the range and add it to the output list
// end time is the *FOLLOWING* switch's timestamp - we cheat by working backwards from the range end, so no dates need to be compared
var endTime = periodEnd;
var outList = new List<SwitchListEntry>();
foreach (var e in entries)
{
// Override the start time of the switch if it's outside the range (only true for the "out of range" switch we included above)
var switchStartClamped = e.TimespanStart < periodStart
? periodStart
: e.TimespanStart;
outList.Add(new SwitchListEntry outList.Add(new SwitchListEntry
{ {
Members = (await GetSwitchMemberIds(switchInRange)).Select(id => memberObjects[id]).ToList(), Members = e.Members,
TimespanStart = switchStartClamped, TimespanStart = switchStartClamped,
TimespanEnd = endTime TimespanEnd = endTime
}); });
// next switch's end is this switch's start // next switch's end is this switch's start (we're working backward in time)
endTime = switchInRange.Timestamp; endTime = e.TimespanStart;
} }
return outList; return outList;

View File

@ -49,6 +49,10 @@ create table if not exists switches
system serial not null references systems (id) on delete cascade, system serial not null references systems (id) on delete cascade,
timestamp timestamp not null default (current_timestamp at time zone 'utc') timestamp timestamp not null default (current_timestamp at time zone 'utc')
); );
CREATE INDEX IF NOT EXISTS idx_switches_system
ON switches USING btree (
system ASC NULLS LAST
) INCLUDE ("timestamp");
create table if not exists switch_members create table if not exists switch_members
( (
@ -56,6 +60,10 @@ create table if not exists switch_members
switch serial not null references switches (id) on delete cascade, switch serial not null references switches (id) on delete cascade,
member serial not null references members (id) on delete cascade member serial not null references members (id) on delete cascade
); );
CREATE INDEX IF NOT EXISTS idx_switch_members_switch
ON switch_members USING btree (
switch ASC NULLS LAST
) INCLUDE (member);
create table if not exists webhooks create table if not exists webhooks
( (