MM-41752: Batch update mention increment (#19596)
Previously, we were incrementing mentions one-by-one all concurrently in an unbounded fashion. This would cause a big spike in memory usage if there were an `@all` mention in a large channel. We fix this by changing the SQL query to take all userIDs at once. https://mattermost.atlassian.net/browse/MM-41752 ```release-note NONE ``` Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
dd100a3a69
Коммит
768fe43d3a
@@ -196,7 +196,6 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
|
||||
}
|
||||
|
||||
mentionedUsersList := make(model.StringArray, 0, len(mentions.Mentions))
|
||||
updateMentionChans := []chan *model.AppError{}
|
||||
mentionAutofollowChans := []chan *model.AppError{}
|
||||
threadParticipants := map[string]bool{post.UserId: true}
|
||||
participantMemberships := map[string]*model.ThreadMembership{}
|
||||
@@ -290,32 +289,16 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
|
||||
}
|
||||
for id := range mentions.Mentions {
|
||||
mentionedUsersList = append(mentionedUsersList, id)
|
||||
|
||||
umc := make(chan *model.AppError, 1)
|
||||
go func(userID string) {
|
||||
defer close(umc)
|
||||
nErr := a.Srv().Store.Channel().IncrementMentionCount(post.ChannelId, userID, post.RootId == "")
|
||||
if nErr != nil {
|
||||
umc <- model.NewAppError("SendNotifications", "app.channel.increment_mention_count.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
umc <- nil
|
||||
}(id)
|
||||
updateMentionChans = append(updateMentionChans, umc)
|
||||
}
|
||||
|
||||
// Make sure all mention updates are complete to prevent race conditions.
|
||||
// Probably better to batch these DB updates in the future
|
||||
// MUST be completed before push notifications send
|
||||
for _, umc := range updateMentionChans {
|
||||
if err := <-umc; err != nil {
|
||||
mlog.Warn(
|
||||
"Failed to update mention count",
|
||||
mlog.String("post_id", post.Id),
|
||||
mlog.String("channel_id", post.ChannelId),
|
||||
mlog.Err(err),
|
||||
)
|
||||
}
|
||||
nErr := a.Srv().Store.Channel().IncrementMentionCount(post.ChannelId, mentionedUsersList, post.RootId == "")
|
||||
if nErr != nil {
|
||||
mlog.Warn(
|
||||
"Failed to update mention count",
|
||||
mlog.String("post_id", post.Id),
|
||||
mlog.String("channel_id", post.ChannelId),
|
||||
mlog.Err(nErr),
|
||||
)
|
||||
}
|
||||
|
||||
// Log the problems that might have occurred while auto following the thread
|
||||
|
||||
@@ -4635,10 +4635,6 @@
|
||||
"id": "app.channel.get_unread.app_error",
|
||||
"translation": "Unable to get the channel unread messages."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.increment_mention_count.app_error",
|
||||
"translation": "Unable to increment the mention count."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.migrate_channel_members.select.app_error",
|
||||
"translation": "Failed to select the batch of channel members."
|
||||
|
||||
@@ -1748,7 +1748,7 @@ func (s *OpenTracingLayerChannelStore) GroupSyncedChannelCount() (int64, error)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelID string, userID string, isRoot bool) error {
|
||||
func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ChannelStore.IncrementMentionCount")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
@@ -1757,7 +1757,7 @@ func (s *OpenTracingLayerChannelStore) IncrementMentionCount(channelID string, u
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userID, isRoot)
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
|
||||
@@ -1975,11 +1975,11 @@ func (s *RetryLayerChannelStore) GroupSyncedChannelCount() (int64, error) {
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerChannelStore) IncrementMentionCount(channelID string, userID string, isRoot bool) error {
|
||||
func (s *RetryLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userID, isRoot)
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2549,24 +2549,32 @@ func (s SqlChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID s
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) IncrementMentionCount(channelId string, userId string, isRoot bool) error {
|
||||
func (s SqlChannelStore) IncrementMentionCount(channelId string, userIDs []string, isRoot bool) error {
|
||||
now := model.GetMillis()
|
||||
|
||||
rootInc := 0
|
||||
if isRoot {
|
||||
rootInc = 1
|
||||
}
|
||||
_, err := s.GetMasterX().Exec(
|
||||
`UPDATE
|
||||
ChannelMembers
|
||||
SET
|
||||
MentionCount = MentionCount + 1,
|
||||
MentionCountRoot = MentionCountRoot + ?,
|
||||
LastUpdateAt = ?
|
||||
WHERE
|
||||
UserId = ?
|
||||
AND ChannelId = ?`, rootInc, now, userId, channelId)
|
||||
|
||||
sql, args, err := s.getQueryBuilder().
|
||||
Update("ChannelMembers").
|
||||
Set("MentionCount", sq.Expr("MentionCount + 1")).
|
||||
Set("MentionCountRoot", sq.Expr("MentionCountRoot + ?", rootInc)).
|
||||
Set("LastUpdateAt", now).
|
||||
Where(sq.Eq{
|
||||
"UserId": userIDs,
|
||||
"ChannelId": channelId,
|
||||
}).
|
||||
ToSql()
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to Update ChannelMembers with channelId=%s and userId=%s", channelId, userId)
|
||||
return errors.Wrap(err, "IncrementMentionCount_Tosql")
|
||||
}
|
||||
|
||||
_, err = s.GetMasterX().Exec(sql, args...)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to Update ChannelMembers with channelId=%s and userId=%v", channelId, userIDs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ type ChannelStore interface {
|
||||
UpdateLastViewedAt(channelIds []string, userID string, updateThreads bool) (map[string]int64, error)
|
||||
UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount, mentionCountRoot int, setUnreadCountRoot bool) (*model.ChannelUnreadAt, error)
|
||||
CountPostsAfter(channelID string, timestamp int64, userID string) (int, int, error)
|
||||
IncrementMentionCount(channelID string, userID string, isRoot bool) error
|
||||
IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error
|
||||
AnalyticsTypeCount(teamID string, channelType model.ChannelType) (int64, error)
|
||||
GetMembersForUser(teamID string, userID string) (model.ChannelMembers, error)
|
||||
GetTeamMembersForChannel(channelID string) ([]string, error)
|
||||
|
||||
@@ -4790,16 +4790,16 @@ func testChannelStoreIncrementMentionCount(t *testing.T, ss store.Store) {
|
||||
_, err := ss.Channel().SaveMember(&m1)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ss.Channel().IncrementMentionCount(m1.ChannelId, m1.UserId, false)
|
||||
err = ss.Channel().IncrementMentionCount(m1.ChannelId, []string{m1.UserId}, false)
|
||||
require.NoError(t, err, "failed to update")
|
||||
|
||||
err = ss.Channel().IncrementMentionCount(m1.ChannelId, "missing id", false)
|
||||
err = ss.Channel().IncrementMentionCount(m1.ChannelId, []string{"missing id"}, false)
|
||||
require.NoError(t, err, "failed to update")
|
||||
|
||||
err = ss.Channel().IncrementMentionCount("missing id", m1.UserId, false)
|
||||
err = ss.Channel().IncrementMentionCount("missing id", []string{m1.UserId}, false)
|
||||
require.NoError(t, err, "failed to update")
|
||||
|
||||
err = ss.Channel().IncrementMentionCount("missing id", "missing id", false)
|
||||
err = ss.Channel().IncrementMentionCount("missing id", []string{"missing id"}, false)
|
||||
require.NoError(t, err, "failed to update")
|
||||
}
|
||||
|
||||
|
||||
@@ -1508,13 +1508,13 @@ func (_m *ChannelStore) GroupSyncedChannelCount() (int64, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// IncrementMentionCount provides a mock function with given fields: channelID, userID, isRoot
|
||||
func (_m *ChannelStore) IncrementMentionCount(channelID string, userID string, isRoot bool) error {
|
||||
ret := _m.Called(channelID, userID, isRoot)
|
||||
// IncrementMentionCount provides a mock function with given fields: channelID, userIDs, isRoot
|
||||
func (_m *ChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
|
||||
ret := _m.Called(channelID, userIDs, isRoot)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, bool) error); ok {
|
||||
r0 = rf(channelID, userID, isRoot)
|
||||
if rf, ok := ret.Get(0).(func(string, []string, bool) error); ok {
|
||||
r0 = rf(channelID, userIDs, isRoot)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
@@ -2285,6 +2285,15 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
|
||||
_, nErr = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u2.Id}, -1)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
u3 := &model.User{}
|
||||
u3.Email = MakeEmail()
|
||||
u3.Username = "user3" + model.NewId()
|
||||
_, err = ss.User().Save(u3)
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, ss.User().PermanentDelete(u3.Id)) }()
|
||||
_, nErr = ss.Team().SaveMember(&model.TeamMember{TeamId: teamId, UserId: u3.Id}, -1)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
_, nErr = ss.Channel().Save(&c1, -1)
|
||||
require.NoError(t, nErr, "couldn't save item")
|
||||
|
||||
@@ -2301,6 +2310,14 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
|
||||
_, nErr = ss.Channel().SaveMember(&m2)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
m3 := model.ChannelMember{}
|
||||
m3.ChannelId = c1.Id
|
||||
m3.UserId = u3.Id
|
||||
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
|
||||
_, nErr = ss.Channel().SaveMember(&m3)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
m1.ChannelId = c2.Id
|
||||
m2.ChannelId = c2.Id
|
||||
|
||||
@@ -2310,12 +2327,12 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
|
||||
p1 := model.Post{}
|
||||
p1.ChannelId = c1.Id
|
||||
p1.UserId = u1.Id
|
||||
p1.Message = "this is a message for @" + u2.Username
|
||||
p1.Message = "this is a message for @" + u2.Username + " and " + "@" + u3.Username
|
||||
|
||||
// Post one message with mention to open channel
|
||||
_, nErr = ss.Post().Save(&p1)
|
||||
require.NoError(t, nErr)
|
||||
nErr = ss.Channel().IncrementMentionCount(c1.Id, u2.Id, false)
|
||||
nErr = ss.Channel().IncrementMentionCount(c1.Id, []string{u2.Id, u3.Id}, false)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
// Post 2 messages without mention to direct channel
|
||||
@@ -2326,7 +2343,7 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
|
||||
|
||||
_, nErr = ss.Post().Save(&p2)
|
||||
require.NoError(t, nErr)
|
||||
nErr = ss.Channel().IncrementMentionCount(c2.Id, u2.Id, false)
|
||||
nErr = ss.Channel().IncrementMentionCount(c2.Id, []string{u2.Id}, false)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
p3 := model.Post{}
|
||||
@@ -2336,13 +2353,17 @@ func testUserUnreadCount(t *testing.T, ss store.Store) {
|
||||
_, nErr = ss.Post().Save(&p3)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
nErr = ss.Channel().IncrementMentionCount(c2.Id, u2.Id, false)
|
||||
nErr = ss.Channel().IncrementMentionCount(c2.Id, []string{u2.Id}, false)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
badge, unreadCountErr := ss.User().GetUnreadCount(u2.Id)
|
||||
require.NoError(t, unreadCountErr)
|
||||
require.Equal(t, int64(3), badge, "should have 3 unread messages")
|
||||
|
||||
badge, unreadCountErr = ss.User().GetUnreadCount(u3.Id)
|
||||
require.NoError(t, unreadCountErr)
|
||||
require.Equal(t, int64(1), badge, "should have 1 unread message")
|
||||
|
||||
badge, unreadCountErr = ss.User().GetUnreadCountForChannel(u2.Id, c1.Id)
|
||||
require.NoError(t, unreadCountErr)
|
||||
require.Equal(t, int64(1), badge, "should have 1 unread messages for that channel")
|
||||
|
||||
@@ -1605,10 +1605,10 @@ func (s *TimerLayerChannelStore) GroupSyncedChannelCount() (int64, error) {
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerChannelStore) IncrementMentionCount(channelID string, userID string, isRoot bool) error {
|
||||
func (s *TimerLayerChannelStore) IncrementMentionCount(channelID string, userIDs []string, isRoot bool) error {
|
||||
start := timemodule.Now()
|
||||
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userID, isRoot)
|
||||
err := s.ChannelStore.IncrementMentionCount(channelID, userIDs, isRoot)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
|
||||
Ссылка в новой задаче
Block a user