diff options
Diffstat (limited to 'internal/cache')
| -rw-r--r-- | internal/cache/cache.go | 5 | ||||
| -rw-r--r-- | internal/cache/db.go | 7 | ||||
| -rw-r--r-- | internal/cache/invalidate.go | 86 | ||||
| -rw-r--r-- | internal/cache/mutes.go | 109 | ||||
| -rw-r--r-- | internal/cache/size.go | 73 | ||||
| -rw-r--r-- | internal/cache/visibility.go | 12 |
6 files changed, 209 insertions, 83 deletions
diff --git a/internal/cache/cache.go b/internal/cache/cache.go index d05b85a15..54777441f 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -49,6 +49,10 @@ type Caches struct { // Timelines ... Timelines TimelineCaches + // Mutes provides access to the item mutes + // cache. (used by the item mutes filter). + Mutes MutesCache + // Visibility provides access to the item visibility // cache. (used by the visibility filter). Visibility VisibilityCache @@ -125,6 +129,7 @@ func (c *Caches) Init() { c.initWebfinger() c.initWebPushSubscription() c.initWebPushSubscriptionIDs() + c.initMutes() c.initVisibility() } diff --git a/internal/cache/db.go b/internal/cache/db.go index 78cc01e06..5592ca493 100644 --- a/internal/cache/db.go +++ b/internal/cache/db.go @@ -1531,9 +1531,10 @@ func (c *Caches) initThreadMute() { {Fields: "AccountID", Multiple: true}, {Fields: "ThreadID,AccountID"}, }, - MaxSize: cap, - IgnoreErr: ignoreErrors, - Copy: copyF, + MaxSize: cap, + IgnoreErr: ignoreErrors, + Copy: copyF, + Invalidate: c.OnInvalidateThreadMute, }) } diff --git a/internal/cache/invalidate.go b/internal/cache/invalidate.go index 3512bb51e..88b7415ae 100644 --- a/internal/cache/invalidate.go +++ b/internal/cache/invalidate.go @@ -28,12 +28,18 @@ import ( // HOOKS TO BE CALLED ON DELETE YOU MUST FIRST POPULATE IT IN THE CACHE. func (c *Caches) OnInvalidateAccount(account *gtsmodel.Account) { - // Invalidate stats for this account. + // Invalidate cached stats objects for this account. c.DB.AccountStats.Invalidate("AccountID", account.ID) - // Invalidate account ID cached visibility. + // Invalidate as possible visibility target result. c.Visibility.Invalidate("ItemID", account.ID) - c.Visibility.Invalidate("RequesterID", account.ID) + + // If account is local, invalidate as + // possible mute / visibility result requester. + if account.IsLocal() { + c.Visibility.Invalidate("RequesterID", account.ID) + c.Mutes.Invalidate("RequesterID", account.ID) + } // Invalidate this account's // following / follower lists. @@ -66,13 +72,31 @@ func (c *Caches) OnInvalidateApplication(app *gtsmodel.Application) { } func (c *Caches) OnInvalidateBlock(block *gtsmodel.Block) { - // Invalidate block origin account ID cached visibility. - c.Visibility.Invalidate("ItemID", block.AccountID) - c.Visibility.Invalidate("RequesterID", block.AccountID) + // Invalidate both block origin and target as + // possible lookup targets for visibility results. + c.Visibility.InvalidateIDs("ItemID", []string{ + block.TargetAccountID, + block.AccountID, + }) + + // Track which of block / target are local. + localAccountIDs := make([]string, 0, 2) + + // If origin is local (or uncertain), also invalidate + // results for them as mute / visibility result requester. + if block.Account == nil || block.Account.IsLocal() { + localAccountIDs = append(localAccountIDs, block.AccountID) + } - // Invalidate block target account ID cached visibility. - c.Visibility.Invalidate("ItemID", block.TargetAccountID) - c.Visibility.Invalidate("RequesterID", block.TargetAccountID) + // If target is local (or uncertain), also invalidate + // results for them as mute / visibility result requester. + if block.TargetAccount == nil || block.TargetAccount.IsLocal() { + localAccountIDs = append(localAccountIDs, block.TargetAccountID) + } + + // Now perform local mute / visibility result invalidations. + c.Visibility.InvalidateIDs("RequesterID", localAccountIDs) + c.Mutes.InvalidateIDs("RequesterID", localAccountIDs) // Invalidate source account's block lists. c.DB.BlockIDs.Invalidate(block.AccountID) @@ -92,13 +116,31 @@ func (c *Caches) OnInvalidateFollow(follow *gtsmodel.Follow) { // Invalidate follow request with this same ID. c.DB.FollowRequest.Invalidate("ID", follow.ID) - // Invalidate follow origin account ID cached visibility. - c.Visibility.Invalidate("ItemID", follow.AccountID) - c.Visibility.Invalidate("RequesterID", follow.AccountID) + // Invalidate both follow origin and target as + // possible lookup targets for visibility results. + c.Visibility.InvalidateIDs("ItemID", []string{ + follow.TargetAccountID, + follow.AccountID, + }) + + // Track which of follow / target are local. + localAccountIDs := make([]string, 0, 2) + + // If origin is local (or uncertain), also invalidate + // results for them as mute / visibility result requester. + if follow.Account == nil || follow.Account.IsLocal() { + localAccountIDs = append(localAccountIDs, follow.AccountID) + } + + // If target is local (or uncertain), also invalidate + // results for them as mute / visibility result requester. + if follow.TargetAccount == nil || follow.TargetAccount.IsLocal() { + localAccountIDs = append(localAccountIDs, follow.TargetAccountID) + } - // Invalidate follow target account ID cached visibility. - c.Visibility.Invalidate("ItemID", follow.TargetAccountID) - c.Visibility.Invalidate("RequesterID", follow.TargetAccountID) + // Now perform local mute / visibility result invalidations. + c.Visibility.InvalidateIDs("RequesterID", localAccountIDs) + c.Mutes.InvalidateIDs("RequesterID", localAccountIDs) // Invalidate ID slice cache. c.DB.FollowIDs.Invalidate( @@ -227,12 +269,16 @@ func (c *Caches) OnInvalidatePollVote(vote *gtsmodel.PollVote) { } func (c *Caches) OnInvalidateStatus(status *gtsmodel.Status) { - // Invalidate stats for this account. + // Invalidate cached stats objects for this account. c.DB.AccountStats.Invalidate("AccountID", status.AccountID) // Invalidate status ID cached visibility. c.Visibility.Invalidate("ItemID", status.ID) + // Invalidate mute results involving status. + c.Mutes.Invalidate("StatusID", status.ID) + c.Mutes.Invalidate("ThreadID", status.ThreadID) + // Invalidate each media by the IDs we're aware of. // This must be done as the status table is aware of // the media IDs in use before the media table is @@ -277,6 +323,11 @@ func (c *Caches) OnInvalidateStatusFave(fave *gtsmodel.StatusFave) { c.DB.StatusFaveIDs.Invalidate(fave.StatusID) } +func (c *Caches) OnInvalidateThreadMute(mute *gtsmodel.ThreadMute) { + // Invalidate cached mute ressults encapsulating this thread and account. + c.Mutes.Invalidate("RequesterID,ThreadID", mute.AccountID, mute.ThreadID) +} + func (c *Caches) OnInvalidateToken(token *gtsmodel.Token) { // Invalidate token's push subscription. c.DB.WebPushSubscription.Invalidate("ID", token.ID) @@ -294,6 +345,9 @@ func (c *Caches) OnInvalidateUser(user *gtsmodel.User) { func (c *Caches) OnInvalidateUserMute(mute *gtsmodel.UserMute) { // Invalidate source account's user mute lists. c.DB.UserMuteIDs.Invalidate(mute.AccountID) + + // Invalidate source account's cached mute results. + c.Mutes.Invalidate("RequesterID", mute.AccountID) } func (c *Caches) OnInvalidateWebPushSubscription(subscription *gtsmodel.WebPushSubscription) { diff --git a/internal/cache/mutes.go b/internal/cache/mutes.go new file mode 100644 index 000000000..9ad7736a0 --- /dev/null +++ b/internal/cache/mutes.go @@ -0,0 +1,109 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +package cache + +import ( + "time" + + "code.superseriousbusiness.org/gotosocial/internal/config" + "code.superseriousbusiness.org/gotosocial/internal/log" + "codeberg.org/gruf/go-structr" +) + +type MutesCache struct { + StructCache[*CachedMute] +} + +func (c *Caches) initMutes() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofMute(), // model in-mem size. + config.GetCacheMutesMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(m1 *CachedMute) *CachedMute { + m2 := new(CachedMute) + *m2 = *m1 + return m2 + } + + c.Mutes.Init(structr.CacheConfig[*CachedMute]{ + Indices: []structr.IndexConfig{ + {Fields: "RequesterID,StatusID"}, + {Fields: "RequesterID,ThreadID", Multiple: true}, + {Fields: "StatusID", Multiple: true}, + {Fields: "ThreadID", Multiple: true}, + {Fields: "RequesterID", Multiple: true}, + }, + MaxSize: cap, + IgnoreErr: func(err error) bool { + // don't cache any errors, + // it gets a little too tricky + // otherwise with ensuring + // errors are cleared out + return true + }, + Copy: copyF, + }) +} + +// CachedMute contains the details +// of a cached mute lookup. +type CachedMute struct { + + // StatusID is the ID of the + // status this is a result for. + StatusID string + + // ThreadID is the ID of the + // thread status is a part of. + ThreadID string + + // RequesterID is the ID of the requesting + // account for this user mute lookup. + RequesterID string + + // Mute indicates whether ItemID + // is muted by RequesterID. + Mute bool + + // MuteExpiry stores the time at which + // (if any) the stored mute value expires. + MuteExpiry time.Time + + // Notifications indicates whether + // this mute should prevent notifications + // being shown for ItemID to RequesterID. + Notifications bool + + // NotificationExpiry stores the time at which + // (if any) the stored notification value expires. + NotificationExpiry time.Time +} + +// MuteExpired returns whether the mute value has expired. +func (m *CachedMute) MuteExpired(now time.Time) bool { + return !m.MuteExpiry.IsZero() && !m.MuteExpiry.After(now) +} + +// NotificationExpired returns whether the notification mute value has expired. +func (m *CachedMute) NotificationExpired(now time.Time) bool { + return !m.NotificationExpiry.IsZero() && !m.NotificationExpiry.After(now) +} diff --git a/internal/cache/size.go b/internal/cache/size.go index 7898f9dfd..ef9259f88 100644 --- a/internal/cache/size.go +++ b/internal/cache/size.go @@ -150,7 +150,7 @@ func calculateCacheMax(keySz, valSz uintptr, ratio float64) int { // The inputted memory ratio does not take into account the // total of all ratios, so divide it here to get perc. ratio. - totalRatio := ratio / totalOfRatios() + totalRatio := ratio / config.GetTotalOfMemRatios() // TODO: we should also further weight this ratio depending // on the combined keySz + valSz as a ratio of all available @@ -172,65 +172,6 @@ func calculateCacheMax(keySz, valSz uintptr, ratio float64) int { return int(fMaxMem / (fKeySz + fValSz + emptyBucketOverhead + float64(cacheElemOverhead))) } -// totalOfRatios returns the total of all cache ratios added together. -func totalOfRatios() float64 { - - // NOTE: this is not performant calculating - // this every damn time (mainly the mutex unlocks - // required to access each config var). fortunately - // we only do this on init so fuck it :D - return 0 + - config.GetCacheAccountMemRatio() + - config.GetCacheAccountNoteMemRatio() + - config.GetCacheAccountSettingsMemRatio() + - config.GetCacheAccountStatsMemRatio() + - config.GetCacheApplicationMemRatio() + - config.GetCacheBlockMemRatio() + - config.GetCacheBlockIDsMemRatio() + - config.GetCacheBoostOfIDsMemRatio() + - config.GetCacheClientMemRatio() + - config.GetCacheEmojiMemRatio() + - config.GetCacheEmojiCategoryMemRatio() + - config.GetCacheFilterMemRatio() + - config.GetCacheFilterKeywordMemRatio() + - config.GetCacheFilterStatusMemRatio() + - config.GetCacheFollowMemRatio() + - config.GetCacheFollowIDsMemRatio() + - config.GetCacheFollowRequestMemRatio() + - config.GetCacheFollowRequestIDsMemRatio() + - config.GetCacheFollowingTagIDsMemRatio() + - config.GetCacheInReplyToIDsMemRatio() + - config.GetCacheInstanceMemRatio() + - config.GetCacheInteractionRequestMemRatio() + - config.GetCacheListMemRatio() + - config.GetCacheListIDsMemRatio() + - config.GetCacheListedIDsMemRatio() + - config.GetCacheMarkerMemRatio() + - config.GetCacheMediaMemRatio() + - config.GetCacheMentionMemRatio() + - config.GetCacheMoveMemRatio() + - config.GetCacheNotificationMemRatio() + - config.GetCachePollMemRatio() + - config.GetCachePollVoteMemRatio() + - config.GetCachePollVoteIDsMemRatio() + - config.GetCacheReportMemRatio() + - config.GetCacheSinBinStatusMemRatio() + - config.GetCacheStatusMemRatio() + - config.GetCacheStatusBookmarkMemRatio() + - config.GetCacheStatusBookmarkIDsMemRatio() + - config.GetCacheStatusFaveMemRatio() + - config.GetCacheStatusFaveIDsMemRatio() + - config.GetCacheTagMemRatio() + - config.GetCacheThreadMuteMemRatio() + - config.GetCacheTokenMemRatio() + - config.GetCacheTombstoneMemRatio() + - config.GetCacheUserMemRatio() + - config.GetCacheUserMuteMemRatio() + - config.GetCacheUserMuteIDsMemRatio() + - config.GetCacheWebfingerMemRatio() + - config.GetCacheVisibilityMemRatio() -} - func sizeofAccount() uintptr { return uintptr(size.Of(>smodel.Account{ ID: exampleID, @@ -769,6 +710,18 @@ func sizeofTombstone() uintptr { })) } +func sizeofMute() uintptr { + return uintptr(size.Of(&CachedMute{ + StatusID: exampleID, + ThreadID: exampleID, + RequesterID: exampleID, + Mute: true, + MuteExpiry: exampleTime, + Notifications: true, + NotificationExpiry: exampleTime, + })) +} + func sizeofVisibility() uintptr { return uintptr(size.Of(&CachedVisibility{ ItemID: exampleID, diff --git a/internal/cache/visibility.go b/internal/cache/visibility.go index 63927cf08..3797ab701 100644 --- a/internal/cache/visibility.go +++ b/internal/cache/visibility.go @@ -34,7 +34,7 @@ func (c *Caches) initVisibility() { config.GetCacheVisibilityMemRatio(), ) - log.Infof(nil, "Visibility cache size = %d", cap) + log.Infof(nil, "cache size = %d", cap) copyF := func(v1 *CachedVisibility) *CachedVisibility { v2 := new(CachedVisibility) @@ -73,12 +73,16 @@ const ( VisibilityTypePublic = VisibilityType('p') ) -// CachedVisibility represents a cached visibility lookup value. +// CachedVisibility represents a +// cached visibility lookup value. type CachedVisibility struct { - // ItemID is the ID of the item in question (status / account). + + // ItemID is the ID of the item + // in question (status / account). ItemID string - // RequesterID is the ID of the requesting account for this visibility lookup. + // RequesterID is the ID of the requesting + // account for this visibility lookup. RequesterID string // Type is the visibility lookup type. |
