1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2// See LICENSE.txt for license information.
3
4package localcachelayer
5
6import (
7	"runtime"
8	"time"
9
10	"github.com/mattermost/mattermost-server/v6/einterfaces"
11	"github.com/mattermost/mattermost-server/v6/model"
12	"github.com/mattermost/mattermost-server/v6/services/cache"
13	"github.com/mattermost/mattermost-server/v6/store"
14)
15
16const (
17	ReactionCacheSize = 20000
18	ReactionCacheSec  = 30 * 60
19
20	RoleCacheSize = 20000
21	RoleCacheSec  = 30 * 60
22
23	SchemeCacheSize = 20000
24	SchemeCacheSec  = 30 * 60
25
26	FileInfoCacheSize = 25000
27	FileInfoCacheSec  = 30 * 60
28
29	ChannelGuestCountCacheSize = model.ChannelCacheSize
30	ChannelGuestCountCacheSec  = 30 * 60
31
32	WebhookCacheSize = 25000
33	WebhookCacheSec  = 15 * 60
34
35	EmojiCacheSize = 5000
36	EmojiCacheSec  = 30 * 60
37
38	ChannelPinnedPostsCounsCacheSize = model.ChannelCacheSize
39	ChannelPinnedPostsCountsCacheSec = 30 * 60
40
41	ChannelMembersCountsCacheSize = model.ChannelCacheSize
42	ChannelMembersCountsCacheSec  = 30 * 60
43
44	LastPostsCacheSize = 20000
45	LastPostsCacheSec  = 30 * 60
46
47	TermsOfServiceCacheSize = 20000
48	TermsOfServiceCacheSec  = 30 * 60
49	LastPostTimeCacheSize   = 25000
50	LastPostTimeCacheSec    = 15 * 60
51
52	UserProfileByIDCacheSize = 20000
53	UserProfileByIDSec       = 30 * 60
54
55	ProfilesInChannelCacheSize = model.ChannelCacheSize
56	ProfilesInChannelCacheSec  = 15 * 60
57
58	TeamCacheSize = 20000
59	TeamCacheSec  = 30 * 60
60
61	ChannelCacheSec = 15 * 60 // 15 mins
62)
63
64var clearCacheMessageData = []byte("")
65
66type LocalCacheStore struct {
67	store.Store
68	metrics einterfaces.MetricsInterface
69	cluster einterfaces.ClusterInterface
70
71	reaction      LocalCacheReactionStore
72	reactionCache cache.Cache
73
74	fileInfo      LocalCacheFileInfoStore
75	fileInfoCache cache.Cache
76
77	role                 LocalCacheRoleStore
78	roleCache            cache.Cache
79	rolePermissionsCache cache.Cache
80
81	scheme      LocalCacheSchemeStore
82	schemeCache cache.Cache
83
84	emoji              *LocalCacheEmojiStore
85	emojiCacheById     cache.Cache
86	emojiIdCacheByName cache.Cache
87
88	channel                      LocalCacheChannelStore
89	channelMemberCountsCache     cache.Cache
90	channelGuestCountCache       cache.Cache
91	channelPinnedPostCountsCache cache.Cache
92	channelByIdCache             cache.Cache
93
94	webhook      LocalCacheWebhookStore
95	webhookCache cache.Cache
96
97	post               LocalCachePostStore
98	postLastPostsCache cache.Cache
99	lastPostTimeCache  cache.Cache
100
101	user                   *LocalCacheUserStore
102	userProfileByIdsCache  cache.Cache
103	profilesInChannelCache cache.Cache
104
105	team                       LocalCacheTeamStore
106	teamAllTeamIdsForUserCache cache.Cache
107
108	termsOfService      LocalCacheTermsOfServiceStore
109	termsOfServiceCache cache.Cache
110}
111
112func NewLocalCacheLayer(baseStore store.Store, metrics einterfaces.MetricsInterface, cluster einterfaces.ClusterInterface, cacheProvider cache.Provider) (localCacheStore LocalCacheStore, err error) {
113	localCacheStore = LocalCacheStore{
114		Store:   baseStore,
115		cluster: cluster,
116		metrics: metrics,
117	}
118	// Reactions
119	if localCacheStore.reactionCache, err = cacheProvider.NewCache(&cache.CacheOptions{
120		Size:                   ReactionCacheSize,
121		Name:                   "Reaction",
122		DefaultExpiry:          ReactionCacheSec * time.Second,
123		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForReactions,
124	}); err != nil {
125		return
126	}
127	localCacheStore.reaction = LocalCacheReactionStore{ReactionStore: baseStore.Reaction(), rootStore: &localCacheStore}
128
129	// Roles
130	if localCacheStore.roleCache, err = cacheProvider.NewCache(&cache.CacheOptions{
131		Size:                   RoleCacheSize,
132		Name:                   "Role",
133		DefaultExpiry:          RoleCacheSec * time.Second,
134		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForRoles,
135		Striped:                true,
136		StripedBuckets:         maxInt(runtime.NumCPU()-1, 1),
137	}); err != nil {
138		return
139	}
140	if localCacheStore.rolePermissionsCache, err = cacheProvider.NewCache(&cache.CacheOptions{
141		Size:                   RoleCacheSize,
142		Name:                   "RolePermission",
143		DefaultExpiry:          RoleCacheSec * time.Second,
144		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForRolePermissions,
145	}); err != nil {
146		return
147	}
148	localCacheStore.role = LocalCacheRoleStore{RoleStore: baseStore.Role(), rootStore: &localCacheStore}
149
150	// Schemes
151	if localCacheStore.schemeCache, err = cacheProvider.NewCache(&cache.CacheOptions{
152		Size:                   SchemeCacheSize,
153		Name:                   "Scheme",
154		DefaultExpiry:          SchemeCacheSec * time.Second,
155		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForSchemes,
156	}); err != nil {
157		return
158	}
159	localCacheStore.scheme = LocalCacheSchemeStore{SchemeStore: baseStore.Scheme(), rootStore: &localCacheStore}
160
161	// FileInfo
162	if localCacheStore.fileInfoCache, err = cacheProvider.NewCache(&cache.CacheOptions{
163		Size:                   FileInfoCacheSize,
164		Name:                   "FileInfo",
165		DefaultExpiry:          FileInfoCacheSec * time.Second,
166		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForFileInfos,
167	}); err != nil {
168		return
169	}
170	localCacheStore.fileInfo = LocalCacheFileInfoStore{FileInfoStore: baseStore.FileInfo(), rootStore: &localCacheStore}
171
172	// Webhooks
173	if localCacheStore.webhookCache, err = cacheProvider.NewCache(&cache.CacheOptions{
174		Size:                   WebhookCacheSize,
175		Name:                   "Webhook",
176		DefaultExpiry:          WebhookCacheSec * time.Second,
177		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForWebhooks,
178	}); err != nil {
179		return
180	}
181	localCacheStore.webhook = LocalCacheWebhookStore{WebhookStore: baseStore.Webhook(), rootStore: &localCacheStore}
182
183	// Emojis
184	if localCacheStore.emojiCacheById, err = cacheProvider.NewCache(&cache.CacheOptions{
185		Size:                   EmojiCacheSize,
186		Name:                   "EmojiById",
187		DefaultExpiry:          EmojiCacheSec * time.Second,
188		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForEmojisById,
189	}); err != nil {
190		return
191	}
192	if localCacheStore.emojiIdCacheByName, err = cacheProvider.NewCache(&cache.CacheOptions{
193		Size:                   EmojiCacheSize,
194		Name:                   "EmojiByName",
195		DefaultExpiry:          EmojiCacheSec * time.Second,
196		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForEmojisIdByName,
197	}); err != nil {
198		return
199	}
200	localCacheStore.emoji = &LocalCacheEmojiStore{
201		EmojiStore:               baseStore.Emoji(),
202		rootStore:                &localCacheStore,
203		emojiByIdInvalidations:   make(map[string]bool),
204		emojiByNameInvalidations: make(map[string]bool),
205	}
206
207	// Channels
208	if localCacheStore.channelPinnedPostCountsCache, err = cacheProvider.NewCache(&cache.CacheOptions{
209		Size:                   ChannelPinnedPostsCounsCacheSize,
210		Name:                   "ChannelPinnedPostsCounts",
211		DefaultExpiry:          ChannelPinnedPostsCountsCacheSec * time.Second,
212		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForChannelPinnedpostsCounts,
213	}); err != nil {
214		return
215	}
216	if localCacheStore.channelMemberCountsCache, err = cacheProvider.NewCache(&cache.CacheOptions{
217		Size:                   ChannelMembersCountsCacheSize,
218		Name:                   "ChannelMemberCounts",
219		DefaultExpiry:          ChannelMembersCountsCacheSec * time.Second,
220		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForChannelMemberCounts,
221	}); err != nil {
222		return
223	}
224	if localCacheStore.channelGuestCountCache, err = cacheProvider.NewCache(&cache.CacheOptions{
225		Size:                   ChannelGuestCountCacheSize,
226		Name:                   "ChannelGuestsCount",
227		DefaultExpiry:          ChannelGuestCountCacheSec * time.Second,
228		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForChannelGuestCount,
229	}); err != nil {
230		return
231	}
232	if localCacheStore.channelByIdCache, err = cacheProvider.NewCache(&cache.CacheOptions{
233		Size:                   model.ChannelCacheSize,
234		Name:                   "channelById",
235		DefaultExpiry:          ChannelCacheSec * time.Second,
236		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForChannel,
237	}); err != nil {
238		return
239	}
240	localCacheStore.channel = LocalCacheChannelStore{ChannelStore: baseStore.Channel(), rootStore: &localCacheStore}
241
242	// Posts
243	if localCacheStore.postLastPostsCache, err = cacheProvider.NewCache(&cache.CacheOptions{
244		Size:                   LastPostsCacheSize,
245		Name:                   "LastPost",
246		DefaultExpiry:          LastPostsCacheSec * time.Second,
247		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForLastPosts,
248	}); err != nil {
249		return
250	}
251	if localCacheStore.lastPostTimeCache, err = cacheProvider.NewCache(&cache.CacheOptions{
252		Size:                   LastPostTimeCacheSize,
253		Name:                   "LastPostTime",
254		DefaultExpiry:          LastPostTimeCacheSec * time.Second,
255		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForLastPostTime,
256	}); err != nil {
257		return
258	}
259	localCacheStore.post = LocalCachePostStore{PostStore: baseStore.Post(), rootStore: &localCacheStore}
260
261	// TOS
262	if localCacheStore.termsOfServiceCache, err = cacheProvider.NewCache(&cache.CacheOptions{
263		Size:                   TermsOfServiceCacheSize,
264		Name:                   "TermsOfService",
265		DefaultExpiry:          TermsOfServiceCacheSec * time.Second,
266		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForTermsOfService,
267	}); err != nil {
268		return
269	}
270	localCacheStore.termsOfService = LocalCacheTermsOfServiceStore{TermsOfServiceStore: baseStore.TermsOfService(), rootStore: &localCacheStore}
271
272	// Users
273	if localCacheStore.userProfileByIdsCache, err = cacheProvider.NewCache(&cache.CacheOptions{
274		Size:                   UserProfileByIDCacheSize,
275		Name:                   "UserProfileByIds",
276		DefaultExpiry:          UserProfileByIDSec * time.Second,
277		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForProfileByIds,
278		Striped:                true,
279		StripedBuckets:         maxInt(runtime.NumCPU()-1, 1),
280	}); err != nil {
281		return
282	}
283	if localCacheStore.profilesInChannelCache, err = cacheProvider.NewCache(&cache.CacheOptions{
284		Size:                   ProfilesInChannelCacheSize,
285		Name:                   "ProfilesInChannel",
286		DefaultExpiry:          ProfilesInChannelCacheSec * time.Second,
287		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForProfileInChannel,
288	}); err != nil {
289		return
290	}
291	localCacheStore.user = &LocalCacheUserStore{
292		UserStore:                     baseStore.User(),
293		rootStore:                     &localCacheStore,
294		userProfileByIdsInvalidations: make(map[string]bool),
295	}
296
297	// Teams
298	if localCacheStore.teamAllTeamIdsForUserCache, err = cacheProvider.NewCache(&cache.CacheOptions{
299		Size:                   TeamCacheSize,
300		Name:                   "Team",
301		DefaultExpiry:          TeamCacheSec * time.Second,
302		InvalidateClusterEvent: model.ClusterEventInvalidateCacheForTeams,
303	}); err != nil {
304		return
305	}
306	localCacheStore.team = LocalCacheTeamStore{TeamStore: baseStore.Team(), rootStore: &localCacheStore}
307
308	if cluster != nil {
309		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForReactions, localCacheStore.reaction.handleClusterInvalidateReaction)
310		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForRoles, localCacheStore.role.handleClusterInvalidateRole)
311		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForRolePermissions, localCacheStore.role.handleClusterInvalidateRolePermissions)
312		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForSchemes, localCacheStore.scheme.handleClusterInvalidateScheme)
313		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForFileInfos, localCacheStore.fileInfo.handleClusterInvalidateFileInfo)
314		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForLastPostTime, localCacheStore.post.handleClusterInvalidateLastPostTime)
315		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForWebhooks, localCacheStore.webhook.handleClusterInvalidateWebhook)
316		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForEmojisById, localCacheStore.emoji.handleClusterInvalidateEmojiById)
317		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForEmojisIdByName, localCacheStore.emoji.handleClusterInvalidateEmojiIdByName)
318		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForChannelPinnedpostsCounts, localCacheStore.channel.handleClusterInvalidateChannelPinnedPostCount)
319		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForChannelMemberCounts, localCacheStore.channel.handleClusterInvalidateChannelMemberCounts)
320		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForChannelGuestCount, localCacheStore.channel.handleClusterInvalidateChannelGuestCounts)
321		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForChannel, localCacheStore.channel.handleClusterInvalidateChannelById)
322		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForLastPosts, localCacheStore.post.handleClusterInvalidateLastPosts)
323		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForTermsOfService, localCacheStore.termsOfService.handleClusterInvalidateTermsOfService)
324		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForProfileByIds, localCacheStore.user.handleClusterInvalidateScheme)
325		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForProfileInChannel, localCacheStore.user.handleClusterInvalidateProfilesInChannel)
326		cluster.RegisterClusterMessageHandler(model.ClusterEventInvalidateCacheForTeams, localCacheStore.team.handleClusterInvalidateTeam)
327	}
328	return
329}
330
331func maxInt(a, b int) int {
332	if a > b {
333		return a
334	}
335	return b
336}
337
338func (s LocalCacheStore) Reaction() store.ReactionStore {
339	return s.reaction
340}
341
342func (s LocalCacheStore) Role() store.RoleStore {
343	return s.role
344}
345
346func (s LocalCacheStore) Scheme() store.SchemeStore {
347	return s.scheme
348}
349
350func (s LocalCacheStore) FileInfo() store.FileInfoStore {
351	return s.fileInfo
352}
353
354func (s LocalCacheStore) Webhook() store.WebhookStore {
355	return s.webhook
356}
357
358func (s LocalCacheStore) Emoji() store.EmojiStore {
359	return s.emoji
360}
361
362func (s LocalCacheStore) Channel() store.ChannelStore {
363	return s.channel
364}
365
366func (s LocalCacheStore) Post() store.PostStore {
367	return s.post
368}
369
370func (s LocalCacheStore) TermsOfService() store.TermsOfServiceStore {
371	return s.termsOfService
372}
373
374func (s LocalCacheStore) User() store.UserStore {
375	return s.user
376}
377
378func (s LocalCacheStore) Team() store.TeamStore {
379	return s.team
380}
381
382func (s LocalCacheStore) DropAllTables() {
383	s.Invalidate()
384	s.Store.DropAllTables()
385}
386
387func (s *LocalCacheStore) doInvalidateCacheCluster(cache cache.Cache, key string) {
388	cache.Remove(key)
389	if s.cluster != nil {
390		msg := &model.ClusterMessage{
391			Event:    cache.GetInvalidateClusterEvent(),
392			SendType: model.ClusterSendBestEffort,
393			Data:     []byte(key),
394		}
395		s.cluster.SendClusterMessage(msg)
396	}
397}
398
399func (s *LocalCacheStore) doStandardAddToCache(cache cache.Cache, key string, value interface{}) {
400	cache.SetWithDefaultExpiry(key, value)
401}
402
403func (s *LocalCacheStore) doStandardReadCache(cache cache.Cache, key string, value interface{}) error {
404	err := cache.Get(key, value)
405	if err == nil {
406		if s.metrics != nil {
407			s.metrics.IncrementMemCacheHitCounter(cache.Name())
408		}
409		return nil
410	}
411	if s.metrics != nil {
412		s.metrics.IncrementMemCacheMissCounter(cache.Name())
413	}
414	return err
415}
416
417func (s *LocalCacheStore) doClearCacheCluster(cache cache.Cache) {
418	cache.Purge()
419	if s.cluster != nil {
420		msg := &model.ClusterMessage{
421			Event:    cache.GetInvalidateClusterEvent(),
422			SendType: model.ClusterSendBestEffort,
423			Data:     clearCacheMessageData,
424		}
425		s.cluster.SendClusterMessage(msg)
426	}
427}
428
429func (s *LocalCacheStore) Invalidate() {
430	s.doClearCacheCluster(s.reactionCache)
431	s.doClearCacheCluster(s.schemeCache)
432	s.doClearCacheCluster(s.roleCache)
433	s.doClearCacheCluster(s.fileInfoCache)
434	s.doClearCacheCluster(s.webhookCache)
435	s.doClearCacheCluster(s.emojiCacheById)
436	s.doClearCacheCluster(s.emojiIdCacheByName)
437	s.doClearCacheCluster(s.channelMemberCountsCache)
438	s.doClearCacheCluster(s.channelPinnedPostCountsCache)
439	s.doClearCacheCluster(s.channelGuestCountCache)
440	s.doClearCacheCluster(s.channelByIdCache)
441	s.doClearCacheCluster(s.postLastPostsCache)
442	s.doClearCacheCluster(s.termsOfServiceCache)
443	s.doClearCacheCluster(s.lastPostTimeCache)
444	s.doClearCacheCluster(s.userProfileByIdsCache)
445	s.doClearCacheCluster(s.profilesInChannelCache)
446	s.doClearCacheCluster(s.teamAllTeamIdsForUserCache)
447	s.doClearCacheCluster(s.rolePermissionsCache)
448}
449