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