github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/store/localcachelayer/user_layer.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package localcachelayer 5 6 import ( 7 "context" 8 "sort" 9 "sync" 10 11 "github.com/mattermost/mattermost-server/v5/model" 12 "github.com/mattermost/mattermost-server/v5/store" 13 "github.com/mattermost/mattermost-server/v5/store/sqlstore" 14 ) 15 16 type LocalCacheUserStore struct { 17 store.UserStore 18 rootStore *LocalCacheStore 19 userProfileByIdsMut sync.Mutex 20 userProfileByIdsInvalidations map[string]bool 21 } 22 23 func (s *LocalCacheUserStore) handleClusterInvalidateScheme(msg *model.ClusterMessage) { 24 if msg.Data == ClearCacheMessageData { 25 s.rootStore.userProfileByIdsCache.Purge() 26 } else { 27 s.userProfileByIdsMut.Lock() 28 s.userProfileByIdsInvalidations[msg.Data] = true 29 s.userProfileByIdsMut.Unlock() 30 s.rootStore.userProfileByIdsCache.Remove(msg.Data) 31 } 32 } 33 34 func (s *LocalCacheUserStore) handleClusterInvalidateProfilesInChannel(msg *model.ClusterMessage) { 35 if msg.Data == ClearCacheMessageData { 36 s.rootStore.profilesInChannelCache.Purge() 37 } else { 38 s.rootStore.profilesInChannelCache.Remove(msg.Data) 39 } 40 } 41 42 func (s *LocalCacheUserStore) ClearCaches() { 43 s.rootStore.userProfileByIdsCache.Purge() 44 s.rootStore.profilesInChannelCache.Purge() 45 46 if s.rootStore.metrics != nil { 47 s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Profile By Ids - Purge") 48 s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Profiles in Channel - Purge") 49 } 50 } 51 52 func (s *LocalCacheUserStore) InvalidateProfileCacheForUser(userId string) { 53 s.userProfileByIdsMut.Lock() 54 s.userProfileByIdsInvalidations[userId] = true 55 s.userProfileByIdsMut.Unlock() 56 s.rootStore.doInvalidateCacheCluster(s.rootStore.userProfileByIdsCache, userId) 57 58 if s.rootStore.metrics != nil { 59 s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Profile By Ids - Remove") 60 } 61 } 62 63 func (s *LocalCacheUserStore) InvalidateProfilesInChannelCacheByUser(userId string) { 64 keys, err := s.rootStore.profilesInChannelCache.Keys() 65 if err == nil { 66 for _, key := range keys { 67 var userMap map[string]*model.User 68 if err = s.rootStore.profilesInChannelCache.Get(key, &userMap); err == nil { 69 if _, userInCache := userMap[userId]; userInCache { 70 s.rootStore.doInvalidateCacheCluster(s.rootStore.profilesInChannelCache, key) 71 if s.rootStore.metrics != nil { 72 s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Profiles in Channel - Remove by User") 73 } 74 } 75 } 76 } 77 } 78 } 79 80 func (s *LocalCacheUserStore) InvalidateProfilesInChannelCache(channelID string) { 81 s.rootStore.doInvalidateCacheCluster(s.rootStore.profilesInChannelCache, channelID) 82 if s.rootStore.metrics != nil { 83 s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Profiles in Channel - Remove by Channel") 84 } 85 } 86 87 func (s *LocalCacheUserStore) GetAllProfilesInChannel(ctx context.Context, channelId string, allowFromCache bool) (map[string]*model.User, error) { 88 if allowFromCache { 89 var cachedMap map[string]*model.User 90 if err := s.rootStore.doStandardReadCache(s.rootStore.profilesInChannelCache, channelId, &cachedMap); err == nil { 91 return cachedMap, nil 92 } 93 } 94 95 userMap, err := s.UserStore.GetAllProfilesInChannel(ctx, channelId, allowFromCache) 96 if err != nil { 97 return nil, err 98 } 99 100 if allowFromCache { 101 s.rootStore.doStandardAddToCache(s.rootStore.profilesInChannelCache, channelId, model.UserMap(userMap)) 102 } 103 104 return userMap, nil 105 } 106 107 func (s *LocalCacheUserStore) GetProfileByIds(ctx context.Context, userIds []string, options *store.UserGetByIdsOpts, allowFromCache bool) ([]*model.User, error) { 108 if !allowFromCache { 109 return s.UserStore.GetProfileByIds(ctx, userIds, options, false) 110 } 111 112 if options == nil { 113 options = &store.UserGetByIdsOpts{} 114 } 115 116 users := []*model.User{} 117 remainingUserIds := make([]string, 0) 118 119 fromMaster := false 120 for _, userId := range userIds { 121 var cacheItem *model.User 122 if err := s.rootStore.doStandardReadCache(s.rootStore.userProfileByIdsCache, userId, &cacheItem); err == nil { 123 if options.Since == 0 || cacheItem.UpdateAt > options.Since { 124 users = append(users, cacheItem) 125 } 126 } else { 127 // If it was invalidated, then we need to query master. 128 s.userProfileByIdsMut.Lock() 129 if s.userProfileByIdsInvalidations[userId] { 130 fromMaster = true 131 // And then remove the key from the map. 132 delete(s.userProfileByIdsInvalidations, userId) 133 } 134 s.userProfileByIdsMut.Unlock() 135 remainingUserIds = append(remainingUserIds, userId) 136 } 137 } 138 139 if s.rootStore.metrics != nil { 140 s.rootStore.metrics.AddMemCacheHitCounter("Profile By Ids", float64(len(users))) 141 s.rootStore.metrics.AddMemCacheMissCounter("Profile By Ids", float64(len(remainingUserIds))) 142 } 143 144 if len(remainingUserIds) > 0 { 145 if fromMaster { 146 ctx = sqlstore.WithMaster(ctx) 147 } 148 remainingUsers, err := s.UserStore.GetProfileByIds(ctx, remainingUserIds, options, false) 149 if err != nil { 150 return nil, err 151 } 152 for _, user := range remainingUsers { 153 s.rootStore.doStandardAddToCache(s.rootStore.userProfileByIdsCache, user.Id, user) 154 users = append(users, user) 155 } 156 } 157 158 return users, nil 159 } 160 161 // Get is a cache wrapper around the SqlStore method to get a user profile by id. 162 // It checks if the user entry is present in the cache, returning the entry from cache 163 // if it is present. Otherwise, it fetches the entry from the store and stores it in the 164 // cache. 165 func (s *LocalCacheUserStore) Get(ctx context.Context, id string) (*model.User, error) { 166 var cacheItem *model.User 167 if err := s.rootStore.doStandardReadCache(s.rootStore.userProfileByIdsCache, id, &cacheItem); err == nil { 168 if s.rootStore.metrics != nil { 169 s.rootStore.metrics.AddMemCacheHitCounter("Profile By Id", float64(1)) 170 } 171 return cacheItem, nil 172 } 173 if s.rootStore.metrics != nil { 174 s.rootStore.metrics.AddMemCacheMissCounter("Profile By Id", float64(1)) 175 } 176 177 // If it was invalidated, then we need to query master. 178 s.userProfileByIdsMut.Lock() 179 if s.userProfileByIdsInvalidations[id] { 180 ctx = sqlstore.WithMaster(ctx) 181 // And then remove the key from the map. 182 delete(s.userProfileByIdsInvalidations, id) 183 } 184 s.userProfileByIdsMut.Unlock() 185 186 user, err := s.UserStore.Get(ctx, id) 187 if err != nil { 188 return nil, err 189 } 190 s.rootStore.doStandardAddToCache(s.rootStore.userProfileByIdsCache, id, user) 191 return user, nil 192 } 193 194 // GetMany is a cache wrapper around the SqlStore method to get a user profiles by ids. 195 // It checks if the user entries are present in the cache, returning the entries from cache 196 // if it is present. Otherwise, it fetches the entries from the store and stores it in the 197 // cache. 198 func (s *LocalCacheUserStore) GetMany(ctx context.Context, ids []string) ([]*model.User, error) { 199 // we are doing a loop instead of caching the full set in the cache because the number of permutations that we can have 200 // in this func is making caching of the total set not beneficial. 201 var cachedUsers []*model.User 202 var notCachedUserIds []string 203 uniqIDs := dedup(ids) 204 205 fromMaster := false 206 for _, id := range uniqIDs { 207 var cachedUser *model.User 208 if err := s.rootStore.doStandardReadCache(s.rootStore.userProfileByIdsCache, id, &cachedUser); err == nil { 209 if s.rootStore.metrics != nil { 210 s.rootStore.metrics.AddMemCacheHitCounter("Profile By Id", float64(1)) 211 } 212 cachedUsers = append(cachedUsers, cachedUser) 213 } else { 214 if s.rootStore.metrics != nil { 215 s.rootStore.metrics.AddMemCacheMissCounter("Profile By Id", float64(1)) 216 } 217 // If it was invalidated, then we need to query master. 218 s.userProfileByIdsMut.Lock() 219 if s.userProfileByIdsInvalidations[id] { 220 fromMaster = true 221 // And then remove the key from the map. 222 delete(s.userProfileByIdsInvalidations, id) 223 } 224 s.userProfileByIdsMut.Unlock() 225 226 notCachedUserIds = append(notCachedUserIds, id) 227 } 228 } 229 230 if len(notCachedUserIds) > 0 { 231 if fromMaster { 232 ctx = sqlstore.WithMaster(ctx) 233 } 234 dbUsers, err := s.UserStore.GetMany(ctx, notCachedUserIds) 235 if err != nil { 236 return nil, err 237 } 238 for _, user := range dbUsers { 239 s.rootStore.doStandardAddToCache(s.rootStore.userProfileByIdsCache, user.Id, user) 240 cachedUsers = append(cachedUsers, user) 241 } 242 } 243 244 return cachedUsers, nil 245 } 246 247 func dedup(elements []string) []string { 248 if len(elements) == 0 { 249 return elements 250 } 251 252 sort.Strings(elements) 253 254 j := 0 255 for i := 1; i < len(elements); i++ { 256 if elements[j] == elements[i] { 257 continue 258 } 259 j++ 260 // preserve the original data 261 // in[i], in[j] = in[j], in[i] 262 // only set what is required 263 elements[j] = elements[i] 264 } 265 266 return elements[:j+1] 267 }