mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
MM-21211: Optimize Channelstore.UpdateLastViewedAt (#13529)
* MM-21211: Optimize Channelstore.UpdateLastViewedAt For postgres, we use CTEs to do both queries at one and return the data. For mysql, CTEs aren't available until version 8. So we just optimize the existing query. - We use IN instead of multiple OR conditions for the channel membership check. According to mysql https://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html#operator_in: If no type conversion is needed for the values in the IN() list, they are all constants of the same type, and expr can be compared to each of them as a value of the same type (possibly after type conversion), an optimization takes place. The values the list are sorted and the search for expr is done using a binary search, which makes the IN() operation very quick. - We use the previous column LastViewedAt to replace the value of LastUpdateAt instead of doing a switch case again. https://dev.mysql.com/doc/refman/5.6/en/ansi-diff-update.html If you access a column from the table to be updated in an expression, UPDATE uses the current value of the column. Results (ran with getchannel profile): Postgres: | Metric | Baseline | Actual | Delta | Delta % | | --- | --- | --- | --- | --- | | Hits | 1594 | 1574 | -20 | -1.25% | Error Rate | 0.00% | 0.00% | 0% | 0% | | Mean Response Time | 7.08ms | 6.36ms | -0.72ms | -10.20% | | Median Response Time | 5.00ms | 4.00ms | -1.00ms | -20.00% | | 95th Percentile | 9.00ms | 8.00ms | -1.00ms | -11.11% | Mysql: | Metric | Baseline | Actual | Delta | Delta % | | --- | --- | --- | --- | --- | | Hits | 1600 | 1566 | -34 | -2.12% | Error Rate | 0.00% | 0.00% | 0% | 0% | | Mean Response Time | 7.39ms | 7.02ms | -0.37ms | -4.94% | | Median Response Time | 6.00ms | 6.00ms | 0ms | 0% | | 95th Percentile | 10.00ms | 10.00ms | 0ms | 0% | * Add comment on using CTEs for mysql8 * incorporate review comments Co-authored-by: mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
@@ -1652,19 +1652,8 @@ func (s SqlChannelStore) PermanentDeleteMembersByUser(userId string) *model.AppE
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string) (map[string]int64, *model.AppError) {
|
||||
props := make(map[string]interface{})
|
||||
|
||||
updateIdQuery := ""
|
||||
for index, channelId := range channelIds {
|
||||
if len(updateIdQuery) > 0 {
|
||||
updateIdQuery += " OR "
|
||||
}
|
||||
|
||||
props["channelId"+strconv.Itoa(index)] = channelId
|
||||
updateIdQuery += "ChannelId = :channelId" + strconv.Itoa(index)
|
||||
}
|
||||
|
||||
selectIdQuery := strings.Replace(updateIdQuery, "ChannelId", "Id", -1)
|
||||
keys, props := MapStringsToQueryParams(channelIds, "Channel")
|
||||
props["UserId"] = userId
|
||||
|
||||
var lastPostAtTimes []struct {
|
||||
Id string
|
||||
@@ -1672,21 +1661,51 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string)
|
||||
TotalMsgCount int64
|
||||
}
|
||||
|
||||
selectQuery := "SELECT Id, LastPostAt, TotalMsgCount FROM Channels WHERE (" + selectIdQuery + ")"
|
||||
query := `SELECT Id, LastPostAt, TotalMsgCount FROM Channels WHERE Id IN ` + keys
|
||||
// TODO: use a CTE for mysql too when version 8 becomes the minimum supported version.
|
||||
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
|
||||
query = `WITH c AS ( ` + query + `),
|
||||
updated AS (
|
||||
UPDATE
|
||||
ChannelMembers cm
|
||||
SET
|
||||
MentionCount = 0,
|
||||
MsgCount = greatest(cm.MsgCount, c.TotalMsgCount),
|
||||
LastViewedAt = greatest(cm.LastViewedAt, c.LastPostAt),
|
||||
LastUpdateAt = greatest(cm.LastViewedAt, c.LastPostAt)
|
||||
FROM c
|
||||
WHERE cm.UserId = :UserId
|
||||
AND c.Id=cm.ChannelId
|
||||
)
|
||||
SELECT Id, LastPostAt FROM c`
|
||||
}
|
||||
|
||||
if _, err := s.GetMaster().Select(&lastPostAtTimes, selectQuery, props); err != nil || len(lastPostAtTimes) <= 0 {
|
||||
var extra string
|
||||
_, err := s.GetMaster().Select(&lastPostAtTimes, query, props)
|
||||
if err != nil || len(lastPostAtTimes) == 0 {
|
||||
status := http.StatusInternalServerError
|
||||
var extra string
|
||||
if err == nil {
|
||||
status = http.StatusBadRequest
|
||||
extra = "No channels found"
|
||||
} else {
|
||||
extra = err.Error()
|
||||
}
|
||||
return nil, model.NewAppError("SqlChannelStore.UpdateLastViewedAt", "store.sql_channel.update_last_viewed_at.app_error", nil, "channel_ids="+strings.Join(channelIds, ",")+", user_id="+userId+", "+extra, status)
|
||||
|
||||
return nil, model.NewAppError("SqlChannelStore.UpdateLastViewedAt",
|
||||
"store.sql_channel.update_last_viewed_at.app_error",
|
||||
nil,
|
||||
"channel_ids="+strings.Join(channelIds, ",")+", user_id="+userId+", "+extra,
|
||||
status)
|
||||
}
|
||||
|
||||
times := map[string]int64{}
|
||||
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
|
||||
for _, t := range lastPostAtTimes {
|
||||
times[t.Id] = t.LastPostAt
|
||||
}
|
||||
return times, nil
|
||||
}
|
||||
|
||||
msgCountQuery := ""
|
||||
lastViewedQuery := ""
|
||||
for index, t := range lastPostAtTimes {
|
||||
@@ -1701,33 +1720,16 @@ func (s SqlChannelStore) UpdateLastViewedAt(channelIds []string, userId string)
|
||||
props["channelId"+strconv.Itoa(index)] = t.Id
|
||||
}
|
||||
|
||||
var updateQuery string
|
||||
|
||||
if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
|
||||
updateQuery = `UPDATE
|
||||
ChannelMembers
|
||||
SET
|
||||
MentionCount = 0,
|
||||
MsgCount = CAST(CASE ChannelId ` + msgCountQuery + ` END AS BIGINT),
|
||||
LastViewedAt = CAST(CASE ChannelId ` + lastViewedQuery + ` END AS BIGINT),
|
||||
LastUpdateAt = CAST(CASE ChannelId ` + lastViewedQuery + ` END AS BIGINT)
|
||||
WHERE
|
||||
UserId = :UserId
|
||||
AND (` + updateIdQuery + `)`
|
||||
} else if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
||||
updateQuery = `UPDATE
|
||||
updateQuery := `UPDATE
|
||||
ChannelMembers
|
||||
SET
|
||||
MentionCount = 0,
|
||||
MsgCount = CASE ChannelId ` + msgCountQuery + ` END,
|
||||
LastViewedAt = CASE ChannelId ` + lastViewedQuery + ` END,
|
||||
LastUpdateAt = CASE ChannelId ` + lastViewedQuery + ` END
|
||||
LastUpdateAt = LastViewedAt
|
||||
WHERE
|
||||
UserId = :UserId
|
||||
AND (` + updateIdQuery + `)`
|
||||
}
|
||||
|
||||
props["UserId"] = userId
|
||||
AND ChannelId IN ` + keys
|
||||
|
||||
if _, err := s.GetMaster().Exec(updateQuery, props); err != nil {
|
||||
return nil, model.NewAppError("SqlChannelStore.UpdateLastViewedAt", "store.sql_channel.update_last_viewed_at.app_error", nil, "channel_ids="+strings.Join(channelIds, ",")+", user_id="+userId+", "+err.Error(), http.StatusInternalServerError)
|
||||
|
||||
Reference in New Issue
Block a user