github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/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/masterhung0112/hk_server/v5/model" 12 "github.com/masterhung0112/hk_server/v5/store" 13 "github.com/masterhung0112/hk_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 len(remainingUserIds) > 0 { 140 if fromMaster { 141 ctx = sqlstore.WithMaster(ctx) 142 } 143 remainingUsers, err := s.UserStore.GetProfileByIds(ctx, remainingUserIds, options, false) 144 if err != nil { 145 return nil, err 146 } 147 for _, user := range remainingUsers { 148 s.rootStore.doStandardAddToCache(s.rootStore.userProfileByIdsCache, user.Id, user) 149 users = append(users, user) 150 } 151 } 152 153 return users, nil 154 } 155 156 // Get is a cache wrapper around the SqlStore method to get a user profile by id. 157 // It checks if the user entry is present in the cache, returning the entry from cache 158 // if it is present. Otherwise, it fetches the entry from the store and stores it in the 159 // cache. 160 func (s *LocalCacheUserStore) Get(ctx context.Context, id string) (*model.User, error) { 161 var cacheItem *model.User 162 if err := s.rootStore.doStandardReadCache(s.rootStore.userProfileByIdsCache, id, &cacheItem); err == nil { 163 if s.rootStore.metrics != nil { 164 s.rootStore.metrics.AddMemCacheHitCounter("Profile By Id", float64(1)) 165 } 166 return cacheItem, nil 167 } 168 if s.rootStore.metrics != nil { 169 s.rootStore.metrics.AddMemCacheMissCounter("Profile By Id", float64(1)) 170 } 171 172 // If it was invalidated, then we need to query master. 173 s.userProfileByIdsMut.Lock() 174 if s.userProfileByIdsInvalidations[id] { 175 ctx = sqlstore.WithMaster(ctx) 176 // And then remove the key from the map. 177 delete(s.userProfileByIdsInvalidations, id) 178 } 179 s.userProfileByIdsMut.Unlock() 180 181 user, err := s.UserStore.Get(ctx, id) 182 if err != nil { 183 return nil, err 184 } 185 s.rootStore.doStandardAddToCache(s.rootStore.userProfileByIdsCache, id, user) 186 return user, nil 187 } 188 189 // GetMany is a cache wrapper around the SqlStore method to get a user profiles by ids. 190 // It checks if the user entries are present in the cache, returning the entries from cache 191 // if it is present. Otherwise, it fetches the entries from the store and stores it in the 192 // cache. 193 func (s *LocalCacheUserStore) GetMany(ctx context.Context, ids []string) ([]*model.User, error) { 194 // we are doing a loop instead of caching the full set in the cache because the number of permutations that we can have 195 // in this func is making caching of the total set not beneficial. 196 var cachedUsers []*model.User 197 var notCachedUserIds []string 198 uniqIDs := dedup(ids) 199 200 fromMaster := false 201 for _, id := range uniqIDs { 202 var cachedUser *model.User 203 if err := s.rootStore.doStandardReadCache(s.rootStore.userProfileByIdsCache, id, &cachedUser); err == nil { 204 if s.rootStore.metrics != nil { 205 s.rootStore.metrics.AddMemCacheHitCounter("Profile By Id", float64(1)) 206 } 207 cachedUsers = append(cachedUsers, cachedUser) 208 } else { 209 if s.rootStore.metrics != nil { 210 s.rootStore.metrics.AddMemCacheMissCounter("Profile By Id", float64(1)) 211 } 212 // If it was invalidated, then we need to query master. 213 s.userProfileByIdsMut.Lock() 214 if s.userProfileByIdsInvalidations[id] { 215 fromMaster = true 216 // And then remove the key from the map. 217 delete(s.userProfileByIdsInvalidations, id) 218 } 219 s.userProfileByIdsMut.Unlock() 220 221 notCachedUserIds = append(notCachedUserIds, id) 222 } 223 } 224 225 if len(notCachedUserIds) > 0 { 226 if fromMaster { 227 ctx = sqlstore.WithMaster(ctx) 228 } 229 dbUsers, err := s.UserStore.GetMany(ctx, notCachedUserIds) 230 if err != nil { 231 return nil, err 232 } 233 for _, user := range dbUsers { 234 s.rootStore.doStandardAddToCache(s.rootStore.userProfileByIdsCache, user.Id, user) 235 cachedUsers = append(cachedUsers, user) 236 } 237 } 238 239 return cachedUsers, nil 240 } 241 242 func dedup(elements []string) []string { 243 if len(elements) == 0 { 244 return elements 245 } 246 247 sort.Strings(elements) 248 249 j := 0 250 for i := 1; i < len(elements); i++ { 251 if elements[j] == elements[i] { 252 continue 253 } 254 j++ 255 // preserve the original data 256 // in[i], in[j] = in[j], in[i] 257 // only set what is required 258 elements[j] = elements[i] 259 } 260 261 return elements[:j+1] 262 }