diff options
Diffstat (limited to 'internal/cache')
| -rw-r--r-- | internal/cache/cache.go | 38 | ||||
| -rw-r--r-- | internal/cache/invalidate.go | 28 | ||||
| -rw-r--r-- | internal/cache/mutes.go | 4 | ||||
| -rw-r--r-- | internal/cache/size.go | 15 | ||||
| -rw-r--r-- | internal/cache/statusfilter.go | 107 | ||||
| -rw-r--r-- | internal/cache/visibility.go | 4 |
6 files changed, 151 insertions, 45 deletions
diff --git a/internal/cache/cache.go b/internal/cache/cache.go index d3d2d5f2b..5611ddec0 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -41,21 +41,22 @@ type Caches struct { // the block []headerfilter.Filter cache. BlockHeaderFilters headerfilter.Cache - // TTL cache of statuses -> filterable text fields. - // To ensure up-to-date fields, cache is keyed as: - // `[status.ID][status.UpdatedAt.Unix()]` - StatusesFilterableFields *ttl.Cache[string, []string] - - // Timelines ... + // Timelines provides access to the + // collection of timeline object caches, + // used in timeline lookups and streaming. Timelines TimelineCaches // Mutes provides access to the item mutes // cache. (used by the item mutes filter). - Mutes MutesCache + Mutes StructCache[*CachedMute] + + // StatusFilter provides access to the status filter + // cache. (used by the status-filter results filter). + StatusFilter StructCache[*CachedStatusFilterResults] // Visibility provides access to the item visibility // cache. (used by the visibility filter). - Visibility VisibilityCache + Visibility StructCache[*CachedVisibility] // Webfinger provides access to the webfinger URL cache. Webfinger *ttl.Cache[string, string] // TTL=24hr, sweep=5min @@ -119,7 +120,6 @@ func (c *Caches) Init() { c.initStatusEdit() c.initStatusFave() c.initStatusFaveIDs() - c.initStatusesFilterableFields() c.initTag() c.initThreadMute() c.initToken() @@ -131,6 +131,7 @@ func (c *Caches) Init() { c.initWebPushSubscription() c.initWebPushSubscriptionIDs() c.initMutes() + c.initStatusFilter() c.initVisibility() } @@ -143,10 +144,6 @@ func (c *Caches) Start() error { return gtserror.New("could not start webfinger cache") } - if !c.StatusesFilterableFields.Start(5 * time.Minute) { - return gtserror.New("could not start statusesFilterableFields cache") - } - return nil } @@ -158,9 +155,6 @@ func (c *Caches) Stop() { if c.Webfinger != nil { _ = c.Webfinger.Stop() } - if c.StatusesFilterableFields != nil { - _ = c.StatusesFilterableFields.Stop() - } } // Sweep will sweep all the available caches to ensure none @@ -183,6 +177,7 @@ func (c *Caches) Sweep(threshold float64) { c.DB.Emoji.Trim(threshold) c.DB.EmojiCategory.Trim(threshold) c.DB.Filter.Trim(threshold) + c.DB.FilterIDs.Trim(threshold) c.DB.FilterKeyword.Trim(threshold) c.DB.FilterStatus.Trim(threshold) c.DB.Follow.Trim(threshold) @@ -218,20 +213,13 @@ func (c *Caches) Sweep(threshold float64) { c.DB.User.Trim(threshold) c.DB.UserMute.Trim(threshold) c.DB.UserMuteIDs.Trim(threshold) + c.Mutes.Trim(threshold) + c.StatusFilter.Trim(threshold) c.Timelines.Home.Trim() c.Timelines.List.Trim() c.Visibility.Trim(threshold) } -func (c *Caches) initStatusesFilterableFields() { - c.StatusesFilterableFields = new(ttl.Cache[string, []string]) - c.StatusesFilterableFields.Init( - 0, - 512, - 1*time.Hour, - ) -} - func (c *Caches) initWebfinger() { // Calculate maximum cache size. cap := calculateCacheMax( diff --git a/internal/cache/invalidate.go b/internal/cache/invalidate.go index 4941b2540..569238e9b 100644 --- a/internal/cache/invalidate.go +++ b/internal/cache/invalidate.go @@ -26,19 +26,23 @@ import ( // as an invalidation indicates a database INSERT / UPDATE / DELETE. // NOTE THEY ARE ONLY CALLED WHEN THE ITEM IS IN THE CACHE, SO FOR // HOOKS TO BE CALLED ON DELETE YOU MUST FIRST POPULATE IT IN THE CACHE. +// +// Also note that while Timelines are a part of the Caches{} object, +// they are generally not modified as part of side-effects here, as +// they often need specific IDs or more information that can only be +// fetched from the database. As such, they are generally handled as +// side-effects in the ./internal/processor/workers/ package. func (c *Caches) OnInvalidateAccount(account *gtsmodel.Account) { - // Invalidate cached stats objects for this account. - c.DB.AccountStats.Invalidate("AccountID", account.ID) - // Invalidate as possible visibility target result. c.Visibility.Invalidate("ItemID", account.ID) // If account is local, invalidate as - // possible mute / visibility result requester. + // possible visibility result requester, + // also, invalidate any cached stats. if account.IsLocal() { + c.DB.AccountStats.Invalidate("AccountID", account.ID) c.Visibility.Invalidate("RequesterID", account.ID) - c.Mutes.Invalidate("RequesterID", account.ID) } // Invalidate this account's @@ -94,9 +98,8 @@ func (c *Caches) OnInvalidateBlock(block *gtsmodel.Block) { localAccountIDs = append(localAccountIDs, block.TargetAccountID) } - // Now perform local mute / visibility result invalidations. + // Now perform local 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) @@ -120,9 +123,8 @@ func (c *Caches) OnInvalidateFilter(filter *gtsmodel.Filter) { c.DB.FilterKeyword.InvalidateIDs("ID", filter.KeywordIDs) c.DB.FilterStatus.InvalidateIDs("ID", filter.StatusIDs) - // Invalidate account's timelines (in case local). - c.Timelines.Home.Unprepare(filter.AccountID) - c.Timelines.List.Unprepare(filter.AccountID) + // Invalidate account's status filter cache. + c.StatusFilter.Invalidate("RequesterID", filter.AccountID) } func (c *Caches) OnInvalidateFilterKeyword(filterKeyword *gtsmodel.FilterKeyword) { @@ -161,9 +163,8 @@ func (c *Caches) OnInvalidateFollow(follow *gtsmodel.Follow) { localAccountIDs = append(localAccountIDs, follow.TargetAccountID) } - // Now perform local mute / visibility result invalidations. + // Now perform local visibility result invalidations. c.Visibility.InvalidateIDs("RequesterID", localAccountIDs) - c.Mutes.InvalidateIDs("RequesterID", localAccountIDs) // Invalidate ID slice cache. c.DB.FollowIDs.Invalidate( @@ -295,6 +296,9 @@ func (c *Caches) OnInvalidateStatus(status *gtsmodel.Status) { // Invalidate cached stats objects for this account. c.DB.AccountStats.Invalidate("AccountID", status.AccountID) + // Invalidate filter results targeting status. + c.StatusFilter.Invalidate("StatusID", status.ID) + // Invalidate status ID cached visibility. c.Visibility.Invalidate("ItemID", status.ID) diff --git a/internal/cache/mutes.go b/internal/cache/mutes.go index 9ad7736a0..bdf7990dc 100644 --- a/internal/cache/mutes.go +++ b/internal/cache/mutes.go @@ -25,10 +25,6 @@ import ( "codeberg.org/gruf/go-structr" ) -type MutesCache struct { - StructCache[*CachedMute] -} - func (c *Caches) initMutes() { // Calculate maximum cache size. cap := calculateResultCacheMax( diff --git a/internal/cache/size.go b/internal/cache/size.go index 8a6c9e9ad..ab54ada87 100644 --- a/internal/cache/size.go +++ b/internal/cache/size.go @@ -25,6 +25,7 @@ import ( "unsafe" "code.superseriousbusiness.org/gotosocial/internal/ap" + apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model" "code.superseriousbusiness.org/gotosocial/internal/config" "code.superseriousbusiness.org/gotosocial/internal/gtsmodel" "code.superseriousbusiness.org/gotosocial/internal/id" @@ -653,6 +654,20 @@ func sizeofStatusFave() uintptr { })) } +func sizeofStatusFilterResults() uintptr { + return uintptr(size.Of(&CachedStatusFilterResults{ + StatusID: exampleID, + RequesterID: exampleID, + Results: [5][]StatusFilterResult{ + {{Result: &apimodel.FilterResult{KeywordMatches: []string{"key", "word"}}}, {Result: &apimodel.FilterResult{StatusMatches: []string{exampleID, exampleID}}}, {}}, + {{Result: &apimodel.FilterResult{KeywordMatches: []string{"key", "word"}}}, {Result: &apimodel.FilterResult{StatusMatches: []string{exampleID, exampleID}}}, {}}, + {{Result: &apimodel.FilterResult{KeywordMatches: []string{"key", "word"}}}, {Result: &apimodel.FilterResult{StatusMatches: []string{exampleID, exampleID}}}, {}}, + {{Result: &apimodel.FilterResult{KeywordMatches: []string{"key", "word"}}}, {Result: &apimodel.FilterResult{StatusMatches: []string{exampleID, exampleID}}}, {}}, + {{Result: &apimodel.FilterResult{KeywordMatches: []string{"key", "word"}}}, {Result: &apimodel.FilterResult{StatusMatches: []string{exampleID, exampleID}}}, {}}, + }, + })) +} + func sizeofTag() uintptr { return uintptr(size.Of(>smodel.Tag{ ID: exampleID, diff --git a/internal/cache/statusfilter.go b/internal/cache/statusfilter.go new file mode 100644 index 000000000..073caa7f0 --- /dev/null +++ b/internal/cache/statusfilter.go @@ -0,0 +1,107 @@ +// 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" + + apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model" + "code.superseriousbusiness.org/gotosocial/internal/config" + "code.superseriousbusiness.org/gotosocial/internal/log" + "codeberg.org/gruf/go-structr" +) + +func (c *Caches) initStatusFilter() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofStatusFilterResults(), // model in-mem size. + config.GetCacheStatusFilterMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(r1 *CachedStatusFilterResults) *CachedStatusFilterResults { + r2 := new(CachedStatusFilterResults) + *r2 = *r1 + return r2 + } + + c.StatusFilter.Init(structr.CacheConfig[*CachedStatusFilterResults]{ + Indices: []structr.IndexConfig{ + {Fields: "RequesterID,StatusID"}, + {Fields: "StatusID", 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, + }) +} + +const ( + KeyContextHome = iota + KeyContextPublic + KeyContextNotifs + KeyContextThread + KeyContextAccount + keysLen // must always be last in list +) + +// CachedStatusFilterResults contains the +// results of a cached status filter lookup. +type CachedStatusFilterResults struct { + + // StatusID is the ID of the + // status this is a result for. + StatusID string + + // RequesterID is the ID of the requesting + // account for this status filter lookup. + RequesterID string + + // Results is a map (int-key-array) of status filter + // result slices in all possible filtering contexts. + Results [keysLen][]StatusFilterResult +} + +// StatusFilterResult stores a single (positive, +// i.e. match) filter result for a status by a filter. +type StatusFilterResult struct { + + // Expiry stores the time at which + // (if any) the filter result expires. + Expiry time.Time + + // Result stores any generated filter result for + // this match intended to be shown at the frontend. + // This can be used to determine the filter action: + // - value => gtsmodel.FilterActionWarn + // - nil => gtsmodel.FilterActionHide + Result *apimodel.FilterResult +} + +// Expired returns whether the filter result has expired. +func (r *StatusFilterResult) Expired(now time.Time) bool { + return !r.Expiry.IsZero() && !r.Expiry.After(now) +} diff --git a/internal/cache/visibility.go b/internal/cache/visibility.go index 3797ab701..bfb72e4f6 100644 --- a/internal/cache/visibility.go +++ b/internal/cache/visibility.go @@ -23,10 +23,6 @@ import ( "codeberg.org/gruf/go-structr" ) -type VisibilityCache struct { - StructCache[*CachedVisibility] -} - func (c *Caches) initVisibility() { // Calculate maximum cache size. cap := calculateResultCacheMax( |
