MM-55295: Improve DB performance of some APIs (#25318)

Load tests show that channelstore.GetMember and
channelstore.GetMembersForUser are among the chief
queries that take up CPU in the DB.

In this PR, we attempt some strategic optimizations to
reduce/optimize calls to channelstore.GetMember

1. Optimize  `(a *App) HasPermissionToChannel`

We replace GetChannelMember with GetAllChannelMembersForUser
because it's cache backed. So although it gets more data,
it does not hit the DB and saves some latency.

2. Optimize getPostsForChannelAroundLastUnread

We repace getChannelMember with getChannelMemberOnly
which is a lite version of the store call which queries
just the ChannelMembers table. This is because
in the app layer, we just use the LastViewedAt attribute.
Therefore, there is no reason to join with 5 tables when
a single table can do the job.

3. Optimize publishWebsocketEventForPermalinkPost

We use GetAllChannelMembersById instead of GetChannelMembersPage
which again joins with a lot of other tables.

4. Optimize countMentionsFromPost

Again, we replace GetChannelMember which is a costly call joining
multiple tables, with GetAllChannelMembersNotifyPropsForChannel
which is cache-backed and gives us just what we need in the app
layer - notify props.

```release-note
Make small optimizations in several DB calls:
- App.HasPermissionToChannel
- getPostsForChannelAroundLastUnread
- publishWebsocketEventForPermalinkPost
- countMentionsFromPost
```

https://mattermost.atlassian.net/browse/MM-55295

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Agniva De Sarker
2023-11-13 21:15:57 +05:30
коммит произвёл GitHub
родитель 1ed5d87342
Коммит ec88ab4ee9
12 изменённых файлов: 189 добавлений и 53 удалений

Просмотреть файл

@@ -245,6 +245,10 @@ func testChannelStoreSaveDirectChannel(t *testing.T, ss store.Store, s SqlStore)
require.NoError(t, nErr)
require.Len(t, members, 2, "should have saved 2 members")
userIDs, nErr := ss.Channel().GetAllChannelMemberIdsByChannelId(o1.Id)
require.NoError(t, nErr)
require.ElementsMatch(t, []string{u1.Id, u2.Id}, userIDs)
_, nErr = ss.Channel().SaveDirectChannel(&o1, &m1, &m2)
require.Error(t, nErr, "shouldn't be a able to update from save")
@@ -281,6 +285,10 @@ func testChannelStoreSaveDirectChannel(t *testing.T, ss store.Store, s SqlStore)
require.NoError(t, nErr)
require.Len(t, members, 1, "should have saved just 1 member")
userIDs, nErr = ss.Channel().GetAllChannelMemberIdsByChannelId(o1.Id)
require.NoError(t, nErr)
require.ElementsMatch(t, []string{u1.Id}, userIDs)
// Manually truncate Channels table until testlib can handle cleanups
s.GetMasterX().Exec("TRUNCATE Channels")
}
@@ -7472,6 +7480,10 @@ func testChannelStoreRemoveAllDeactivatedMembers(t *testing.T, ss store.Store, s
assert.NoError(t, err)
assert.Len(t, d1, 3)
userIDs, nErr := ss.Channel().GetAllChannelMemberIdsByChannelId(c1.Id)
require.NoError(t, nErr)
require.ElementsMatch(t, []string{u1.Id, u2.Id, u3.Id}, userIDs)
// Deactivate users 1 & 2.
u1.DeleteAt = model.GetMillis()
u2.DeleteAt = model.GetMillis()
@@ -7489,6 +7501,10 @@ func testChannelStoreRemoveAllDeactivatedMembers(t *testing.T, ss store.Store, s
assert.Len(t, d2, 1)
assert.Equal(t, u3.Id, d2[0].UserId)
userIDs, nErr = ss.Channel().GetAllChannelMemberIdsByChannelId(c1.Id)
require.NoError(t, nErr)
require.ElementsMatch(t, []string{u3.Id}, userIDs)
// Manually truncate Channels table until testlib can handle cleanups
s.GetMasterX().Exec("TRUNCATE Channels")
}

Просмотреть файл

@@ -432,8 +432,8 @@ func (_m *ChannelStore) GetAll(teamID string) ([]*model.Channel, error) {
return r0, r1
}
// GetAllChannelMembersById provides a mock function with given fields: id
func (_m *ChannelStore) GetAllChannelMembersById(id string) ([]string, error) {
// GetAllChannelMemberIdsByChannelId provides a mock function with given fields: id
func (_m *ChannelStore) GetAllChannelMemberIdsByChannelId(id string) ([]string, error) {
ret := _m.Called(id)
var r0 []string
@@ -1314,6 +1314,32 @@ func (_m *ChannelStore) GetMemberForPost(postID string, userID string) (*model.C
return r0, r1
}
// GetMemberOnly provides a mock function with given fields: ctx, channelID, userID
func (_m *ChannelStore) GetMemberOnly(ctx context.Context, channelID string, userID string) (*model.ChannelMember, error) {
ret := _m.Called(ctx, channelID, userID)
var r0 *model.ChannelMember
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.ChannelMember, error)); ok {
return rf(ctx, channelID, userID)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) *model.ChannelMember); ok {
r0 = rf(ctx, channelID, userID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.ChannelMember)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, channelID, userID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetMembers provides a mock function with given fields: channelID, offset, limit
func (_m *ChannelStore) GetMembers(channelID string, offset int, limit int) (model.ChannelMembers, error) {
ret := _m.Called(channelID, offset, limit)