perf(store): index (Receiver, Date) so tell history streams sorted
GetTellHistoryWithSender filters on Receiver and orders by Date DESC. Without a matching index SQLite sorted the whole receiver history into a temp b-tree before yielding row one -- measured 10 ms to first row against 9621 tells, all of it under TabsListLock, which defeats the early break in the caller. (Receiver, ChatType, Date) does not help: the ChatType IN filter sits between the equality prefix and the sort column, so the temp b-tree stays. Verified on a real database: plan now reads SEARCH ... USING INDEX idx_messages_receiver_date with no sort step. No SQL LIMIT -- an earlier 500-row cap was removed in v1.4.10 because it cut less-frequent partners off the back of the window, and 83% of partners have fewer than 21 tells in total. The migration dispatcher is cumulative, so Migrate5 is appended to every existing case, not just the new one.
This commit is contained in:
@@ -279,18 +279,25 @@ internal class MessageStore : IDisposable
|
||||
migrationsToDo.Add(Migrate2);
|
||||
migrationsToDo.Add(Migrate3);
|
||||
migrationsToDo.Add(Migrate4);
|
||||
migrationsToDo.Add(Migrate5);
|
||||
break;
|
||||
case 1:
|
||||
migrationsToDo.Add(Migrate2);
|
||||
migrationsToDo.Add(Migrate3);
|
||||
migrationsToDo.Add(Migrate4);
|
||||
migrationsToDo.Add(Migrate5);
|
||||
break;
|
||||
case 2:
|
||||
migrationsToDo.Add(Migrate3);
|
||||
migrationsToDo.Add(Migrate4);
|
||||
migrationsToDo.Add(Migrate5);
|
||||
break;
|
||||
case 3:
|
||||
migrationsToDo.Add(Migrate4);
|
||||
migrationsToDo.Add(Migrate5);
|
||||
break;
|
||||
case 4:
|
||||
migrationsToDo.Add(Migrate5);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -430,6 +437,23 @@ internal class MessageStore : IDisposable
|
||||
SetMigrationVersion(4);
|
||||
}
|
||||
|
||||
private void Migrate5()
|
||||
{
|
||||
_logger.LogInformation("Running migration 5: Add (Receiver, Date) index for tell history");
|
||||
|
||||
// GetTellHistoryWithSender filters on Receiver and orders by Date DESC.
|
||||
// Without a matching index SQLite sorts the whole receiver history into a
|
||||
// temp b-tree before returning row one, which defeats the early break in
|
||||
// the caller. (Receiver, ChatType, Date) does NOT help: the ChatType IN
|
||||
// filter sits between the equality prefix and the sort column.
|
||||
using var cmd = Connection.CreateCommand();
|
||||
cmd.CommandText =
|
||||
"CREATE INDEX IF NOT EXISTS idx_messages_receiver_date ON messages (Receiver, Date);";
|
||||
cmd.ExecuteNonQuery();
|
||||
|
||||
SetMigrationVersion(5);
|
||||
}
|
||||
|
||||
private void SetMigrationVersion(int version)
|
||||
{
|
||||
_logger.LogInformation($"Setting version {version}");
|
||||
@@ -1014,7 +1038,8 @@ internal class MessageStore : IDisposable
|
||||
}
|
||||
|
||||
// Returns up to `limit` tells exchanged with the named player, oldest-first.
|
||||
// SQL narrows by Receiver + ChatType via the (Receiver, Date) index, then
|
||||
// SQL narrows by Receiver + ChatType via the (Receiver, Date) index (migration
|
||||
// 5; before that the ordering fell back to a temp b-tree over all rows), then
|
||||
// the client-side loop runs PlayerPayload comparison and breaks once
|
||||
// `limit` partner matches accumulate. Earlier versions had a hardcoded
|
||||
// 500-row scan cap that cut less-frequent pinned partners off the back of
|
||||
|
||||
Reference in New Issue
Block a user