Skip to content

perf(chat): partial indexes for the launch scans - #1977

Open
bmc08gt wants to merge 1 commit into
code/cashfrom
perf/chat-launch-indexes
Open

bmc08gt wants to merge 1 commit into
code/cashfrom
perf/chat-launch-indexes

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

Three chat_messages scans run at every login: failInterruptedSends (from recoverInterruptedSends, which blocks first paint at RealChatCoordinator.kt:169-170), hasDuplicateClientIds and chatsWithKeyPending. Each reads the whole table looking for rows that are almost never there.

Change

  • v53 adds one partial index per scan, holding only the rows that scan looks for: status = 'SENDING', encryption_state = 'KEY_PENDING', and (chat_id_hex, pending_client_id_hex) where pending_client_id_hex IS NOT NULL. Each WHERE repeats its scan's condition, because SQLite uses a partial index only for a query whose WHERE implies the index's.
  • @Index can't declare a WHERE. ChatMessageEntity declares the three indexes by the same names and columns, so Room's schema check passes (it compares name, columns and uniqueness, not the WHERE). As a result 53.json shows them as full indexes.
  • ChatPartialIndexes.ensure replaces any of the three that isn't partial. Migration52To53 (the spec on the 52→53 auto-migration) calls it after Room creates the indexes, and a RoomDatabase.Callback calls it on every open. The open hook covers fresh installs, and any later migration that rebuilds chat_messages from the entity and recreates them as full indexes.

Tests

ChatPartialIndexMigrationTest builds a v52 database from 52.json and upgrades it through Room. It checks that the rows survive, that all three indexes are partial, and that each scan returns what it did before. It also covers a fresh database, checks that each scan plans through its index, and checks that an index rebuilt without its WHERE is fixed on the next open.

Numbers

Chat-load harness, ChatArrivalBenchmarkTest.launchIndexExperiment, 1000 chats x 10,000 messages, median of the timed runs:

Scan code/cash this branch
hasDuplicateClientIds 2,612 ms 0.25 ms
failInterruptedSends 1,942 ms 0.41 ms
chatsWithKeyPending 2,574 ms 0.12 ms

The code/cash column is the harness baseline from 2026-11-06. The branch column is the harness's BEFORE phase run on this branch, with its seed database opened through ChatPartialIndexes.Callback. The harness's AFTER phase adds its own experiment indexes on top of these, so it isn't the comparison. The machine's load average was between 24 and 90 during the branch run.

Three chat_messages scans run at every login before the chat list
paints: failInterruptedSends (from recoverInterruptedSends, which blocks
first paint), hasDuplicateClientIds and chatsWithKeyPending. Each reads
the whole table for rows that are almost never there.

v53 adds one partial index per scan, each holding only the rows it looks
for: status = 'SENDING', encryption_state = 'KEY_PENDING', and
(chat_id_hex, pending_client_id_hex) where the client id is set. Each
WHERE repeats its scan's condition, since SQLite only uses a partial
index for a query whose WHERE implies the index's.

@Index can't express WHERE. The entity declares the same names and
columns so Room's schema check, which compares names, columns and
uniqueness and not the WHERE, accepts them, and 53.json shows full
indexes. ChatPartialIndexes.ensure replaces any of the three that isn't
partial: Migration52To53 calls it after the auto-migration creates them,
and a Callback calls it on every open, which covers a fresh install and
any later migration that rebuilds chat_messages from the entity.

ChatPartialIndexMigrationTest builds a v52 database from 52.json,
upgrades it through Room, and checks the rows survive, all three indexes
are partial and the scans return what they did. It also checks a fresh
database, that each scan's condition plans through its index, and that
an index rebuilt without its WHERE is fixed on the next open.
@bmc08gt bmc08gt self-assigned this Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant