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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
1ed5d87342
Коммит
ec88ab4ee9
@@ -101,7 +101,6 @@ func (a *App) SessionHasPermissionToChannel(c request.CTX, session model.Session
|
||||
}
|
||||
|
||||
ids, err := a.Srv().Store().Channel().GetAllChannelMembersForUser(session.UserId, true, true)
|
||||
|
||||
var channelRoles []string
|
||||
if err == nil {
|
||||
if roles, ok := ids[channelID]; ok {
|
||||
@@ -299,17 +298,22 @@ func (a *App) HasPermissionToChannel(c request.CTX, askingUserId string, channel
|
||||
return false
|
||||
}
|
||||
|
||||
channelMember, err := a.GetChannelMember(c, channelID, askingUserId)
|
||||
// We call GetAllChannelMembersForUser instead of just getting
|
||||
// a single member from the DB, because it's cache backed
|
||||
// and this is a very frequent call.
|
||||
ids, err := a.Srv().Store().Channel().GetAllChannelMembersForUser(askingUserId, true, true)
|
||||
var channelRoles []string
|
||||
if err == nil {
|
||||
roles := channelMember.GetRoles()
|
||||
if a.RolesGrantPermission(roles, permission.Id) {
|
||||
return true
|
||||
if roles, ok := ids[channelID]; ok {
|
||||
channelRoles = strings.Fields(roles)
|
||||
if a.RolesGrantPermission(channelRoles, permission.Id) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var channel *model.Channel
|
||||
channel, err = a.GetChannel(c, channelID)
|
||||
if err == nil {
|
||||
channel, appErr := a.GetChannel(c, channelID)
|
||||
if appErr == nil {
|
||||
return a.HasPermissionToTeam(c, askingUserId, channel.TeamId, permission)
|
||||
}
|
||||
|
||||
|
||||
@@ -2063,6 +2063,21 @@ func (s *Server) getChannelMember(c request.CTX, channelID string, userID string
|
||||
return channelMember, nil
|
||||
}
|
||||
|
||||
func (s *Server) getChannelMemberOnly(c request.CTX, channelID string, userID string) (*model.ChannelMember, *model.AppError) {
|
||||
channelMember, err := s.Store().Channel().GetMemberOnly(c.Context(), channelID, userID)
|
||||
if err != nil {
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
case errors.As(err, &nfErr):
|
||||
return nil, model.NewAppError("getChannelMemberOnly", MissingChannelMemberError, nil, "", http.StatusNotFound).Wrap(err)
|
||||
default:
|
||||
return nil, model.NewAppError("getChannelMemberOnly", "app.channel.get_member.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return channelMember, nil
|
||||
}
|
||||
|
||||
func (a *App) GetChannelMembersPage(c request.CTX, channelID string, page, perPage int) (model.ChannelMembers, *model.AppError) {
|
||||
channelMembers, err := a.Srv().Store().Channel().GetMembers(channelID, page*perPage, perPage)
|
||||
if err != nil {
|
||||
|
||||
@@ -785,9 +785,9 @@ func (a *App) publishWebsocketEventForPermalinkPost(c request.CTX, post *model.P
|
||||
return false, err
|
||||
}
|
||||
|
||||
channelMembers, err := a.GetChannelMembersPage(c, post.ChannelId, 0, 10000000)
|
||||
if err != nil {
|
||||
return false, err
|
||||
userIDs, nErr := a.Srv().Store().Channel().GetAllChannelMemberIdsByChannelId(post.ChannelId)
|
||||
if nErr != nil {
|
||||
return false, model.NewAppError("publishWebsocketEventForPermalinkPost", "app.channel.get_members.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
permalinkPreviewedChannel, err := a.GetChannel(c, previewedPost.ChannelId)
|
||||
@@ -800,18 +800,18 @@ func (a *App) publishWebsocketEventForPermalinkPost(c request.CTX, post *model.P
|
||||
}
|
||||
|
||||
permalinkPreviewedPost := post.GetPreviewPost()
|
||||
for _, cm := range channelMembers {
|
||||
for _, userID := range userIDs {
|
||||
if permalinkPreviewedPost != nil {
|
||||
post.Metadata.Embeds[0].Data = permalinkPreviewedPost
|
||||
}
|
||||
|
||||
postForUser := a.sanitizePostMetadataForUserAndChannel(c, post, permalinkPreviewedPost, permalinkPreviewedChannel, cm.UserId)
|
||||
postForUser := a.sanitizePostMetadataForUserAndChannel(c, post, permalinkPreviewedPost, permalinkPreviewedChannel, userID)
|
||||
|
||||
// Using DeepCopy here to avoid a race condition
|
||||
// between publishing the event and setting the "post" data value below.
|
||||
messageCopy := message.DeepCopy()
|
||||
broadcastCopy := messageCopy.GetBroadcast()
|
||||
broadcastCopy.UserId = cm.UserId
|
||||
broadcastCopy.UserId = userID
|
||||
messageCopy.SetBroadcast(broadcastCopy)
|
||||
|
||||
postJSON, jsonErr := postForUser.ToJSON()
|
||||
@@ -1260,7 +1260,7 @@ func (a *App) AddCursorIdsForPostList(originalList *model.PostList, afterPost, b
|
||||
func (a *App) GetPostsForChannelAroundLastUnread(c request.CTX, channelID, userID string, limitBefore, limitAfter int, skipFetchThreads bool, collapsedThreads, collapsedThreadsExtended bool) (*model.PostList, *model.AppError) {
|
||||
var member *model.ChannelMember
|
||||
var err *model.AppError
|
||||
if member, err = a.GetChannelMember(c, channelID, userID); err != nil {
|
||||
if member, err = a.Srv().getChannelMemberOnly(c, channelID, userID); err != nil {
|
||||
return nil, err
|
||||
} else if member.LastViewedAt == 0 {
|
||||
return model.NewPostList(), nil
|
||||
@@ -1836,9 +1836,9 @@ func (a *App) countThreadMentions(c request.CTX, user *model.User, post *model.P
|
||||
// countMentionsFromPost returns the number of posts in the post's channel that mention the user after and including the
|
||||
// given post.
|
||||
func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model.Post) (int, int, int, *model.AppError) {
|
||||
channel, err := a.GetChannel(c, post.ChannelId)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
channel, appErr := a.GetChannel(c, post.ChannelId)
|
||||
if appErr != nil {
|
||||
return 0, 0, 0, appErr
|
||||
}
|
||||
|
||||
if channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup {
|
||||
@@ -1859,15 +1859,15 @@ func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model
|
||||
return count, countRoot, urgentCount, nil
|
||||
}
|
||||
|
||||
channelMember, err := a.GetChannelMember(c, channel.Id, user.Id)
|
||||
members, err := a.Srv().Store().Channel().GetAllChannelMembersNotifyPropsForChannel(channel.Id, true)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
return 0, 0, 0, model.NewAppError("countMentionsFromPost", "app.channel.count_posts_since.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
keywords := MentionKeywords{}
|
||||
keywords.AddUser(
|
||||
user,
|
||||
channelMember.NotifyProps,
|
||||
members[user.Id],
|
||||
&model.Status{Status: model.StatusOnline}, // Assume the user is online since they would've triggered this
|
||||
true, // Assume channel mentions are always allowed for simplicity
|
||||
)
|
||||
@@ -1877,9 +1877,9 @@ func (a *App) countMentionsFromPost(c request.CTX, user *model.User, post *model
|
||||
// A mapping of thread root IDs to whether or not a post in that thread mentions the user
|
||||
mentionedByThread := make(map[string]bool)
|
||||
|
||||
thread, err := a.GetPostThread(post.Id, model.GetPostsOptions{}, user.Id)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
thread, appErr := a.GetPostThread(post.Id, model.GetPostsOptions{}, user.Id)
|
||||
if appErr != nil {
|
||||
return 0, 0, 0, appErr
|
||||
}
|
||||
|
||||
count := 0
|
||||
|
||||
Ссылка в новой задаче
Block a user