mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-28 00:28:39 +00:00
plans: justify member role index
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
# Member support chats list: update from events instead of reloading all members
|
||||
# Member support chats: faster opening and sending
|
||||
|
||||
Two independent changes: the member support list is updated from events instead of reloading all members (opening), and group members get a role index for the moderator lookup (sending).
|
||||
|
||||
## Problem
|
||||
|
||||
A group owner with a large group opens the "Chat with members" list, then opens members' support chats one after another. The first chat opens quickly. Every later one takes several seconds to open.
|
||||
A group owner with a large group opens the "Chat with members" list, then opens members' support chats one after another. The first chat opens quickly. Every later one takes several seconds to open. Sending in a support chat is also slow, although a support message only goes to the moderators and the member.
|
||||
|
||||
## Cause
|
||||
|
||||
@@ -73,8 +75,36 @@ A member's first support message arrives as a `NewChatItems` event with that mem
|
||||
|
||||
- In channels (`useRelays`), opening a support chat runs the chat view's initialisation, which loads all members for relay groups on both platforms. So each support chat open in a channel still does a full member load. This was already the case before this change; the reported bug is in an ordinary group.
|
||||
|
||||
## Sending: index group members by role
|
||||
|
||||
**Cause.** A support-chat send gets its recipients from `getGroupModerators` (`getGroupRecipients`, `Library/Internal.hs`): `groupMemberQuery ... WHERE m.user_id = ? AND m.group_id = ? AND ... AND m.member_role IN (?,?,?)`. The only index is `idx_group_members_group_id (user_id, group_id)`, so SQLite reads every member row of the group and filters on the role. On the reporter's device this query took 178 ms on average and 4.8 s at most, over 80 calls, and it holds the single database connection while it runs.
|
||||
|
||||
**Fix.** Migration `20260926_member_role_index` (SQLite and Postgres) replaces `idx_group_members_group_id (user_id, group_id)` with `idx_group_members_group_id_member_role (user_id, group_id, member_role)`. The new index has the old one as a prefix, so every query that used the old index can use the new one, and the number of indexes on `group_members` stays the same.
|
||||
|
||||
**Measured** on a copy of a real database with 20,031 members in one group (11 moderators or above), same query text, warm cache:
|
||||
|
||||
| Query | Before | After |
|
||||
|---|---|---|
|
||||
| `getGroupModerators` | 8.3 ms | 0.13 ms |
|
||||
| `getGroupMembers` (all) | 204.6 ms | 198.4 ms |
|
||||
| `getGroupMembersForExpiration` | 7.0 ms | 6.6 ms |
|
||||
| lookup by `local_display_name` / `member_category` | 5.3 / 5.0 ms | 5.4 / 4.8 ms |
|
||||
| `DELETE` all group members (rolled back) | 458 ms | 417 ms |
|
||||
|
||||
Only the role-filtered queries change; the other rows are within noise, because those queries read every member row either way.
|
||||
|
||||
**Plans.** I compared `EXPLAIN QUERY PLAN` before and after for all 144 queries touching `group_members` in `chat_query_plans.txt`, with foreign keys on, on SQLite 3.39.2 (the version the apps bundle) and 3.40.1:
|
||||
- 135 plans are unchanged.
|
||||
- 4 now use `member_role` in the index: `getGroupModerators`, its two-role variant, and the two `member_role = ?` relay queries.
|
||||
- 5 read the same `(user_id, group_id)` range from the new index. The covering `DELETE` stays covering.
|
||||
- None gains a scan or a temp B-tree sort.
|
||||
- The only query that orders by `group_member_id` filters on `group_id` alone and never used this index.
|
||||
|
||||
Keeping the old index alongside was also checked: SQLite then chooses the new index for all 9 queries anyway, so the old one would only add write cost.
|
||||
|
||||
**Cost.** The one-off migration took 3.2 s to create the index and 0.9 s to drop the old one on a 520,155-member table (9 MB index). Role changes now also update the index, and role changes are rare.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Refresh only the viewed member on return** (`apiGroupMemberInfo`). Rejected: it still polls, and it misses changes to other members.
|
||||
- **A core query returning only members with support chats** (`support_chat_ts IS NOT NULL`). This would also speed up the first list load. It is a larger API change and can follow separately.
|
||||
- **A `(user_id, group_id, member_role)` index** for `getGroupModerators`, which runs on every support send. That is an independent change on a separate branch.
|
||||
|
||||
Reference in New Issue
Block a user