From 0361e8b97ed40648d36d06c8af7823509446cf5c Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Mon, 13 Jan 2020 21:16:51 +0530 Subject: [PATCH] MM-21210: Add LRU cache for ChannelStore.GetMembersForUser (#13593) Automatic Merge --- model/cluster_message.go | 1 + services/cache/cache.go | 3 + services/cache/lru/lru.go | 19 +++ store/localcachelayer/channel_layer.go | 136 ++++++++++++++++++++ store/localcachelayer/channel_layer_test.go | 66 +++++++++- store/localcachelayer/layer.go | 93 +++++++++---- store/localcachelayer/main_test.go | 13 ++ store/storetest/mocks/ChannelStore.go | 10 ++ 8 files changed, 314 insertions(+), 27 deletions(-) diff --git a/model/cluster_message.go b/model/cluster_message.go index ec62eb7588..a7c8d8834d 100644 --- a/model/cluster_message.go +++ b/model/cluster_message.go @@ -17,6 +17,7 @@ const ( CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_POSTS = "inv_channel_posts" CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_NOTIFY_PROPS = "inv_channel_members_notify_props" CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS = "inv_channel_members" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_FOR_USER = "inv_channel_members_user" CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_BY_NAME = "inv_channel_name" CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL = "inv_channel" CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_GUEST_COUNT = "inv_channel_guest_count" diff --git a/services/cache/cache.go b/services/cache/cache.go index b0f7335b98..c445e977df 100644 --- a/services/cache/cache.go +++ b/services/cache/cache.go @@ -31,6 +31,9 @@ type Cache interface { // Remove deletes the value for a key. Remove(key interface{}) + // RemoveByPrefix deletes all keys containing the given prefix string. + RemoveByPrefix(prefix string) + // Keys returns a slice of the keys in the cache. Keys() []interface{} diff --git a/services/cache/lru/lru.go b/services/cache/lru/lru.go index 0292a2a9ae..ec0bad3033 100644 --- a/services/cache/lru/lru.go +++ b/services/cache/lru/lru.go @@ -12,6 +12,7 @@ package lru import ( "container/list" + "strings" "sync" "time" @@ -187,6 +188,24 @@ func (c *Cache) Remove(key interface{}) { } } +// RemoveByPrefix deletes all keys containing the given prefix string. +func (c *Cache) RemoveByPrefix(prefix string) { + c.lock.Lock() + defer c.lock.Unlock() + + for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() { + e := ent.Value.(*entry) + if e.generation == c.currentGeneration { + keyString := e.key.(string) + if strings.HasPrefix(keyString, prefix) { + if ent, ok := c.items[e.key]; ok { + c.removeElement(ent) + } + } + } + } +} + // Keys returns a slice of the keys in the cache, from oldest to newest. func (c *Cache) Keys() []interface{} { c.lock.RLock() diff --git a/store/localcachelayer/channel_layer.go b/store/localcachelayer/channel_layer.go index 75d751fd73..5d191f80fa 100644 --- a/store/localcachelayer/channel_layer.go +++ b/store/localcachelayer/channel_layer.go @@ -45,17 +45,29 @@ func (s *LocalCacheChannelStore) handleClusterInvalidateChannelById(msg *model.C } } +func (s *LocalCacheChannelStore) handleClusterInvalidateChannelMembersForUser(msg *model.ClusterMessage) { + if msg.Data == CLEAR_CACHE_MESSAGE_DATA { + s.rootStore.channelMembersForUserCache.Purge() + return + } + + // Remove keys with prefix msg.Data + s.rootStore.channelMembersForUserCache.RemoveByPrefix(msg.Data) +} + func (s LocalCacheChannelStore) ClearCaches() { s.rootStore.doClearCacheCluster(s.rootStore.channelMemberCountsCache) s.rootStore.doClearCacheCluster(s.rootStore.channelPinnedPostCountsCache) s.rootStore.doClearCacheCluster(s.rootStore.channelGuestCountCache) s.rootStore.doClearCacheCluster(s.rootStore.channelByIdCache) + s.rootStore.doClearCacheCluster(s.rootStore.channelMembersForUserCache) s.ChannelStore.ClearCaches() if s.rootStore.metrics != nil { s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Channel Pinned Post Counts - Purge") s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Channel Member Counts - Purge") s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Channel Guest Count - Purge") s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Channel - Purge") + s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Channel Members For User - Purge") } } @@ -73,6 +85,44 @@ func (s LocalCacheChannelStore) InvalidateMemberCount(channelId string) { } } +// invalidateMembersForUser removes all keys from channelMembersForUserCache +// with the prefix of the given userID. +// We cannot simply call doInvalidateCacheCluster because we need to remove +// keys with a prefix rather than remove a single key. +func (s LocalCacheChannelStore) invalidateMembersForUser(userId string) { + s.rootStore.channelMembersForUserCache.RemoveByPrefix(userId) + if s.rootStore.cluster != nil { + msg := &model.ClusterMessage{ + Event: model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_FOR_USER, + SendType: model.CLUSTER_SEND_BEST_EFFORT, + Data: userId, + } + s.rootStore.cluster.SendClusterMessage(msg) + } + + if s.rootStore.metrics != nil { + s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Channel Members For User - Remove by UserId") + } +} + +// invalidateMembersForAllUsers purges the entire channelMembersForUserCache cache. +func (s LocalCacheChannelStore) invalidateMembersForAllUsers() { + s.rootStore.channelMembersForUserCache.Purge() + + if s.rootStore.cluster != nil { + msg := &model.ClusterMessage{ + Event: model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_FOR_USER, + SendType: model.CLUSTER_SEND_BEST_EFFORT, + Data: CLEAR_CACHE_MESSAGE_DATA, + } + s.rootStore.cluster.SendClusterMessage(msg) + } + + if s.rootStore.metrics != nil { + s.rootStore.metrics.IncrementMemCacheInvalidationCounter("Channel Members For User - Purge") + } +} + func (s LocalCacheChannelStore) InvalidateGuestCount(channelId string) { s.rootStore.doInvalidateCacheCluster(s.rootStore.channelGuestCountCache, channelId) if s.rootStore.metrics != nil { @@ -150,6 +200,92 @@ func (s LocalCacheChannelStore) GetPinnedPostCount(channelId string, allowFromCa return count, nil } +// SaveMember is a wrapper method for the underlying store which just takes +// care of the cache invalidation. +func (s LocalCacheChannelStore) SaveMember(member *model.ChannelMember) (*model.ChannelMember, *model.AppError) { + defer s.invalidateMembersForUser(member.UserId) + return s.ChannelStore.SaveMember(member) +} + +// UpdateMember is a wrapper method for the underlying store which just takes +// care of the cache invalidation. +func (s LocalCacheChannelStore) UpdateMember(member *model.ChannelMember) (*model.ChannelMember, *model.AppError) { + defer s.invalidateMembersForUser(member.UserId) + return s.ChannelStore.UpdateMember(member) +} + +// RemoveMember is a wrapper method for the underlying store which just takes +// care of the cache invalidation. +func (s LocalCacheChannelStore) RemoveMember(channelId, userId string) *model.AppError { + defer s.invalidateMembersForUser(userId) + return s.ChannelStore.RemoveMember(channelId, userId) +} + +// UpdateLastViewedAt is a wrapper method for the underlying store which just takes +// care of the cache invalidation. +func (s LocalCacheChannelStore) UpdateLastViewedAt(channelIds []string, userId string) (map[string]int64, *model.AppError) { + defer s.invalidateMembersForUser(userId) + return s.ChannelStore.UpdateLastViewedAt(channelIds, userId) +} + +// UpdateLastViewedAtPost is a wrapper method for the underlying store which just takes +// care of the cache invalidation. +func (s LocalCacheChannelStore) UpdateLastViewedAtPost(unreadPost *model.Post, userID string, mentionCount int) (*model.ChannelUnreadAt, *model.AppError) { + defer s.invalidateMembersForUser(userID) + return s.ChannelStore.UpdateLastViewedAtPost(unreadPost, userID, mentionCount) +} + +// PermanentDeleteMembersByChannel is a wrapper method for the underlying store which just takes +// care of the cache invalidation. +func (s LocalCacheChannelStore) PermanentDeleteMembersByChannel(channelId string) *model.AppError { + defer s.invalidateMembersForAllUsers() + return s.ChannelStore.PermanentDeleteMembersByChannel(channelId) +} + +// PermanentDeleteMembersByUser is a wrapper method for the underlying store which just takes +// care of the cache invalidation. +func (s LocalCacheChannelStore) PermanentDeleteMembersByUser(userId string) *model.AppError { + defer s.invalidateMembersForUser(userId) + return s.ChannelStore.PermanentDeleteMembersByUser(userId) +} + +// RemoveAllDeactivatedMembers is a wrapper method for the underlying store which just takes +// care of the cache invalidation. +func (s LocalCacheChannelStore) RemoveAllDeactivatedMembers(channelId string) *model.AppError { + defer s.invalidateMembersForAllUsers() + return s.ChannelStore.RemoveAllDeactivatedMembers(channelId) +} + +// ClearAllCustomRoleAssignments is a wrapper method for the underlying store which just takes +// care of the cache invalidation. +func (s LocalCacheChannelStore) ClearAllCustomRoleAssignments() *model.AppError { + defer s.invalidateMembersForAllUsers() + return s.ChannelStore.ClearAllCustomRoleAssignments() +} + +// IncrementMentionCount is a wrapper method for the underlying store which just takes +// care of the cache invalidation. +func (s LocalCacheChannelStore) IncrementMentionCount(channelId, userId string) *model.AppError { + defer s.invalidateMembersForUser(userId) + return s.ChannelStore.IncrementMentionCount(channelId, userId) +} + +// GetMembersForUser is a cache wrapper method for ChannelStore. +func (s LocalCacheChannelStore) GetMembersForUser(teamId, userId string) (*model.ChannelMembers, *model.AppError) { + key := userId + "-" + teamId + if members := s.rootStore.doStandardReadCache(s.rootStore.channelMembersForUserCache, key); members != nil { + return members.(*model.ChannelMembers), nil + } + + members, err := s.ChannelStore.GetMembersForUser(teamId, userId) + if err != nil { + return nil, err + } + + s.rootStore.doStandardAddToCache(s.rootStore.channelMembersForUserCache, key, members) + return members, nil +} + func (s LocalCacheChannelStore) Get(id string, allowFromCache bool) (*model.Channel, *model.AppError) { if allowFromCache { diff --git a/store/localcachelayer/channel_layer_test.go b/store/localcachelayer/channel_layer_test.go index ef3046d2ac..894849823d 100644 --- a/store/localcachelayer/channel_layer_test.go +++ b/store/localcachelayer/channel_layer_test.go @@ -4,9 +4,10 @@ package localcachelayer import ( - "github.com/mattermost/mattermost-server/v5/model" "testing" + "github.com/mattermost/mattermost-server/v5/model" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -296,3 +297,66 @@ func TestChannelStoreChannel(t *testing.T) { mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "Get", 2) }) } + +func TestChannelStoreChannelMembersForUserCache(t *testing.T) { + fakeChannelMembers := model.ChannelMembers([]model.ChannelMember{ + { + UserId: "123", + }, + }) + + t.Run("first call not cached, second cached and returning same data", func(t *testing.T) { + mockStore := getMockStore() + mockCacheProvider := getMockCacheProvider() + cachedStore := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) + + gotMembers, err := cachedStore.Channel().GetMembersForUser("teamId", "userId1") + require.Nil(t, err) + assert.Equal(t, &fakeChannelMembers, gotMembers) + mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMembersForUser", 1) + + _, _ = cachedStore.Channel().GetMembersForUser("teamId", "userId1") + mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMembersForUser", 1) + }) + + t.Run("first call not cached, invalidate, and then not cached again", func(t *testing.T) { + mockStore := getMockStore() + mockCacheProvider := getMockCacheProvider() + cachedStore := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) + + gotMembers, err := cachedStore.Channel().GetMembersForUser("teamId", "userId1") + require.Nil(t, err) + assert.Equal(t, &fakeChannelMembers, gotMembers) + mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMembersForUser", 1) + + // invalidate + cachedStore.Channel().(LocalCacheChannelStore).invalidateMembersForUser("userId1") + + _, _ = cachedStore.Channel().GetMembersForUser("teamId", "userId1") + mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMembersForUser", 2) + }) + + t.Run("cache multiple items, invalidate all, then nothing cached ", func(t *testing.T) { + mockStore := getMockStore() + mockCacheProvider := getMockCacheProvider() + cachedStore := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) + + gotMembers, err := cachedStore.Channel().GetMembersForUser("teamId", "userId1") + require.Nil(t, err) + assert.Equal(t, &fakeChannelMembers, gotMembers) + mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMembersForUser", 1) + + gotMembers, err = cachedStore.Channel().GetMembersForUser("teamId", "userId2") + require.Nil(t, err) + assert.Equal(t, &fakeChannelMembers, gotMembers) + mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMembersForUser", 2) + + // invalidate all + cachedStore.Channel().(LocalCacheChannelStore).invalidateMembersForAllUsers() + + _, _ = cachedStore.Channel().GetMembersForUser("teamId", "userId1") + mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMembersForUser", 3) + _, _ = cachedStore.Channel().GetMembersForUser("teamId", "userId2") + mockStore.Channel().(*mocks.ChannelStore).AssertNumberOfCalls(t, "GetMembersForUser", 4) + }) +} diff --git a/store/localcachelayer/layer.go b/store/localcachelayer/layer.go index 1c4085e049..495ab29c04 100644 --- a/store/localcachelayer/layer.go +++ b/store/localcachelayer/layer.go @@ -35,6 +35,9 @@ const ( CHANNEL_MEMBERS_COUNTS_CACHE_SIZE = model.CHANNEL_CACHE_SIZE CHANNEL_MEMBERS_COUNTS_CACHE_SEC = 30 * 60 + CHANNEL_MEMBERS_FOR_USER_SIZE = model.CHANNEL_CACHE_SIZE + CHANNEL_MEMBERS_FOR_USER_CACHE_SEC = 30 * 60 + LAST_POSTS_CACHE_SIZE = 20000 LAST_POSTS_CACHE_SEC = 30 * 60 @@ -59,34 +62,45 @@ const ( type LocalCacheStore struct { store.Store - metrics einterfaces.MetricsInterface - cluster einterfaces.ClusterInterface - reaction LocalCacheReactionStore - reactionCache cache.Cache - role LocalCacheRoleStore - roleCache cache.Cache - scheme LocalCacheSchemeStore - schemeCache cache.Cache - emoji LocalCacheEmojiStore - emojiCacheById cache.Cache - emojiIdCacheByName cache.Cache + metrics einterfaces.MetricsInterface + cluster einterfaces.ClusterInterface + + reaction LocalCacheReactionStore + reactionCache cache.Cache + + role LocalCacheRoleStore + roleCache cache.Cache + + scheme LocalCacheSchemeStore + schemeCache cache.Cache + + emoji LocalCacheEmojiStore + emojiCacheById cache.Cache + emojiIdCacheByName cache.Cache + channel LocalCacheChannelStore channelMemberCountsCache cache.Cache channelGuestCountCache cache.Cache channelPinnedPostCountsCache cache.Cache channelByIdCache cache.Cache - webhook LocalCacheWebhookStore - webhookCache cache.Cache - post LocalCachePostStore - postLastPostsCache cache.Cache - lastPostTimeCache cache.Cache - user LocalCacheUserStore - userProfileByIdsCache cache.Cache - profilesInChannelCache cache.Cache - team LocalCacheTeamStore - teamAllTeamIdsForUserCache cache.Cache - termsOfService LocalCacheTermsOfServiceStore - termsOfServiceCache cache.Cache + channelMembersForUserCache cache.Cache + + webhook LocalCacheWebhookStore + webhookCache cache.Cache + + post LocalCachePostStore + postLastPostsCache cache.Cache + lastPostTimeCache cache.Cache + + user LocalCacheUserStore + userProfileByIdsCache cache.Cache + profilesInChannelCache cache.Cache + + team LocalCacheTeamStore + teamAllTeamIdsForUserCache cache.Cache + + termsOfService LocalCacheTermsOfServiceStore + termsOfServiceCache cache.Cache } func NewLocalCacheLayer(baseStore store.Store, metrics einterfaces.MetricsInterface, cluster einterfaces.ClusterInterface, cacheProvider cache.Provider) LocalCacheStore { @@ -96,54 +110,80 @@ func NewLocalCacheLayer(baseStore store.Store, metrics einterfaces.MetricsInterf cluster: cluster, metrics: metrics, } + + // Reactions localCacheStore.reactionCache = cacheProvider.NewCacheWithParams(REACTION_CACHE_SIZE, "Reaction", REACTION_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS) localCacheStore.reaction = LocalCacheReactionStore{ReactionStore: baseStore.Reaction(), rootStore: &localCacheStore} + + // Roles localCacheStore.roleCache = cacheProvider.NewCacheWithParams(ROLE_CACHE_SIZE, "Role", ROLE_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES) localCacheStore.role = LocalCacheRoleStore{RoleStore: baseStore.Role(), rootStore: &localCacheStore} + + // Schemes localCacheStore.schemeCache = cacheProvider.NewCacheWithParams(SCHEME_CACHE_SIZE, "Scheme", SCHEME_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES) localCacheStore.scheme = LocalCacheSchemeStore{SchemeStore: baseStore.Scheme(), rootStore: &localCacheStore} + + // Webhooks localCacheStore.webhookCache = cacheProvider.NewCacheWithParams(WEBHOOK_CACHE_SIZE, "Webhook", WEBHOOK_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_WEBHOOKS) localCacheStore.webhook = LocalCacheWebhookStore{WebhookStore: baseStore.Webhook(), rootStore: &localCacheStore} + + // Emojis localCacheStore.emojiCacheById = cacheProvider.NewCacheWithParams(EMOJI_CACHE_SIZE, "EmojiById", EMOJI_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_BY_ID) localCacheStore.emojiIdCacheByName = cacheProvider.NewCacheWithParams(EMOJI_CACHE_SIZE, "EmojiByName", EMOJI_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_ID_BY_NAME) localCacheStore.emoji = LocalCacheEmojiStore{EmojiStore: baseStore.Emoji(), rootStore: &localCacheStore} + // Channels localCacheStore.channelPinnedPostCountsCache = cacheProvider.NewCacheWithParams(CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SIZE, "ChannelPinnedPostsCounts", CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_PINNEDPOSTS_COUNTS) localCacheStore.channelMemberCountsCache = cacheProvider.NewCacheWithParams(CHANNEL_MEMBERS_COUNTS_CACHE_SIZE, "ChannelMemberCounts", CHANNEL_MEMBERS_COUNTS_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBER_COUNTS) localCacheStore.channelGuestCountCache = cacheProvider.NewCacheWithParams(CHANNEL_GUEST_COUNT_CACHE_SIZE, "ChannelGuestsCount", CHANNEL_GUEST_COUNT_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_GUEST_COUNT) localCacheStore.channelByIdCache = cacheProvider.NewCacheWithParams(model.CHANNEL_CACHE_SIZE, "channelById", CHANNEL_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL) - + localCacheStore.channelMembersForUserCache = cacheProvider.NewCacheWithParams(model.CHANNEL_CACHE_SIZE, "ChannelMembersForUser", CHANNEL_MEMBERS_FOR_USER_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_FOR_USER) localCacheStore.channel = LocalCacheChannelStore{ChannelStore: baseStore.Channel(), rootStore: &localCacheStore} + // Posts localCacheStore.postLastPostsCache = cacheProvider.NewCacheWithParams(LAST_POSTS_CACHE_SIZE, "LastPost", LAST_POSTS_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_LAST_POSTS) localCacheStore.lastPostTimeCache = cacheProvider.NewCacheWithParams(LAST_POST_TIME_CACHE_SIZE, "LastPostTime", LAST_POST_TIME_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_LAST_POST_TIME) - localCacheStore.post = LocalCachePostStore{PostStore: baseStore.Post(), rootStore: &localCacheStore} + // TOS localCacheStore.termsOfServiceCache = cacheProvider.NewCacheWithParams(TERMS_OF_SERVICE_CACHE_SIZE, "TermsOfService", TERMS_OF_SERVICE_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_TERMS_OF_SERVICE) localCacheStore.termsOfService = LocalCacheTermsOfServiceStore{TermsOfServiceStore: baseStore.TermsOfService(), rootStore: &localCacheStore} + + // Users localCacheStore.userProfileByIdsCache = cacheProvider.NewCacheWithParams(USER_PROFILE_BY_ID_CACHE_SIZE, "UserProfileByIds", USER_PROFILE_BY_ID_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_PROFILE_BY_IDS) localCacheStore.profilesInChannelCache = cacheProvider.NewCacheWithParams(PROFILES_IN_CHANNEL_CACHE_SIZE, "ProfilesInChannel", PROFILES_IN_CHANNEL_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_PROFILE_IN_CHANNEL) localCacheStore.user = LocalCacheUserStore{UserStore: baseStore.User(), rootStore: &localCacheStore} + + // Teams localCacheStore.teamAllTeamIdsForUserCache = cacheProvider.NewCacheWithParams(TEAM_CACHE_SIZE, "Team", TEAM_CACHE_SEC, model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_TEAMS) localCacheStore.team = LocalCacheTeamStore{TeamStore: baseStore.Team(), rootStore: &localCacheStore} if cluster != nil { cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS, localCacheStore.reaction.handleClusterInvalidateReaction) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, localCacheStore.role.handleClusterInvalidateRole) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES, localCacheStore.scheme.handleClusterInvalidateScheme) - cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_LAST_POST_TIME, localCacheStore.post.handleClusterInvalidateLastPostTime) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_WEBHOOKS, localCacheStore.webhook.handleClusterInvalidateWebhook) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_BY_ID, localCacheStore.emoji.handleClusterInvalidateEmojiById) cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_ID_BY_NAME, localCacheStore.emoji.handleClusterInvalidateEmojiIdByName) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_PINNEDPOSTS_COUNTS, localCacheStore.channel.handleClusterInvalidateChannelPinnedPostCount) cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBER_COUNTS, localCacheStore.channel.handleClusterInvalidateChannelMemberCounts) cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_GUEST_COUNT, localCacheStore.channel.handleClusterInvalidateChannelGuestCounts) cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL, localCacheStore.channel.handleClusterInvalidateChannelById) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_FOR_USER, localCacheStore.channel.handleClusterInvalidateChannelMembersForUser) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_LAST_POSTS, localCacheStore.post.handleClusterInvalidateLastPosts) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_LAST_POST_TIME, localCacheStore.post.handleClusterInvalidateLastPostTime) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_TERMS_OF_SERVICE, localCacheStore.termsOfService.handleClusterInvalidateTermsOfService) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_PROFILE_BY_IDS, localCacheStore.user.handleClusterInvalidateScheme) cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_PROFILE_IN_CHANNEL, localCacheStore.user.handleClusterInvalidateProfilesInChannel) + cluster.RegisterClusterMessageHandler(model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_TEAMS, localCacheStore.team.handleClusterInvalidateTeam) } return localCacheStore @@ -243,6 +283,7 @@ func (s *LocalCacheStore) Invalidate() { s.doClearCacheCluster(s.emojiCacheById) s.doClearCacheCluster(s.emojiIdCacheByName) s.doClearCacheCluster(s.channelMemberCountsCache) + s.doClearCacheCluster(s.channelMembersForUserCache) s.doClearCacheCluster(s.channelPinnedPostCountsCache) s.doClearCacheCluster(s.channelGuestCountCache) s.doClearCacheCluster(s.channelByIdCache) diff --git a/store/localcachelayer/main_test.go b/store/localcachelayer/main_test.go index 2f75014c4d..901293dfe0 100644 --- a/store/localcachelayer/main_test.go +++ b/store/localcachelayer/main_test.go @@ -80,6 +80,12 @@ func getMockCacheProvider() *mocks.CacheProvider { mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(lru.New(128)) + mockCacheProvider.On("NewCacheWithParams", + mock.AnythingOfType("int"), + "ChannelMembersForUser", + mock.AnythingOfType("int64"), + mock.AnythingOfType("string")).Return(lru.New(128)) + mockCacheProvider.On("NewCacheWithParams", mock.AnythingOfType("int"), "LastPost", @@ -179,6 +185,13 @@ func getMockStore() *mocks.Store { mockPinnedPostsCount := int64(10) mockChannelStore.On("GetPinnedPostCount", "id", true).Return(mockPinnedPostsCount, nil) mockChannelStore.On("GetPinnedPostCount", "id", false).Return(mockPinnedPostsCount, nil) + fakeChannelMembers := model.ChannelMembers([]model.ChannelMember{ + { + UserId: "123", + }, + }) + mockChannelStore.On("GetMembersForUser", "teamId", "userId1").Return(&fakeChannelMembers, nil) + mockChannelStore.On("GetMembersForUser", "teamId", "userId2").Return(&fakeChannelMembers, nil) fakePosts := &model.PostList{} fakeOptions := model.GetPostsOptions{ChannelId: "123", PerPage: 30} diff --git a/store/storetest/mocks/ChannelStore.go b/store/storetest/mocks/ChannelStore.go index d28537fc71..a341229311 100644 --- a/store/storetest/mocks/ChannelStore.go +++ b/store/storetest/mocks/ChannelStore.go @@ -1173,6 +1173,16 @@ func (_m *ChannelStore) InvalidateMemberCount(channelId string) { _m.Called(channelId) } +// InvalidateMembersForAllUsers provides a mock function with given fields: +func (_m *ChannelStore) InvalidateMembersForAllUsers() { + _m.Called() +} + +// InvalidateMembersForUser provides a mock function with given fields: userId +func (_m *ChannelStore) InvalidateMembersForUser(userId string) { + _m.Called(userId) +} + // InvalidatePinnedPostCount provides a mock function with given fields: channelId func (_m *ChannelStore) InvalidatePinnedPostCount(channelId string) { _m.Called(channelId)