diff options
author | 2024-01-19 12:57:29 +0000 | |
---|---|---|
committer | 2024-01-19 12:57:29 +0000 | |
commit | 7ec1e1332e7d04e74451acef18b41f389722b698 (patch) | |
tree | 9c69eca7fc664ab5564279a2e065dfd5c2ddd17b /internal/cache | |
parent | [chore] chore rationalise http return codes for activitypub handlers (#2540) (diff) | |
download | gotosocial-7ec1e1332e7d04e74451acef18b41f389722b698.tar.xz |
[performance] overhaul struct (+ result) caching library for simplicity, performance and multiple-result lookups (#2535)
* rewrite cache library as codeberg.org/gruf/go-structr, implement in gotosocial
* use actual go-structr release version (not just commit hash)
* revert go toolchain changes (damn you go for auto changing this)
* fix go mod woes
* ensure %w is used in calls to errs.Appendf()
* fix error checking
* fix possible panic
* remove unnecessary start/stop functions, move to main Cache{} struct, add note regarding which caches require start/stop
* fix copy-paste artifact... :innocent:
* fix all comment copy-paste artifacts
* remove dropID() function, now we can just use slices.DeleteFunc()
* use util.Deduplicate() instead of collate(), move collate to util
* move orderByIDs() to util package and "generify"
* add a util.DeleteIf() function, use this to delete entries on failed population
* use slices.DeleteFunc() instead of util.DeleteIf() (i had the logic mixed up in my head somehow lol)
* add note about how collate differs from deduplicate
Diffstat (limited to 'internal/cache')
-rw-r--r-- | internal/cache/ap.go | 30 | ||||
-rw-r--r-- | internal/cache/cache.go | 278 | ||||
-rw-r--r-- | internal/cache/db.go | 1071 | ||||
-rw-r--r-- | internal/cache/gts.go | 1119 | ||||
-rw-r--r-- | internal/cache/invalidate.go | 192 | ||||
-rw-r--r-- | internal/cache/visibility.go | 35 |
6 files changed, 1348 insertions, 1377 deletions
diff --git a/internal/cache/ap.go b/internal/cache/ap.go deleted file mode 100644 index 6498d7991..000000000 --- a/internal/cache/ap.go +++ /dev/null @@ -1,30 +0,0 @@ -// 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 - -type APCaches struct{} - -// Init will initialize all the ActivityPub caches in this collection. -// NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe. -func (c *APCaches) Init() {} - -// Start will attempt to start all of the ActivityPub caches, or panic. -func (c *APCaches) Start() {} - -// Stop will attempt to stop all of the ActivityPub caches, or panic. -func (c *APCaches) Stop() {} diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 73e3ad6f0..a278336ae 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -18,8 +18,9 @@ package cache import ( + "time" + "github.com/superseriousbusiness/gotosocial/internal/cache/headerfilter" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/log" ) @@ -49,198 +50,59 @@ type Caches struct { func (c *Caches) Init() { log.Infof(nil, "init: %p", c) - c.GTS.Init() - c.Visibility.Init() - - // Setup cache invalidate hooks. - // !! READ THE METHOD COMMENT - c.setuphooks() + c.initAccount() + c.initAccountNote() + c.initApplication() + c.initBlock() + c.initBlockIDs() + c.initBoostOfIDs() + c.initDomainAllow() + c.initDomainBlock() + c.initEmoji() + c.initEmojiCategory() + c.initFollow() + c.initFollowIDs() + c.initFollowRequest() + c.initFollowRequestIDs() + c.initInReplyToIDs() + c.initInstance() + c.initList() + c.initListEntry() + c.initMarker() + c.initMedia() + c.initMention() + c.initNotification() + c.initPoll() + c.initPollVote() + c.initPollVoteIDs() + c.initReport() + c.initStatus() + c.initStatusFave() + c.initTag() + c.initThreadMute() + c.initStatusFaveIDs() + c.initTombstone() + c.initUser() + c.initWebfinger() + c.initVisibility() } -// Start will start both the GTS and AP cache collections. +// Start will start any caches that require a background +// routine, which usually means any kind of TTL caches. func (c *Caches) Start() { log.Infof(nil, "start: %p", c) - c.GTS.Start() - c.Visibility.Start() + tryUntil("starting *gtsmodel.Webfinger cache", 5, func() bool { + return c.GTS.Webfinger.Start(5 * time.Minute) + }) } -// Stop will stop both the GTS and AP cache collections. +// Stop will stop any caches that require a background +// routine, which usually means any kind of TTL caches. func (c *Caches) Stop() { log.Infof(nil, "stop: %p", c) - c.GTS.Stop() - c.Visibility.Stop() -} - -// setuphooks sets necessary cache invalidation hooks between caches, -// 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. -func (c *Caches) setuphooks() { - c.GTS.Account().SetInvalidateCallback(func(account *gtsmodel.Account) { - // Invalidate account ID cached visibility. - c.Visibility.Invalidate("ItemID", account.ID) - c.Visibility.Invalidate("RequesterID", account.ID) - - // Invalidate this account's - // following / follower lists. - // (see FollowIDs() comment for details). - c.GTS.FollowIDs().InvalidateAll( - ">"+account.ID, - "l>"+account.ID, - "<"+account.ID, - "l<"+account.ID, - ) - - // Invalidate this account's - // follow requesting / request lists. - // (see FollowRequestIDs() comment for details). - c.GTS.FollowRequestIDs().InvalidateAll( - ">"+account.ID, - "<"+account.ID, - ) - - // Invalidate this account's block lists. - c.GTS.BlockIDs().Invalidate(account.ID) - }) - - c.GTS.Block().SetInvalidateCallback(func(block *gtsmodel.Block) { - // Invalidate block origin account ID cached visibility. - c.Visibility.Invalidate("ItemID", block.AccountID) - c.Visibility.Invalidate("RequesterID", block.AccountID) - - // Invalidate block target account ID cached visibility. - c.Visibility.Invalidate("ItemID", block.TargetAccountID) - c.Visibility.Invalidate("RequesterID", block.TargetAccountID) - - // Invalidate source account's block lists. - c.GTS.BlockIDs().Invalidate(block.AccountID) - }) - - c.GTS.EmojiCategory().SetInvalidateCallback(func(category *gtsmodel.EmojiCategory) { - // Invalidate any emoji in this category. - c.GTS.Emoji().Invalidate("CategoryID", category.ID) - }) - - c.GTS.Follow().SetInvalidateCallback(func(follow *gtsmodel.Follow) { - // Invalidate follow request with this same ID. - c.GTS.FollowRequest().Invalidate("ID", follow.ID) - - // Invalidate any related list entries. - c.GTS.ListEntry().Invalidate("FollowID", follow.ID) - - // Invalidate follow origin account ID cached visibility. - c.Visibility.Invalidate("ItemID", follow.AccountID) - c.Visibility.Invalidate("RequesterID", follow.AccountID) - - // Invalidate follow target account ID cached visibility. - c.Visibility.Invalidate("ItemID", follow.TargetAccountID) - c.Visibility.Invalidate("RequesterID", follow.TargetAccountID) - - // Invalidate source account's following - // lists, and destination's follwer lists. - // (see FollowIDs() comment for details). - c.GTS.FollowIDs().InvalidateAll( - ">"+follow.AccountID, - "l>"+follow.AccountID, - "<"+follow.AccountID, - "l<"+follow.AccountID, - "<"+follow.TargetAccountID, - "l<"+follow.TargetAccountID, - ">"+follow.TargetAccountID, - "l>"+follow.TargetAccountID, - ) - }) - - c.GTS.FollowRequest().SetInvalidateCallback(func(followReq *gtsmodel.FollowRequest) { - // Invalidate follow with this same ID. - c.GTS.Follow().Invalidate("ID", followReq.ID) - - // Invalidate source account's followreq - // lists, and destinations follow req lists. - // (see FollowRequestIDs() comment for details). - c.GTS.FollowRequestIDs().InvalidateAll( - ">"+followReq.AccountID, - "<"+followReq.AccountID, - ">"+followReq.TargetAccountID, - "<"+followReq.TargetAccountID, - ) - }) - - c.GTS.List().SetInvalidateCallback(func(list *gtsmodel.List) { - // Invalidate all cached entries of this list. - c.GTS.ListEntry().Invalidate("ListID", list.ID) - }) - - c.GTS.Media().SetInvalidateCallback(func(media *gtsmodel.MediaAttachment) { - if *media.Avatar || *media.Header { - // Invalidate cache of attaching account. - c.GTS.Account().Invalidate("ID", media.AccountID) - } - - if media.StatusID != "" { - // Invalidate cache of attaching status. - c.GTS.Status().Invalidate("ID", media.StatusID) - } - }) - - c.GTS.Poll().SetInvalidateCallback(func(poll *gtsmodel.Poll) { - // Invalidate all cached votes of this poll. - c.GTS.PollVote().Invalidate("PollID", poll.ID) - - // Invalidate cache of poll vote IDs. - c.GTS.PollVoteIDs().Invalidate(poll.ID) - }) - - c.GTS.PollVote().SetInvalidateCallback(func(vote *gtsmodel.PollVote) { - // Invalidate cached poll (contains no. votes). - c.GTS.Poll().Invalidate("ID", vote.PollID) - - // Invalidate cache of poll vote IDs. - c.GTS.PollVoteIDs().Invalidate(vote.PollID) - }) - - c.GTS.Status().SetInvalidateCallback(func(status *gtsmodel.Status) { - // Invalidate status ID cached visibility. - c.Visibility.Invalidate("ItemID", status.ID) - - for _, id := range status.AttachmentIDs { - // 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 - // aware of the status ID they are linked to. - // - // c.GTS.Media().Invalidate("StatusID") will not work. - c.GTS.Media().Invalidate("ID", id) - } - - if status.BoostOfID != "" { - // Invalidate boost ID list of the original status. - c.GTS.BoostOfIDs().Invalidate(status.BoostOfID) - } - - if status.InReplyToID != "" { - // Invalidate in reply to ID list of original status. - c.GTS.InReplyToIDs().Invalidate(status.InReplyToID) - } - - if status.PollID != "" { - // Invalidate cache of attached poll ID. - c.GTS.Poll().Invalidate("ID", status.PollID) - } - }) - - c.GTS.StatusFave().SetInvalidateCallback(func(fave *gtsmodel.StatusFave) { - // Invalidate status fave ID list for this status. - c.GTS.StatusFaveIDs().Invalidate(fave.StatusID) - }) - - c.GTS.User().SetInvalidateCallback(func(user *gtsmodel.User) { - // Invalidate local account ID cached visibility. - c.Visibility.Invalidate("ItemID", user.AccountID) - c.Visibility.Invalidate("RequesterID", user.AccountID) - }) + tryUntil("stopping *gtsmodel.Webfinger cache", 5, c.GTS.Webfinger.Stop) } // Sweep will sweep all the available caches to ensure none @@ -250,30 +112,30 @@ func (c *Caches) setuphooks() { // require an eviction on every single write, which adds // significant overhead to all cache writes. func (c *Caches) Sweep(threshold float64) { - c.GTS.Account().Trim(threshold) - c.GTS.AccountNote().Trim(threshold) - c.GTS.Block().Trim(threshold) - c.GTS.BlockIDs().Trim(threshold) - c.GTS.Emoji().Trim(threshold) - c.GTS.EmojiCategory().Trim(threshold) - c.GTS.Follow().Trim(threshold) - c.GTS.FollowIDs().Trim(threshold) - c.GTS.FollowRequest().Trim(threshold) - c.GTS.FollowRequestIDs().Trim(threshold) - c.GTS.Instance().Trim(threshold) - c.GTS.List().Trim(threshold) - c.GTS.ListEntry().Trim(threshold) - c.GTS.Marker().Trim(threshold) - c.GTS.Media().Trim(threshold) - c.GTS.Mention().Trim(threshold) - c.GTS.Notification().Trim(threshold) - c.GTS.Poll().Trim(threshold) - c.GTS.Report().Trim(threshold) - c.GTS.Status().Trim(threshold) - c.GTS.StatusFave().Trim(threshold) - c.GTS.Tag().Trim(threshold) - c.GTS.ThreadMute().Trim(threshold) - c.GTS.Tombstone().Trim(threshold) - c.GTS.User().Trim(threshold) + c.GTS.Account.Trim(threshold) + c.GTS.AccountNote.Trim(threshold) + c.GTS.Block.Trim(threshold) + c.GTS.BlockIDs.Trim(threshold) + c.GTS.Emoji.Trim(threshold) + c.GTS.EmojiCategory.Trim(threshold) + c.GTS.Follow.Trim(threshold) + c.GTS.FollowIDs.Trim(threshold) + c.GTS.FollowRequest.Trim(threshold) + c.GTS.FollowRequestIDs.Trim(threshold) + c.GTS.Instance.Trim(threshold) + c.GTS.List.Trim(threshold) + c.GTS.ListEntry.Trim(threshold) + c.GTS.Marker.Trim(threshold) + c.GTS.Media.Trim(threshold) + c.GTS.Mention.Trim(threshold) + c.GTS.Notification.Trim(threshold) + c.GTS.Poll.Trim(threshold) + c.GTS.Report.Trim(threshold) + c.GTS.Status.Trim(threshold) + c.GTS.StatusFave.Trim(threshold) + c.GTS.Tag.Trim(threshold) + c.GTS.ThreadMute.Trim(threshold) + c.GTS.Tombstone.Trim(threshold) + c.GTS.User.Trim(threshold) c.Visibility.Trim(threshold) } diff --git a/internal/cache/db.go b/internal/cache/db.go new file mode 100644 index 000000000..894d74109 --- /dev/null +++ b/internal/cache/db.go @@ -0,0 +1,1071 @@ +// 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" + + "codeberg.org/gruf/go-cache/v3/simple" + "codeberg.org/gruf/go-cache/v3/ttl" + "codeberg.org/gruf/go-structr" + "github.com/superseriousbusiness/gotosocial/internal/cache/domain" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/log" +) + +type GTSCaches struct { + // Account provides access to the gtsmodel Account database cache. + Account structr.Cache[*gtsmodel.Account] + + // AccountNote provides access to the gtsmodel Note database cache. + AccountNote structr.Cache[*gtsmodel.AccountNote] + + // Application provides access to the gtsmodel Application database cache. + Application structr.Cache[*gtsmodel.Application] + + // Block provides access to the gtsmodel Block (account) database cache. + Block structr.Cache[*gtsmodel.Block] + + // FollowIDs provides access to the block IDs database cache. + BlockIDs *SliceCache[string] + + // BoostOfIDs provides access to the boost of IDs list database cache. + BoostOfIDs *SliceCache[string] + + // DomainAllow provides access to the domain allow database cache. + DomainAllow *domain.Cache + + // DomainBlock provides access to the domain block database cache. + DomainBlock *domain.Cache + + // Emoji provides access to the gtsmodel Emoji database cache. + Emoji structr.Cache[*gtsmodel.Emoji] + + // EmojiCategory provides access to the gtsmodel EmojiCategory database cache. + EmojiCategory structr.Cache[*gtsmodel.EmojiCategory] + + // Follow provides access to the gtsmodel Follow database cache. + Follow structr.Cache[*gtsmodel.Follow] + + // FollowIDs provides access to the follower / following IDs database cache. + // THIS CACHE IS KEYED AS THE FOLLOWING {prefix}{accountID} WHERE PREFIX IS: + // - '>' for following IDs + // - 'l>' for local following IDs + // - '<' for follower IDs + // - 'l<' for local follower IDs + FollowIDs *SliceCache[string] + + // FollowRequest provides access to the gtsmodel FollowRequest database cache. + FollowRequest structr.Cache[*gtsmodel.FollowRequest] + + // FollowRequestIDs provides access to the follow requester / requesting IDs database + // cache. THIS CACHE IS KEYED AS THE FOLLOWING {prefix}{accountID} WHERE PREFIX IS: + // - '>' for following IDs + // - '<' for follower IDs + FollowRequestIDs *SliceCache[string] + + // Instance provides access to the gtsmodel Instance database cache. + Instance structr.Cache[*gtsmodel.Instance] + + // InReplyToIDs provides access to the status in reply to IDs list database cache. + InReplyToIDs *SliceCache[string] + + // List provides access to the gtsmodel List database cache. + List structr.Cache[*gtsmodel.List] + + // ListEntry provides access to the gtsmodel ListEntry database cache. + ListEntry structr.Cache[*gtsmodel.ListEntry] + + // Marker provides access to the gtsmodel Marker database cache. + Marker structr.Cache[*gtsmodel.Marker] + + // Media provides access to the gtsmodel Media database cache. + Media structr.Cache[*gtsmodel.MediaAttachment] + + // Mention provides access to the gtsmodel Mention database cache. + Mention structr.Cache[*gtsmodel.Mention] + + // Notification provides access to the gtsmodel Notification database cache. + Notification structr.Cache[*gtsmodel.Notification] + + // Poll provides access to the gtsmodel Poll database cache. + Poll structr.Cache[*gtsmodel.Poll] + + // PollVote provides access to the gtsmodel PollVote database cache. + PollVote structr.Cache[*gtsmodel.PollVote] + + // PollVoteIDs provides access to the poll vote IDs list database cache. + PollVoteIDs *SliceCache[string] + + // Report provides access to the gtsmodel Report database cache. + Report structr.Cache[*gtsmodel.Report] + + // Status provides access to the gtsmodel Status database cache. + Status structr.Cache[*gtsmodel.Status] + + // StatusFave provides access to the gtsmodel StatusFave database cache. + StatusFave structr.Cache[*gtsmodel.StatusFave] + + // StatusFaveIDs provides access to the status fave IDs list database cache. + StatusFaveIDs *SliceCache[string] + + // Tag provides access to the gtsmodel Tag database cache. + Tag structr.Cache[*gtsmodel.Tag] + + // Tombstone provides access to the gtsmodel Tombstone database cache. + Tombstone structr.Cache[*gtsmodel.Tombstone] + + // ThreadMute provides access to the gtsmodel ThreadMute database cache. + ThreadMute structr.Cache[*gtsmodel.ThreadMute] + + // User provides access to the gtsmodel User database cache. + User structr.Cache[*gtsmodel.User] + + // Webfinger provides access to the webfinger URL cache. + // TODO: move out of GTS caches since unrelated to DB. + Webfinger *ttl.Cache[string, string] // TTL=24hr, sweep=5min +} + +// NOTE: +// all of the below init functions +// are receivers to the main cache +// struct type, not the database cache +// struct type, in order to get access +// to the full suite of caches for +// our invalidate function hooks. + +func (c *Caches) initAccount() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofAccount(), // model in-mem size. + config.GetCacheAccountMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(a1 *gtsmodel.Account) *gtsmodel.Account { + a2 := new(gtsmodel.Account) + *a2 = *a1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/account.go. + a2.AvatarMediaAttachment = nil + a2.HeaderMediaAttachment = nil + a2.Emojis = nil + + return a2 + } + + c.GTS.Account.Init(structr.Config[*gtsmodel.Account]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "URI"}, + {Fields: "URL"}, + {Fields: "Username,Domain", AllowZero: true}, + {Fields: "PublicKeyURI"}, + {Fields: "InboxURI"}, + {Fields: "OutboxURI"}, + {Fields: "FollowersURI"}, + {Fields: "FollowingURI"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidateAccount, + }) +} + +func (c *Caches) initAccountNote() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofAccountNote(), // model in-mem size. + config.GetCacheAccountNoteMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(n1 *gtsmodel.AccountNote) *gtsmodel.AccountNote { + n2 := new(gtsmodel.AccountNote) + *n2 = *n1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/relationship_note.go. + n2.Account = nil + n2.TargetAccount = nil + + return n2 + } + + c.GTS.AccountNote.Init(structr.Config[*gtsmodel.AccountNote]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "AccountID,TargetAccountID"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initApplication() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofApplication(), // model in-mem size. + config.GetCacheApplicationMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(a1 *gtsmodel.Application) *gtsmodel.Application { + a2 := new(gtsmodel.Application) + *a2 = *a1 + return a2 + } + + c.GTS.Application.Init(structr.Config[*gtsmodel.Application]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "ClientID"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initBlock() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofBlock(), // model in-mem size. + config.GetCacheBlockMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(b1 *gtsmodel.Block) *gtsmodel.Block { + b2 := new(gtsmodel.Block) + *b2 = *b1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/relationship_block.go. + b2.Account = nil + b2.TargetAccount = nil + + return b2 + } + + c.GTS.Block.Init(structr.Config[*gtsmodel.Block]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "URI"}, + {Fields: "AccountID,TargetAccountID"}, + {Fields: "AccountID", Multiple: true}, + {Fields: "TargetAccountID", Multiple: true}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidateBlock, + }) +} + +func (c *Caches) initBlockIDs() { + // Calculate maximum cache size. + cap := calculateSliceCacheMax( + config.GetCacheBlockIDsMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + c.GTS.BlockIDs = &SliceCache[string]{Cache: simple.New[string, []string]( + 0, + cap, + )} +} + +func (c *Caches) initBoostOfIDs() { + // Calculate maximum cache size. + cap := calculateSliceCacheMax( + config.GetCacheBoostOfIDsMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + c.GTS.BoostOfIDs = &SliceCache[string]{Cache: simple.New[string, []string]( + 0, + cap, + )} +} + +func (c *Caches) initDomainAllow() { + c.GTS.DomainAllow = new(domain.Cache) +} + +func (c *Caches) initDomainBlock() { + c.GTS.DomainBlock = new(domain.Cache) +} + +func (c *Caches) initEmoji() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofEmoji(), // model in-mem size. + config.GetCacheEmojiMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(e1 *gtsmodel.Emoji) *gtsmodel.Emoji { + e2 := new(gtsmodel.Emoji) + *e2 = *e1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/emoji.go. + e2.Category = nil + + return e2 + } + + c.GTS.Emoji.Init(structr.Config[*gtsmodel.Emoji]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "URI"}, + {Fields: "Shortcode,Domain", AllowZero: true}, + {Fields: "ImageStaticURL"}, + {Fields: "CategoryID", Multiple: true}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initEmojiCategory() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofEmojiCategory(), // model in-mem size. + config.GetCacheEmojiCategoryMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(c1 *gtsmodel.EmojiCategory) *gtsmodel.EmojiCategory { + c2 := new(gtsmodel.EmojiCategory) + *c2 = *c1 + return c2 + } + + c.GTS.EmojiCategory.Init(structr.Config[*gtsmodel.EmojiCategory]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "Name"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidateEmojiCategory, + }) +} + +func (c *Caches) initFollow() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofFollow(), // model in-mem size. + config.GetCacheFollowMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(f1 *gtsmodel.Follow) *gtsmodel.Follow { + f2 := new(gtsmodel.Follow) + *f2 = *f1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/relationship_follow.go. + f2.Account = nil + f2.TargetAccount = nil + + return f2 + } + + c.GTS.Follow.Init(structr.Config[*gtsmodel.Follow]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "URI"}, + {Fields: "AccountID,TargetAccountID"}, + {Fields: "AccountID", Multiple: true}, + {Fields: "TargetAccountID", Multiple: true}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidateFollow, + }) +} + +func (c *Caches) initFollowIDs() { + // Calculate maximum cache size. + cap := calculateSliceCacheMax( + config.GetCacheFollowIDsMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + c.GTS.FollowIDs = &SliceCache[string]{Cache: simple.New[string, []string]( + 0, + cap, + )} +} + +func (c *Caches) initFollowRequest() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofFollowRequest(), // model in-mem size. + config.GetCacheFollowRequestMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(f1 *gtsmodel.FollowRequest) *gtsmodel.FollowRequest { + f2 := new(gtsmodel.FollowRequest) + *f2 = *f1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/relationship_follow_req.go. + f2.Account = nil + f2.TargetAccount = nil + + return f2 + } + + c.GTS.FollowRequest.Init(structr.Config[*gtsmodel.FollowRequest]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "URI"}, + {Fields: "AccountID,TargetAccountID"}, + {Fields: "AccountID", Multiple: true}, + {Fields: "TargetAccountID", Multiple: true}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidateFollowRequest, + }) +} + +func (c *Caches) initFollowRequestIDs() { + // Calculate maximum cache size. + cap := calculateSliceCacheMax( + config.GetCacheFollowRequestIDsMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + c.GTS.FollowRequestIDs = &SliceCache[string]{Cache: simple.New[string, []string]( + 0, + cap, + )} +} + +func (c *Caches) initInReplyToIDs() { + // Calculate maximum cache size. + cap := calculateSliceCacheMax( + config.GetCacheInReplyToIDsMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + c.GTS.InReplyToIDs = &SliceCache[string]{Cache: simple.New[string, []string]( + 0, + cap, + )} +} + +func (c *Caches) initInstance() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofInstance(), // model in-mem size. + config.GetCacheInstanceMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(i1 *gtsmodel.Instance) *gtsmodel.Instance { + i2 := new(gtsmodel.Instance) + *i2 = *i1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/instance.go. + i2.DomainBlock = nil + i2.ContactAccount = nil + + return i1 + } + + c.GTS.Instance.Init(structr.Config[*gtsmodel.Instance]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "Domain"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initList() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofList(), // model in-mem size. + config.GetCacheListMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(l1 *gtsmodel.List) *gtsmodel.List { + l2 := new(gtsmodel.List) + *l2 = *l1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/list.go. + l2.Account = nil + l2.ListEntries = nil + + return l2 + } + + c.GTS.List.Init(structr.Config[*gtsmodel.List]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidateList, + }) +} + +func (c *Caches) initListEntry() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofListEntry(), // model in-mem size. + config.GetCacheListEntryMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(l1 *gtsmodel.ListEntry) *gtsmodel.ListEntry { + l2 := new(gtsmodel.ListEntry) + *l2 = *l1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/list.go. + l2.Follow = nil + + return l2 + } + + c.GTS.ListEntry.Init(structr.Config[*gtsmodel.ListEntry]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "ListID", Multiple: true}, + {Fields: "FollowID", Multiple: true}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initMarker() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofMarker(), // model in-mem size. + config.GetCacheMarkerMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(m1 *gtsmodel.Marker) *gtsmodel.Marker { + m2 := new(gtsmodel.Marker) + *m2 = *m1 + return m2 + } + + c.GTS.Marker.Init(structr.Config[*gtsmodel.Marker]{ + Indices: []structr.IndexConfig{ + {Fields: "AccountID,Name"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initMedia() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofMedia(), // model in-mem size. + config.GetCacheMediaMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(m1 *gtsmodel.MediaAttachment) *gtsmodel.MediaAttachment { + m2 := new(gtsmodel.MediaAttachment) + *m2 = *m1 + return m2 + } + + c.GTS.Media.Init(structr.Config[*gtsmodel.MediaAttachment]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidateMedia, + }) +} + +func (c *Caches) initMention() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofMention(), // model in-mem size. + config.GetCacheMentionMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(m1 *gtsmodel.Mention) *gtsmodel.Mention { + m2 := new(gtsmodel.Mention) + *m2 = *m1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/mention.go. + m2.Status = nil + m2.OriginAccount = nil + m2.TargetAccount = nil + + return m2 + } + + c.GTS.Mention.Init(structr.Config[*gtsmodel.Mention]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initNotification() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofNotification(), // model in-mem size. + config.GetCacheNotificationMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(n1 *gtsmodel.Notification) *gtsmodel.Notification { + n2 := new(gtsmodel.Notification) + *n2 = *n1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/notification.go. + n2.Status = nil + n2.OriginAccount = nil + n2.TargetAccount = nil + + return n2 + } + + c.GTS.Notification.Init(structr.Config[*gtsmodel.Notification]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "NotificationType,TargetAccountID,OriginAccountID,StatusID", AllowZero: true}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initPoll() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofPoll(), // model in-mem size. + config.GetCachePollMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(p1 *gtsmodel.Poll) *gtsmodel.Poll { + p2 := new(gtsmodel.Poll) + *p2 = *p1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/poll.go. + p2.Status = nil + + // Don't include ephemeral fields + // which are only expected to be + // set on ONE poll instance. + p2.Closing = false + + return p2 + } + + c.GTS.Poll.Init(structr.Config[*gtsmodel.Poll]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "StatusID"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidatePoll, + }) +} + +func (c *Caches) initPollVote() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofPollVote(), // model in-mem size. + config.GetCachePollVoteMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(v1 *gtsmodel.PollVote) *gtsmodel.PollVote { + v2 := new(gtsmodel.PollVote) + *v2 = *v1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/poll.go. + v2.Account = nil + v2.Poll = nil + + return v2 + } + + c.GTS.PollVote.Init(structr.Config[*gtsmodel.PollVote]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "PollID", Multiple: true}, + {Fields: "PollID,AccountID"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidatePollVote, + }) +} + +func (c *Caches) initPollVoteIDs() { + // Calculate maximum cache size. + cap := calculateSliceCacheMax( + config.GetCachePollVoteIDsMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + c.GTS.PollVoteIDs = &SliceCache[string]{Cache: simple.New[string, []string]( + 0, + cap, + )} +} + +func (c *Caches) initReport() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofReport(), // model in-mem size. + config.GetCacheReportMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(r1 *gtsmodel.Report) *gtsmodel.Report { + r2 := new(gtsmodel.Report) + *r2 = *r1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/report.go. + r2.Account = nil + r2.TargetAccount = nil + r2.Statuses = nil + r2.Rules = nil + r2.ActionTakenByAccount = nil + + return r2 + } + + c.GTS.Report.Init(structr.Config[*gtsmodel.Report]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initStatus() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofStatus(), // model in-mem size. + config.GetCacheStatusMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(s1 *gtsmodel.Status) *gtsmodel.Status { + s2 := new(gtsmodel.Status) + *s2 = *s1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/status.go. + s2.Account = nil + s2.InReplyTo = nil + s2.InReplyToAccount = nil + s2.BoostOf = nil + s2.BoostOfAccount = nil + s2.Poll = nil + s2.Attachments = nil + s2.Tags = nil + s2.Mentions = nil + s2.Emojis = nil + s2.CreatedWithApplication = nil + + return s2 + } + + c.GTS.Status.Init(structr.Config[*gtsmodel.Status]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "URI"}, + {Fields: "URL"}, + {Fields: "PollID"}, + {Fields: "BoostOfID,AccountID"}, + {Fields: "ThreadID", Multiple: true}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidateStatus, + }) +} + +func (c *Caches) initStatusFave() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofStatusFave(), // model in-mem size. + config.GetCacheStatusFaveMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(f1 *gtsmodel.StatusFave) *gtsmodel.StatusFave { + f2 := new(gtsmodel.StatusFave) + *f2 = *f1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/statusfave.go. + f2.Account = nil + f2.TargetAccount = nil + f2.Status = nil + + return f2 + } + + c.GTS.StatusFave.Init(structr.Config[*gtsmodel.StatusFave]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "AccountID,StatusID"}, + {Fields: "StatusID", Multiple: true}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidateStatusFave, + }) +} + +func (c *Caches) initStatusFaveIDs() { + // Calculate maximum cache size. + cap := calculateSliceCacheMax( + config.GetCacheStatusFaveIDsMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + c.GTS.StatusFaveIDs = &SliceCache[string]{Cache: simple.New[string, []string]( + 0, + cap, + )} +} + +func (c *Caches) initTag() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofTag(), // model in-mem size. + config.GetCacheTagMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(m1 *gtsmodel.Tag) *gtsmodel.Tag { + m2 := new(gtsmodel.Tag) + *m2 = *m1 + return m2 + } + + c.GTS.Tag.Init(structr.Config[*gtsmodel.Tag]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "Name"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initThreadMute() { + cap := calculateResultCacheMax( + sizeOfThreadMute(), // model in-mem size. + config.GetCacheThreadMuteMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(t1 *gtsmodel.ThreadMute) *gtsmodel.ThreadMute { + t2 := new(gtsmodel.ThreadMute) + *t2 = *t1 + return t2 + } + + c.GTS.ThreadMute.Init(structr.Config[*gtsmodel.ThreadMute]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "ThreadID", Multiple: true}, + {Fields: "AccountID", Multiple: true}, + {Fields: "ThreadID,AccountID"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initTombstone() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofTombstone(), // model in-mem size. + config.GetCacheTombstoneMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(t1 *gtsmodel.Tombstone) *gtsmodel.Tombstone { + t2 := new(gtsmodel.Tombstone) + *t2 = *t1 + return t2 + } + + c.GTS.Tombstone.Init(structr.Config[*gtsmodel.Tombstone]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "URI"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) +} + +func (c *Caches) initUser() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofUser(), // model in-mem size. + config.GetCacheUserMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(u1 *gtsmodel.User) *gtsmodel.User { + u2 := new(gtsmodel.User) + *u2 = *u1 + + // Don't include ptr fields that + // will be populated separately. + // See internal/db/bundb/user.go. + u2.Account = nil + + return u2 + } + + c.GTS.User.Init(structr.Config[*gtsmodel.User]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "AccountID"}, + {Fields: "Email"}, + {Fields: "ConfirmationToken"}, + {Fields: "ExternalID"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + Invalidate: c.OnInvalidateUser, + }) +} + +func (c *Caches) initWebfinger() { + // Calculate maximum cache size. + cap := calculateCacheMax( + sizeofURIStr, sizeofURIStr, + config.GetCacheWebfingerMemRatio(), + ) + + log.Infof(nil, "cache size = %d", cap) + + c.GTS.Webfinger = new(ttl.Cache[string, string]) + c.GTS.Webfinger.Init( + 0, + cap, + 24*time.Hour, + ) +} diff --git a/internal/cache/gts.go b/internal/cache/gts.go deleted file mode 100644 index 507947305..000000000 --- a/internal/cache/gts.go +++ /dev/null @@ -1,1119 +0,0 @@ -// 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" - - "codeberg.org/gruf/go-cache/v3/result" - "codeberg.org/gruf/go-cache/v3/simple" - "codeberg.org/gruf/go-cache/v3/ttl" - "github.com/superseriousbusiness/gotosocial/internal/cache/domain" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/log" -) - -type GTSCaches struct { - account *result.Cache[*gtsmodel.Account] - accountNote *result.Cache[*gtsmodel.AccountNote] - application *result.Cache[*gtsmodel.Application] - block *result.Cache[*gtsmodel.Block] - blockIDs *SliceCache[string] - boostOfIDs *SliceCache[string] - domainAllow *domain.Cache - domainBlock *domain.Cache - emoji *result.Cache[*gtsmodel.Emoji] - emojiCategory *result.Cache[*gtsmodel.EmojiCategory] - follow *result.Cache[*gtsmodel.Follow] - followIDs *SliceCache[string] - followRequest *result.Cache[*gtsmodel.FollowRequest] - followRequestIDs *SliceCache[string] - instance *result.Cache[*gtsmodel.Instance] - inReplyToIDs *SliceCache[string] - list *result.Cache[*gtsmodel.List] - listEntry *result.Cache[*gtsmodel.ListEntry] - marker *result.Cache[*gtsmodel.Marker] - media *result.Cache[*gtsmodel.MediaAttachment] - mention *result.Cache[*gtsmodel.Mention] - notification *result.Cache[*gtsmodel.Notification] - poll *result.Cache[*gtsmodel.Poll] - pollVote *result.Cache[*gtsmodel.PollVote] - pollVoteIDs *SliceCache[string] - report *result.Cache[*gtsmodel.Report] - status *result.Cache[*gtsmodel.Status] - statusFave *result.Cache[*gtsmodel.StatusFave] - statusFaveIDs *SliceCache[string] - tag *result.Cache[*gtsmodel.Tag] - threadMute *result.Cache[*gtsmodel.ThreadMute] - tombstone *result.Cache[*gtsmodel.Tombstone] - user *result.Cache[*gtsmodel.User] - - // TODO: move out of GTS caches since unrelated to DB. - webfinger *ttl.Cache[string, string] // TTL=24hr, sweep=5min -} - -// Init will initialize all the gtsmodel caches in this collection. -// NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe. -func (c *GTSCaches) Init() { - c.initAccount() - c.initAccountNote() - c.initApplication() - c.initBlock() - c.initBlockIDs() - c.initBoostOfIDs() - c.initDomainAllow() - c.initDomainBlock() - c.initEmoji() - c.initEmojiCategory() - c.initFollow() - c.initFollowIDs() - c.initFollowRequest() - c.initFollowRequestIDs() - c.initInReplyToIDs() - c.initInstance() - c.initList() - c.initListEntry() - c.initMarker() - c.initMedia() - c.initMention() - c.initNotification() - c.initPoll() - c.initPollVote() - c.initPollVoteIDs() - c.initReport() - c.initStatus() - c.initStatusFave() - c.initTag() - c.initThreadMute() - c.initStatusFaveIDs() - c.initTombstone() - c.initUser() - c.initWebfinger() -} - -// Start will attempt to start all of the gtsmodel caches, or panic. -func (c *GTSCaches) Start() { - tryUntil("starting *gtsmodel.Webfinger cache", 5, func() bool { - return c.webfinger.Start(5 * time.Minute) - }) -} - -// Stop will attempt to stop all of the gtsmodel caches, or panic. -func (c *GTSCaches) Stop() { - tryUntil("stopping *gtsmodel.Webfinger cache", 5, c.webfinger.Stop) -} - -// Account provides access to the gtsmodel Account database cache. -func (c *GTSCaches) Account() *result.Cache[*gtsmodel.Account] { - return c.account -} - -// AccountNote provides access to the gtsmodel Note database cache. -func (c *GTSCaches) AccountNote() *result.Cache[*gtsmodel.AccountNote] { - return c.accountNote -} - -// Application provides access to the gtsmodel Application database cache. -func (c *GTSCaches) Application() *result.Cache[*gtsmodel.Application] { - return c.application -} - -// Block provides access to the gtsmodel Block (account) database cache. -func (c *GTSCaches) Block() *result.Cache[*gtsmodel.Block] { - return c.block -} - -// FollowIDs provides access to the block IDs database cache. -func (c *GTSCaches) BlockIDs() *SliceCache[string] { - return c.blockIDs -} - -// BoostOfIDs provides access to the boost of IDs list database cache. -func (c *GTSCaches) BoostOfIDs() *SliceCache[string] { - return c.boostOfIDs -} - -// DomainAllow provides access to the domain allow database cache. -func (c *GTSCaches) DomainAllow() *domain.Cache { - return c.domainAllow -} - -// DomainBlock provides access to the domain block database cache. -func (c *GTSCaches) DomainBlock() *domain.Cache { - return c.domainBlock -} - -// Emoji provides access to the gtsmodel Emoji database cache. -func (c *GTSCaches) Emoji() *result.Cache[*gtsmodel.Emoji] { - return c.emoji -} - -// EmojiCategory provides access to the gtsmodel EmojiCategory database cache. -func (c *GTSCaches) EmojiCategory() *result.Cache[*gtsmodel.EmojiCategory] { - return c.emojiCategory -} - -// Follow provides access to the gtsmodel Follow database cache. -func (c *GTSCaches) Follow() *result.Cache[*gtsmodel.Follow] { - return c.follow -} - -// FollowIDs provides access to the follower / following IDs database cache. -// THIS CACHE IS KEYED AS THE FOLLOWING {prefix}{accountID} WHERE PREFIX IS: -// - '>' for following IDs -// - 'l>' for local following IDs -// - '<' for follower IDs -// - 'l<' for local follower IDs -func (c *GTSCaches) FollowIDs() *SliceCache[string] { - return c.followIDs -} - -// FollowRequest provides access to the gtsmodel FollowRequest database cache. -func (c *GTSCaches) FollowRequest() *result.Cache[*gtsmodel.FollowRequest] { - return c.followRequest -} - -// FollowRequestIDs provides access to the follow requester / requesting IDs database -// cache. THIS CACHE IS KEYED AS THE FOLLOWING {prefix}{accountID} WHERE PREFIX IS: -// - '>' for following IDs -// - '<' for follower IDs -func (c *GTSCaches) FollowRequestIDs() *SliceCache[string] { - return c.followRequestIDs -} - -// Instance provides access to the gtsmodel Instance database cache. -func (c *GTSCaches) Instance() *result.Cache[*gtsmodel.Instance] { - return c.instance -} - -// InReplyToIDs provides access to the status in reply to IDs list database cache. -func (c *GTSCaches) InReplyToIDs() *SliceCache[string] { - return c.inReplyToIDs -} - -// List provides access to the gtsmodel List database cache. -func (c *GTSCaches) List() *result.Cache[*gtsmodel.List] { - return c.list -} - -// ListEntry provides access to the gtsmodel ListEntry database cache. -func (c *GTSCaches) ListEntry() *result.Cache[*gtsmodel.ListEntry] { - return c.listEntry -} - -// Marker provides access to the gtsmodel Marker database cache. -func (c *GTSCaches) Marker() *result.Cache[*gtsmodel.Marker] { - return c.marker -} - -// Media provides access to the gtsmodel Media database cache. -func (c *GTSCaches) Media() *result.Cache[*gtsmodel.MediaAttachment] { - return c.media -} - -// Mention provides access to the gtsmodel Mention database cache. -func (c *GTSCaches) Mention() *result.Cache[*gtsmodel.Mention] { - return c.mention -} - -// Notification provides access to the gtsmodel Notification database cache. -func (c *GTSCaches) Notification() *result.Cache[*gtsmodel.Notification] { - return c.notification -} - -// Poll provides access to the gtsmodel Poll database cache. -func (c *GTSCaches) Poll() *result.Cache[*gtsmodel.Poll] { - return c.poll -} - -// PollVote provides access to the gtsmodel PollVote database cache. -func (c *GTSCaches) PollVote() *result.Cache[*gtsmodel.PollVote] { - return c.pollVote -} - -// PollVoteIDs provides access to the poll vote IDs list database cache. -func (c *GTSCaches) PollVoteIDs() *SliceCache[string] { - return c.pollVoteIDs -} - -// Report provides access to the gtsmodel Report database cache. -func (c *GTSCaches) Report() *result.Cache[*gtsmodel.Report] { - return c.report -} - -// Status provides access to the gtsmodel Status database cache. -func (c *GTSCaches) Status() *result.Cache[*gtsmodel.Status] { - return c.status -} - -// StatusFave provides access to the gtsmodel StatusFave database cache. -func (c *GTSCaches) StatusFave() *result.Cache[*gtsmodel.StatusFave] { - return c.statusFave -} - -// StatusFaveIDs provides access to the status fave IDs list database cache. -func (c *GTSCaches) StatusFaveIDs() *SliceCache[string] { - return c.statusFaveIDs -} - -// Tag provides access to the gtsmodel Tag database cache. -func (c *GTSCaches) Tag() *result.Cache[*gtsmodel.Tag] { - return c.tag -} - -// Tombstone provides access to the gtsmodel Tombstone database cache. -func (c *GTSCaches) Tombstone() *result.Cache[*gtsmodel.Tombstone] { - return c.tombstone -} - -// ThreadMute provides access to the gtsmodel ThreadMute database cache. -func (c *GTSCaches) ThreadMute() *result.Cache[*gtsmodel.ThreadMute] { - return c.threadMute -} - -// User provides access to the gtsmodel User database cache. -func (c *GTSCaches) User() *result.Cache[*gtsmodel.User] { - return c.user -} - -// Webfinger provides access to the webfinger URL cache. -func (c *GTSCaches) Webfinger() *ttl.Cache[string, string] { - return c.webfinger -} - -func (c *GTSCaches) initAccount() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofAccount(), // model in-mem size. - config.GetCacheAccountMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(a1 *gtsmodel.Account) *gtsmodel.Account { - a2 := new(gtsmodel.Account) - *a2 = *a1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/account.go. - a2.AvatarMediaAttachment = nil - a2.HeaderMediaAttachment = nil - a2.Emojis = nil - - return a2 - } - - c.account = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "URI"}, - {Name: "URL"}, - {Name: "Username.Domain", AllowZero: true /* domain can be zero i.e. "" */}, - {Name: "PublicKeyURI"}, - {Name: "InboxURI"}, - {Name: "OutboxURI"}, - {Name: "FollowersURI"}, - {Name: "FollowingURI"}, - }, copyF, cap) - - c.account.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initAccountNote() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofAccountNote(), // model in-mem size. - config.GetCacheAccountNoteMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(n1 *gtsmodel.AccountNote) *gtsmodel.AccountNote { - n2 := new(gtsmodel.AccountNote) - *n2 = *n1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/relationship_note.go. - n2.Account = nil - n2.TargetAccount = nil - - return n2 - } - - c.accountNote = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "AccountID.TargetAccountID"}, - }, copyF, cap) - - c.accountNote.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initApplication() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofApplication(), // model in-mem size. - config.GetCacheApplicationMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.application = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "ClientID"}, - }, func(a1 *gtsmodel.Application) *gtsmodel.Application { - a2 := new(gtsmodel.Application) - *a2 = *a1 - return a2 - }, cap) - - c.application.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initBlock() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofBlock(), // model in-mem size. - config.GetCacheBlockMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(b1 *gtsmodel.Block) *gtsmodel.Block { - b2 := new(gtsmodel.Block) - *b2 = *b1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/relationship_block.go. - b2.Account = nil - b2.TargetAccount = nil - - return b2 - } - - c.block = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "URI"}, - {Name: "AccountID.TargetAccountID"}, - {Name: "AccountID", Multi: true}, - {Name: "TargetAccountID", Multi: true}, - }, copyF, cap) - - c.block.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initBlockIDs() { - // Calculate maximum cache size. - cap := calculateSliceCacheMax( - config.GetCacheBlockIDsMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.blockIDs = &SliceCache[string]{Cache: simple.New[string, []string]( - 0, - cap, - )} -} - -func (c *GTSCaches) initBoostOfIDs() { - // Calculate maximum cache size. - cap := calculateSliceCacheMax( - config.GetCacheBoostOfIDsMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.boostOfIDs = &SliceCache[string]{Cache: simple.New[string, []string]( - 0, - cap, - )} -} - -func (c *GTSCaches) initDomainAllow() { - c.domainAllow = new(domain.Cache) -} - -func (c *GTSCaches) initDomainBlock() { - c.domainBlock = new(domain.Cache) -} - -func (c *GTSCaches) initEmoji() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofEmoji(), // model in-mem size. - config.GetCacheEmojiMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(e1 *gtsmodel.Emoji) *gtsmodel.Emoji { - e2 := new(gtsmodel.Emoji) - *e2 = *e1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/emoji.go. - e2.Category = nil - - return e2 - } - - c.emoji = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "URI"}, - {Name: "Shortcode.Domain", AllowZero: true /* domain can be zero i.e. "" */}, - {Name: "ImageStaticURL"}, - {Name: "CategoryID", Multi: true}, - }, copyF, cap) - - c.emoji.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initEmojiCategory() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofEmojiCategory(), // model in-mem size. - config.GetCacheEmojiCategoryMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.emojiCategory = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "Name"}, - }, func(c1 *gtsmodel.EmojiCategory) *gtsmodel.EmojiCategory { - c2 := new(gtsmodel.EmojiCategory) - *c2 = *c1 - return c2 - }, cap) - - c.emojiCategory.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initFollow() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofFollow(), // model in-mem size. - config.GetCacheFollowMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(f1 *gtsmodel.Follow) *gtsmodel.Follow { - f2 := new(gtsmodel.Follow) - *f2 = *f1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/relationship_follow.go. - f2.Account = nil - f2.TargetAccount = nil - - return f2 - } - - c.follow = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "URI"}, - {Name: "AccountID.TargetAccountID"}, - {Name: "AccountID", Multi: true}, - {Name: "TargetAccountID", Multi: true}, - }, copyF, cap) - - c.follow.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initFollowIDs() { - // Calculate maximum cache size. - cap := calculateSliceCacheMax( - config.GetCacheFollowIDsMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.followIDs = &SliceCache[string]{Cache: simple.New[string, []string]( - 0, - cap, - )} -} - -func (c *GTSCaches) initFollowRequest() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofFollowRequest(), // model in-mem size. - config.GetCacheFollowRequestMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(f1 *gtsmodel.FollowRequest) *gtsmodel.FollowRequest { - f2 := new(gtsmodel.FollowRequest) - *f2 = *f1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/relationship_follow_req.go. - f2.Account = nil - f2.TargetAccount = nil - - return f2 - } - - c.followRequest = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "URI"}, - {Name: "AccountID.TargetAccountID"}, - {Name: "AccountID", Multi: true}, - {Name: "TargetAccountID", Multi: true}, - }, copyF, cap) - - c.followRequest.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initFollowRequestIDs() { - // Calculate maximum cache size. - cap := calculateSliceCacheMax( - config.GetCacheFollowRequestIDsMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.followRequestIDs = &SliceCache[string]{Cache: simple.New[string, []string]( - 0, - cap, - )} -} - -func (c *GTSCaches) initInReplyToIDs() { - // Calculate maximum cache size. - cap := calculateSliceCacheMax( - config.GetCacheInReplyToIDsMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.inReplyToIDs = &SliceCache[string]{Cache: simple.New[string, []string]( - 0, - cap, - )} -} - -func (c *GTSCaches) initInstance() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofInstance(), // model in-mem size. - config.GetCacheInstanceMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(i1 *gtsmodel.Instance) *gtsmodel.Instance { - i2 := new(gtsmodel.Instance) - *i2 = *i1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/instance.go. - i2.DomainBlock = nil - i2.ContactAccount = nil - - return i1 - } - - c.instance = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "Domain"}, - }, copyF, cap) - - c.instance.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initList() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofList(), // model in-mem size. - config.GetCacheListMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(l1 *gtsmodel.List) *gtsmodel.List { - l2 := new(gtsmodel.List) - *l2 = *l1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/list.go. - l2.Account = nil - l2.ListEntries = nil - - return l2 - } - - c.list = result.New([]result.Lookup{ - {Name: "ID"}, - }, copyF, cap) - - c.list.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initListEntry() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofListEntry(), // model in-mem size. - config.GetCacheListEntryMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(l1 *gtsmodel.ListEntry) *gtsmodel.ListEntry { - l2 := new(gtsmodel.ListEntry) - *l2 = *l1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/list.go. - l2.Follow = nil - - return l2 - } - - c.listEntry = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "ListID", Multi: true}, - {Name: "FollowID", Multi: true}, - }, copyF, cap) - - c.listEntry.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initMarker() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofMarker(), // model in-mem size. - config.GetCacheMarkerMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.marker = result.New([]result.Lookup{ - {Name: "AccountID.Name"}, - }, func(m1 *gtsmodel.Marker) *gtsmodel.Marker { - m2 := new(gtsmodel.Marker) - *m2 = *m1 - return m2 - }, cap) - - c.marker.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initMedia() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofMedia(), // model in-mem size. - config.GetCacheMediaMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.media = result.New([]result.Lookup{ - {Name: "ID"}, - }, func(m1 *gtsmodel.MediaAttachment) *gtsmodel.MediaAttachment { - m2 := new(gtsmodel.MediaAttachment) - *m2 = *m1 - return m2 - }, cap) - - c.media.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initMention() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofMention(), // model in-mem size. - config.GetCacheMentionMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(m1 *gtsmodel.Mention) *gtsmodel.Mention { - m2 := new(gtsmodel.Mention) - *m2 = *m1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/mention.go. - m2.Status = nil - m2.OriginAccount = nil - m2.TargetAccount = nil - - return m2 - } - - c.mention = result.New([]result.Lookup{ - {Name: "ID"}, - }, copyF, cap) - - c.mention.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initNotification() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofNotification(), // model in-mem size. - config.GetCacheNotificationMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(n1 *gtsmodel.Notification) *gtsmodel.Notification { - n2 := new(gtsmodel.Notification) - *n2 = *n1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/notification.go. - n2.Status = nil - n2.OriginAccount = nil - n2.TargetAccount = nil - - return n2 - } - - c.notification = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "NotificationType.TargetAccountID.OriginAccountID.StatusID"}, - }, copyF, cap) - - c.notification.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initPoll() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofPoll(), // model in-mem size. - config.GetCachePollMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(p1 *gtsmodel.Poll) *gtsmodel.Poll { - p2 := new(gtsmodel.Poll) - *p2 = *p1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/poll.go. - p2.Status = nil - - // Don't include ephemeral fields - // which are only expected to be - // set on ONE poll instance. - p2.Closing = false - - return p2 - } - - c.poll = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "StatusID"}, - }, copyF, cap) - - c.poll.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initPollVote() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofPollVote(), // model in-mem size. - config.GetCachePollVoteMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(v1 *gtsmodel.PollVote) *gtsmodel.PollVote { - v2 := new(gtsmodel.PollVote) - *v2 = *v1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/poll.go. - v2.Account = nil - v2.Poll = nil - - return v2 - } - - c.pollVote = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "PollID.AccountID"}, - {Name: "PollID", Multi: true}, - }, copyF, cap) - - c.pollVote.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initPollVoteIDs() { - // Calculate maximum cache size. - cap := calculateSliceCacheMax( - config.GetCachePollVoteIDsMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.pollVoteIDs = &SliceCache[string]{Cache: simple.New[string, []string]( - 0, - cap, - )} -} - -func (c *GTSCaches) initReport() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofReport(), // model in-mem size. - config.GetCacheReportMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(r1 *gtsmodel.Report) *gtsmodel.Report { - r2 := new(gtsmodel.Report) - *r2 = *r1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/report.go. - r2.Account = nil - r2.TargetAccount = nil - r2.Statuses = nil - r2.Rules = nil - r2.ActionTakenByAccount = nil - - return r2 - } - - c.report = result.New([]result.Lookup{ - {Name: "ID"}, - }, copyF, cap) - - c.report.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initStatus() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofStatus(), // model in-mem size. - config.GetCacheStatusMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(s1 *gtsmodel.Status) *gtsmodel.Status { - s2 := new(gtsmodel.Status) - *s2 = *s1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/status.go. - s2.Account = nil - s2.InReplyTo = nil - s2.InReplyToAccount = nil - s2.BoostOf = nil - s2.BoostOfAccount = nil - s2.Poll = nil - s2.Attachments = nil - s2.Tags = nil - s2.Mentions = nil - s2.Emojis = nil - s2.CreatedWithApplication = nil - - return s2 - } - - c.status = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "URI"}, - {Name: "URL"}, - {Name: "PollID"}, - {Name: "BoostOfID.AccountID"}, - {Name: "ThreadID", Multi: true}, - }, copyF, cap) - - c.status.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initStatusFave() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofStatusFave(), // model in-mem size. - config.GetCacheStatusFaveMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(f1 *gtsmodel.StatusFave) *gtsmodel.StatusFave { - f2 := new(gtsmodel.StatusFave) - *f2 = *f1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/statusfave.go. - f2.Account = nil - f2.TargetAccount = nil - f2.Status = nil - - return f2 - } - - c.statusFave = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "AccountID.StatusID"}, - {Name: "StatusID", Multi: true}, - }, copyF, cap) - - c.statusFave.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initStatusFaveIDs() { - // Calculate maximum cache size. - cap := calculateSliceCacheMax( - config.GetCacheStatusFaveIDsMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.statusFaveIDs = &SliceCache[string]{Cache: simple.New[string, []string]( - 0, - cap, - )} -} - -func (c *GTSCaches) initTag() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofTag(), // model in-mem size. - config.GetCacheTagMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.tag = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "Name"}, - }, func(m1 *gtsmodel.Tag) *gtsmodel.Tag { - m2 := new(gtsmodel.Tag) - *m2 = *m1 - return m2 - }, cap) - - c.tag.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initThreadMute() { - cap := calculateResultCacheMax( - sizeOfThreadMute(), // model in-mem size. - config.GetCacheThreadMuteMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.threadMute = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "ThreadID", Multi: true}, - {Name: "AccountID", Multi: true}, - {Name: "ThreadID.AccountID"}, - }, func(t1 *gtsmodel.ThreadMute) *gtsmodel.ThreadMute { - t2 := new(gtsmodel.ThreadMute) - *t2 = *t1 - return t2 - }, cap) - - c.threadMute.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initTombstone() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofTombstone(), // model in-mem size. - config.GetCacheTombstoneMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.tombstone = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "URI"}, - }, func(t1 *gtsmodel.Tombstone) *gtsmodel.Tombstone { - t2 := new(gtsmodel.Tombstone) - *t2 = *t1 - return t2 - }, cap) - - c.tombstone.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initUser() { - // Calculate maximum cache size. - cap := calculateResultCacheMax( - sizeofUser(), // model in-mem size. - config.GetCacheUserMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - copyF := func(u1 *gtsmodel.User) *gtsmodel.User { - u2 := new(gtsmodel.User) - *u2 = *u1 - - // Don't include ptr fields that - // will be populated separately. - // See internal/db/bundb/user.go. - u2.Account = nil - - return u2 - } - - c.user = result.New([]result.Lookup{ - {Name: "ID"}, - {Name: "AccountID"}, - {Name: "Email"}, - {Name: "ConfirmationToken"}, - {Name: "ExternalID"}, - }, copyF, cap) - - c.user.IgnoreErrors(ignoreErrors) -} - -func (c *GTSCaches) initWebfinger() { - // Calculate maximum cache size. - cap := calculateCacheMax( - sizeofURIStr, sizeofURIStr, - config.GetCacheWebfingerMemRatio(), - ) - - log.Infof(nil, "cache size = %d", cap) - - c.webfinger = ttl.New[string, string]( - 0, - cap, - 24*time.Hour, - ) -} diff --git a/internal/cache/invalidate.go b/internal/cache/invalidate.go new file mode 100644 index 000000000..d85c503da --- /dev/null +++ b/internal/cache/invalidate.go @@ -0,0 +1,192 @@ +// 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 ( + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +// Below are cache invalidation hooks between other caches, +// 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. + +func (c *Caches) OnInvalidateAccount(account *gtsmodel.Account) { + // Invalidate account ID cached visibility. + c.Visibility.Invalidate("ItemID", account.ID) + c.Visibility.Invalidate("RequesterID", account.ID) + + // Invalidate this account's + // following / follower lists. + // (see FollowIDs() comment for details). + c.GTS.FollowIDs.InvalidateAll( + ">"+account.ID, + "l>"+account.ID, + "<"+account.ID, + "l<"+account.ID, + ) + + // Invalidate this account's + // follow requesting / request lists. + // (see FollowRequestIDs() comment for details). + c.GTS.FollowRequestIDs.InvalidateAll( + ">"+account.ID, + "<"+account.ID, + ) + + // Invalidate this account's block lists. + c.GTS.BlockIDs.Invalidate(account.ID) +} + +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 block target account ID cached visibility. + c.Visibility.Invalidate("ItemID", block.TargetAccountID) + c.Visibility.Invalidate("RequesterID", block.TargetAccountID) + + // Invalidate source account's block lists. + c.GTS.BlockIDs.Invalidate(block.AccountID) +} + +func (c *Caches) OnInvalidateEmojiCategory(category *gtsmodel.EmojiCategory) { + // Invalidate any emoji in this category. + c.GTS.Emoji.Invalidate("CategoryID", category.ID) +} + +func (c *Caches) OnInvalidateFollow(follow *gtsmodel.Follow) { + // Invalidate follow request with this same ID. + c.GTS.FollowRequest.Invalidate("ID", follow.ID) + + // Invalidate any related list entries. + c.GTS.ListEntry.Invalidate("FollowID", follow.ID) + + // Invalidate follow origin account ID cached visibility. + c.Visibility.Invalidate("ItemID", follow.AccountID) + c.Visibility.Invalidate("RequesterID", follow.AccountID) + + // Invalidate follow target account ID cached visibility. + c.Visibility.Invalidate("ItemID", follow.TargetAccountID) + c.Visibility.Invalidate("RequesterID", follow.TargetAccountID) + + // Invalidate source account's following + // lists, and destination's follwer lists. + // (see FollowIDs() comment for details). + c.GTS.FollowIDs.InvalidateAll( + ">"+follow.AccountID, + "l>"+follow.AccountID, + "<"+follow.AccountID, + "l<"+follow.AccountID, + "<"+follow.TargetAccountID, + "l<"+follow.TargetAccountID, + ">"+follow.TargetAccountID, + "l>"+follow.TargetAccountID, + ) +} + +func (c *Caches) OnInvalidateFollowRequest(followReq *gtsmodel.FollowRequest) { + // Invalidate follow with this same ID. + c.GTS.Follow.Invalidate("ID", followReq.ID) + + // Invalidate source account's followreq + // lists, and destinations follow req lists. + // (see FollowRequestIDs() comment for details). + c.GTS.FollowRequestIDs.InvalidateAll( + ">"+followReq.AccountID, + "<"+followReq.AccountID, + ">"+followReq.TargetAccountID, + "<"+followReq.TargetAccountID, + ) +} + +func (c *Caches) OnInvalidateList(list *gtsmodel.List) { + // Invalidate all cached entries of this list. + c.GTS.ListEntry.Invalidate("ListID", list.ID) +} + +func (c *Caches) OnInvalidateMedia(media *gtsmodel.MediaAttachment) { + if (media.Avatar != nil && *media.Avatar) || + (media.Header != nil && *media.Header) { + // Invalidate cache of attaching account. + c.GTS.Account.Invalidate("ID", media.AccountID) + } + + if media.StatusID != "" { + // Invalidate cache of attaching status. + c.GTS.Status.Invalidate("ID", media.StatusID) + } +} + +func (c *Caches) OnInvalidatePoll(poll *gtsmodel.Poll) { + // Invalidate all cached votes of this poll. + c.GTS.PollVote.Invalidate("PollID", poll.ID) + + // Invalidate cache of poll vote IDs. + c.GTS.PollVoteIDs.Invalidate(poll.ID) +} + +func (c *Caches) OnInvalidatePollVote(vote *gtsmodel.PollVote) { + // Invalidate cached poll (contains no. votes). + c.GTS.Poll.Invalidate("ID", vote.PollID) + + // Invalidate cache of poll vote IDs. + c.GTS.PollVoteIDs.Invalidate(vote.PollID) +} + +func (c *Caches) OnInvalidateStatus(status *gtsmodel.Status) { + // Invalidate status ID cached visibility. + c.Visibility.Invalidate("ItemID", status.ID) + + for _, id := range status.AttachmentIDs { + // 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 + // aware of the status ID they are linked to. + // + // c.GTS.Media().Invalidate("StatusID") will not work. + c.GTS.Media.Invalidate("ID", id) + } + + if status.BoostOfID != "" { + // Invalidate boost ID list of the original status. + c.GTS.BoostOfIDs.Invalidate(status.BoostOfID) + } + + if status.InReplyToID != "" { + // Invalidate in reply to ID list of original status. + c.GTS.InReplyToIDs.Invalidate(status.InReplyToID) + } + + if status.PollID != "" { + // Invalidate cache of attached poll ID. + c.GTS.Poll.Invalidate("ID", status.PollID) + } +} + +func (c *Caches) OnInvalidateStatusFave(fave *gtsmodel.StatusFave) { + // Invalidate status fave ID list for this status. + c.GTS.StatusFaveIDs.Invalidate(fave.StatusID) +} + +func (c *Caches) OnInvalidateUser(user *gtsmodel.User) { + // Invalidate local account ID cached visibility. + c.Visibility.Invalidate("ItemID", user.AccountID) + c.Visibility.Invalidate("RequesterID", user.AccountID) +} diff --git a/internal/cache/visibility.go b/internal/cache/visibility.go index 8c534206b..878efcdb8 100644 --- a/internal/cache/visibility.go +++ b/internal/cache/visibility.go @@ -18,18 +18,16 @@ package cache import ( - "codeberg.org/gruf/go-cache/v3/result" + "codeberg.org/gruf/go-structr" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/log" ) type VisibilityCache struct { - *result.Cache[*CachedVisibility] + structr.Cache[*CachedVisibility] } -// Init will initialize the visibility cache in this collection. -// NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe. -func (c *VisibilityCache) Init() { +func (c *Caches) initVisibility() { // Calculate maximum cache size. cap := calculateResultCacheMax( sizeofVisibility(), // model in-mem size. @@ -38,25 +36,22 @@ func (c *VisibilityCache) Init() { log.Infof(nil, "Visibility cache size = %d", cap) - c.Cache = result.New([]result.Lookup{ - {Name: "ItemID", Multi: true}, - {Name: "RequesterID", Multi: true}, - {Name: "Type.RequesterID.ItemID"}, - }, func(v1 *CachedVisibility) *CachedVisibility { + copyF := func(v1 *CachedVisibility) *CachedVisibility { v2 := new(CachedVisibility) *v2 = *v1 return v2 - }, cap) + } - c.Cache.IgnoreErrors(ignoreErrors) -} - -// Start will attempt to start the visibility cache, or panic. -func (c *VisibilityCache) Start() { -} - -// Stop will attempt to stop the visibility cache, or panic. -func (c *VisibilityCache) Stop() { + c.Visibility.Init(structr.Config[*CachedVisibility]{ + Indices: []structr.IndexConfig{ + {Fields: "ItemID", Multiple: true}, + {Fields: "RequesterID", Multiple: true}, + {Fields: "Type,RequesterID,ItemID"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + CopyValue: copyF, + }) } // VisibilityType represents a visibility lookup type. |