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  }