break up getThreadsForUser, leverage errgroup (#19709)

* MM-42282: handle teamId parameter correctly

As per https://community-daily.mattermost.com/core/pl/ugs7ue6e4j8a7cgegk1bxje8to, `ThreadStore.GetThreadsForUser` accepts a `teamId` parameter, but incorrectly handles an empty value of `""` as looking only for channels with an empty `teamId` (aka DMs and GMs) instead of finding all channels and effectively ignoring the team property.

Fixes: https://mattermost.atlassian.net/browse/MM-42282

* break up getThreadsForUser, leverage errgroup

This change breaks up `GetThreadsForUser` in the `ThreadStore` into its constituent `GetTotalUnreadThreads`, `GetTotalThreads`, `GetTotalUnreadMentions`, and the original `GetThreadsForUser` but now solely returning the thread structures. Instead of a monolithic method at the store level, the application layer now handles calling bulk requests, leveraging `errgroup` for simpler parallelization.

This change brings with it a few benefits:
* Simpler code, including more idiomatic usage of squirrel
* Simpler SQL, joining tables only when configured conditions require same. (No performance benefit here, since an unused LEFT JOIN generally has no overhead.)
* Discrete Grafana metrics for each store method, giving us better insight into the performance characteristics in play.
* **Performance boost**: reduced overhead when clearing push notifications.

This last point is what prompted the re-re-reactoring in this PR. As I broke things up, I realized that `clearPushNotificationSync` only used the `TotalUnreadMentions`, but asked for the count of total threads and total unread threads. By exposing the discrete methods, this code path avoids two aggregate queries. We clear notifications when marking a thread as read, and when marking a channel with unread mentions as viewed, so I expect we'll see at least a modest boost to performance from simply not wasting these cycles anymore.

No performance improvements are expected from this PR for the general case of using `GetThreadsForUser` to populate the threads view.

* never discard errors from building queries

* no MustSql

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Jesse Hallam
2022-03-15 10:29:00 -03:00
коммит произвёл GitHub
родитель 4bf3d6a7f5
Коммит 47c44a9b7d
10 изменённых файлов: 850 добавлений и 307 удалений

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

@@ -233,15 +233,15 @@ func (_m *ThreadStore) GetThreadUnreadReplyCount(threadMembership *model.ThreadM
}
// GetThreadsForUser provides a mock function with given fields: userId, teamID, opts
func (_m *ThreadStore) GetThreadsForUser(userId string, teamID string, opts model.GetUserThreadsOpts) (*model.Threads, error) {
func (_m *ThreadStore) GetThreadsForUser(userId string, teamID string, opts model.GetUserThreadsOpts) ([]*model.ThreadResponse, error) {
ret := _m.Called(userId, teamID, opts)
var r0 *model.Threads
if rf, ok := ret.Get(0).(func(string, string, model.GetUserThreadsOpts) *model.Threads); ok {
var r0 []*model.ThreadResponse
if rf, ok := ret.Get(0).(func(string, string, model.GetUserThreadsOpts) []*model.ThreadResponse); ok {
r0 = rf(userId, teamID, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Threads)
r0 = ret.Get(0).([]*model.ThreadResponse)
}
}
@@ -255,6 +255,69 @@ func (_m *ThreadStore) GetThreadsForUser(userId string, teamID string, opts mode
return r0, r1
}
// GetTotalThreads provides a mock function with given fields: userId, teamID, opts
func (_m *ThreadStore) GetTotalThreads(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) {
ret := _m.Called(userId, teamID, opts)
var r0 int64
if rf, ok := ret.Get(0).(func(string, string, model.GetUserThreadsOpts) int64); ok {
r0 = rf(userId, teamID, opts)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string, model.GetUserThreadsOpts) error); ok {
r1 = rf(userId, teamID, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetTotalUnreadMentions provides a mock function with given fields: userId, teamID, opts
func (_m *ThreadStore) GetTotalUnreadMentions(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) {
ret := _m.Called(userId, teamID, opts)
var r0 int64
if rf, ok := ret.Get(0).(func(string, string, model.GetUserThreadsOpts) int64); ok {
r0 = rf(userId, teamID, opts)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string, model.GetUserThreadsOpts) error); ok {
r1 = rf(userId, teamID, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetTotalUnreadThreads provides a mock function with given fields: userId, teamID, opts
func (_m *ThreadStore) GetTotalUnreadThreads(userId string, teamID string, opts model.GetUserThreadsOpts) (int64, error) {
ret := _m.Called(userId, teamID, opts)
var r0 int64
if rf, ok := ret.Get(0).(func(string, string, model.GetUserThreadsOpts) int64); ok {
r0 = rf(userId, teamID, opts)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string, model.GetUserThreadsOpts) error); ok {
r1 = rf(userId, teamID, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MaintainMembership provides a mock function with given fields: userID, postID, opts
func (_m *ThreadStore) MaintainMembership(userID string, postID string, opts store.ThreadMembershipOpts) (*model.ThreadMembership, error) {
ret := _m.Called(userID, postID, opts)