-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add *AndRole indexes to usersChannels #4340
Conversation
This was leftover behavior from the move to create-query, but is actually unnecessary.
This adds two new indexes to `usersChannels` and rewrites all queries to
use them:
- `userIdAndRole`
- `channelIdAndRole`
"Role" is one of the roles a user can have in a channel, and is based on
the `is{Role}` fields, i.e. "Role" can be one of "blocked", "pending",
"owner", "moderator", "owner".
Note that the index prefers roles in that order, so even if a write
query is inconsistent and a member has `isMember: true` but also
`isBlocked: true` the user will be counted as blocked.
----
This is modeled after the same thing we did for `usersCommunities`, but
I realized that adding 5 indexes to a table as big as `usersCommunities`
and `usersChannels` is pretty expensive storage-wise. (I could've also
implemented the same indexes as `usersCommunities`, i.e.
`userIdAndIsMember`, `userIdAndIsBlocked`, etc.)
This achieves the same thing and makes all queries be indexed, but
requires a lot less storage. We should probably also move the
usersCommunities indexes to this `*AndRole` system...
Still the same content, just ordered differently due to new queries.
api/models/thread.js
Outdated
| .getAll(currentUser, { index: 'userId' }) | ||
| .filter({ isBlocked: false, isMember: true }) | ||
| .getAll( | ||
| [evalUser, 'member'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be currentUser not evalUser?
api/models/thread.js
Outdated
| .getAll(currentUser, { index: 'userId' }) | ||
| .filter({ isBlocked: false, isMember: true }) | ||
| .getAll( | ||
| [evalUser, 'member'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currentUser not evalUser right?
| .table('usersChannels') | ||
| .getAll(userId, { index: 'userId' }) | ||
| .filter({ channelId, isBlocked: false }) | ||
| .getAll([userId, channelId], { index: 'userIdAndChannelId' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we still want the isBlocked filter here right? Otherwise someone could spoof joining a channel via a direct POST request
| .getAll(channelId, { index: 'channelId' }) | ||
| .filter({ isMember: true }) | ||
| .getAll( | ||
| [channelId, 'member'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify: this automatically handles duplication? e.g. someone is a moderator and a member, it should only count as one membership
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, since the role index can only have one value the users that have a role of "member" are exclusive of other users who have the roles "moderator" or "owner". See my comment below: #4340 (comment)
| .table('usersChannels') | ||
| .getAll(userId, { index: 'userId' }) | ||
| .filter({ isMember: true }) | ||
| .getAll([userId, 'member'], [userId, 'moderator'], [userId, 'owner'], { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've noticed you've done this check in a few places. If we're just trying to get members, why are you using all 3 indexes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because owners and moderators are also members, but their roles are "owner" and "moderator"!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't true, so we can simplify this! If you are a moderator or owner you also are isMember: true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't, unfortunately!
Indexes can only have a single value, not multiple values. The "role" index is set to the highest one based on this precedence:
- Blocked
- Pending
- Owner
- Moderator
- Member
This means:
- If both
isMemberandisOwnerare true, the users role will be"owner". - If both
isMemberandisModeratorare true, the users role will be"moderator". - If both
isMemberandisBlockedare true (for whatever reason, bad db data most likely) the the role will be"blocked".
That means if we want to get all members we also have to fetch the users who have moderator and owner roles, on top of the ones that have member roles.
Does that make sense?
|
Great reviews, will dig further into this tomorrow unless you want to dig into this and finish it off today! |
Will be testing some other work today and trying to fix some other issues with Chronos, so feel free to finish up as you have time this week ;) |
|
Cleaned up those copy-and-paste goofs in the queries with |
|
Sure thing @mxstbr. One question about this new index: how is the performance? Do all the branches end up taking longer than a normal query without an index? |
|
Indexes are computed at write-time, rather than read-time, so those |
| .table('usersChannels') | ||
| .getAll(channelId, { index: 'channelId' }) | ||
| .filter({ userId, isBlocked: true }) | ||
| .getAll([userId, channelId], { index: 'userIdAndChannelId' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, one more spot: I think we want to filter isBlocked: true to ensure that it's only approving a blocked user
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Committing
|
Afaict in my local testing everything is working well! I double checked search filtering and profile filtering based on private communities and channels, and everything is holding up. One thing I noticed: at some point along the way we made it impossible to fetch a private channel where you're not a member. This means we never render the 'request to join private channel' view :P Not high pri, but we probably want that request-to-join screen back at some point. |
|
Gonna merge and add this to 2.7.8 |
This adds two new indexes to
usersChannelsand rewrites all queries to use them:userIdAndRolechannelIdAndRole"Role" is one of the roles a user can have in a channel, and is based on the
is{Role}fields, i.e. "Role" can be one of "blocked", "pending", "owner", "moderator", "owner".Note that the index prefers roles in that order, so even if a write query is inconsistent and a member has
isMember: truebut alsoisBlocked: truethe user will be counted as blocked.This is modeled after the same thing we did for
usersCommunities, but I realized that adding 5 indexes to a table as big asusersCommunitiesandusersChannelsis pretty expensive storage-wise. (I could've also implemented the same indexes asusersCommunities, i.e.userIdAndIsMember,userIdAndIsBlocked, etc.)This achieves the same thing and makes all queries be indexed, but requires a lot less storage. We should probably also move the usersCommunities indexes to this
*AndRolesystem...Status
Deploy after merge (delete what needn't be deployed)
Run database migrations (delete if no migration was added)
YES
Related issues
Based on #4339