summaryrefslogtreecommitdiff
path: root/internal/timeline/index.go
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2025-04-26 09:56:15 +0000
committerLibravatar GitHub <noreply@github.com>2025-04-26 09:56:15 +0000
commit6a6a4993338262f87df34c9be051bfaac75c1829 (patch)
treebfbda090dc4b25efdd34145c016d7cc7b9c14d6e /internal/timeline/index.go
parent[chore] Move deps to code.superseriousbusiness.org (#4054) (diff)
downloadgotosocial-6a6a4993338262f87df34c9be051bfaac75c1829.tar.xz
[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type * further work rewriting timeline caching * more work integration new timeline code * remove old code * add local timeline, fix up merge conflicts * remove old use of go-bytes * implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr * remove old timeline package, add local timeline cache * remove references to old timeline types that needed starting up in tests * start adding page validation * fix test-identified timeline cache package issues * fix up more tests, fix missing required changes, etc * add exclusion for test.out in gitignore * clarify some things better in code comments * tweak cache size limits * fix list timeline cache fetching * further list timeline fixes * linter, ssssssssshhhhhhhhhhhh please * fix linter hints * reslice the output if it's beyond length of 'lim' * remove old timeline initialization code, bump go-structr to v0.9.4 * continued from previous commit * improved code comments * don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts * finish writing more code comments * some variable renaming, for ease of following * change the way we update lo,hi paging values during timeline load * improved code comments for updated / returned lo , hi paging values * finish writing code comments for the StatusTimeline{} type itself * fill in more code comments * update go-structr version to latest with changed timeline unique indexing logic * have a local and public timeline *per user* * rewrite calls to public / local timeline calls * remove the zero length check, as lo, hi values might still be set * simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions * swap the lo, hi values :facepalm: * add (now) missing slice reverse of tag timeline statuses when paging ASC * remove local / public caches (is out of scope for this work), share more timeline code * remove unnecessary change * again, remove more unused code * remove unused function to appease the linter * move boost checking to prepare function * fix use of timeline.lastOrder, fix incorrect range functions used * remove comments for repeat code * remove the boost logic from prepare function * do a maximum of 5 loads, not 10 * add repeat boost filtering logic, update go-structr, general improvements * more code comments * add important note * fix timeline tests now that timelines are returned in page order * remove unused field * add StatusTimeline{} tests * add more status timeline tests * start adding preloading support * ensure repeat boosts are marked in preloaded entries * share a bunch of the database load code in timeline cache, don't clear timelines on relationship change * add logic to allow dynamic clear / preloading of timelines * comment-out unused functions, but leave in place as we might end-up using them * fix timeline preload state check * much improved status timeline code comments * more code comments, don't bother inserting statuses if timeline not preloaded * shift around some logic to make sure things aren't accidentally left set * finish writing code comments * remove trim-after-insert behaviour * fix-up some comments referring to old logic * remove unsetting of lo, hi * fix preload repeatBoost checking logic * don't return on status filter errors, these are usually transient * better concurrency safety in Clear() and Done() * fix test broken due to addition of preloader * fix repeatBoost logic that doesn't account for already-hidden repeatBoosts * ensure edit submodels are dropped on cache insertion * update code-comment to expand CAS accronym * use a plus1hULID() instead of 24h * remove unused functions * add note that public / local timeline requester can be nil * fix incorrect visibility filtering of tag timeline statuses * ensure we filter home timeline statuses on local only * some small re-orderings to confirm query params in correct places * fix the local only home timeline filter func
Diffstat (limited to 'internal/timeline/index.go')
-rw-r--r--internal/timeline/index.go283
1 files changed, 0 insertions, 283 deletions
diff --git a/internal/timeline/index.go b/internal/timeline/index.go
deleted file mode 100644
index 6abb6d28d..000000000
--- a/internal/timeline/index.go
+++ /dev/null
@@ -1,283 +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 timeline
-
-import (
- "container/list"
- "context"
- "errors"
-
- "codeberg.org/gruf/go-kv"
- "github.com/superseriousbusiness/gotosocial/internal/db"
- "github.com/superseriousbusiness/gotosocial/internal/gtserror"
- "github.com/superseriousbusiness/gotosocial/internal/log"
-)
-
-func (t *timeline) indexXBetweenIDs(ctx context.Context, amount int, behindID string, beforeID string, frontToBack bool) error {
- l := log.
- WithContext(ctx).
- WithFields(kv.Fields{
- {"amount", amount},
- {"behindID", behindID},
- {"beforeID", beforeID},
- {"frontToBack", frontToBack},
- }...)
- l.Trace("entering indexXBetweenIDs")
-
- if beforeID >= behindID {
- // This is an impossible situation, we
- // can't index anything between these.
- return nil
- }
-
- t.Lock()
- defer t.Unlock()
-
- // Lazily init indexed items.
- if t.items.data == nil {
- t.items.data = &list.List{}
- t.items.data.Init()
- }
-
- // Start by mapping out the list so we know what
- // we have to do. Depending on the current state
- // of the list we might not have to do *anything*.
- var (
- position int
- listLen = t.items.data.Len()
- behindIDPosition int
- beforeIDPosition int
- )
-
- for e := t.items.data.Front(); e != nil; e = e.Next() {
- entry := e.Value.(*indexedItemsEntry)
-
- position++
-
- if entry.itemID > behindID {
- l.Trace("item is too new, continuing")
- continue
- }
-
- if behindIDPosition == 0 {
- // Gone far enough through the list
- // and found our behindID mark.
- // We only need to set this once.
- l.Tracef("found behindID mark %s at position %d", entry.itemID, position)
- behindIDPosition = position
- }
-
- if entry.itemID >= beforeID {
- // Push the beforeID mark back
- // one place every iteration.
- l.Tracef("setting beforeID mark %s at position %d", entry.itemID, position)
- beforeIDPosition = position
- }
-
- if entry.itemID <= beforeID {
- // We've gone beyond the bounds of
- // items we're interested in; stop.
- l.Trace("reached older items, breaking")
- break
- }
- }
-
- // We can now figure out if we need to make db calls.
- var grabMore bool
- switch {
- case listLen < amount:
- // The whole list is shorter than the
- // amount we're being asked to return,
- // make up the difference.
- grabMore = true
- amount -= listLen
- case beforeIDPosition-behindIDPosition < amount:
- // Not enough items between behindID and
- // beforeID to return amount required,
- // try to get more.
- grabMore = true
- }
-
- if !grabMore {
- // We're good!
- return nil
- }
-
- // Fetch additional items.
- items, err := t.grab(ctx, amount, behindID, beforeID, frontToBack)
- if err != nil {
- return err
- }
-
- // Index all the items we got. We already have
- // a lock on the timeline, so don't call IndexOne
- // here, since that will also try to get a lock!
- for _, item := range items {
- entry := &indexedItemsEntry{
- itemID: item.GetID(),
- boostOfID: item.GetBoostOfID(),
- accountID: item.GetAccountID(),
- boostOfAccountID: item.GetBoostOfAccountID(),
- }
-
- if _, err := t.items.insertIndexed(ctx, entry); err != nil {
- return gtserror.Newf("error inserting entry with itemID %s into index: %w", entry.itemID, err)
- }
- }
-
- return nil
-}
-
-// grab wraps the timeline's grabFunction in paging + filtering logic.
-func (t *timeline) grab(ctx context.Context, amount int, behindID string, beforeID string, frontToBack bool) ([]Timelineable, error) {
- var (
- sinceID string
- minID string
- grabbed int
- maxID = behindID
- filtered = make([]Timelineable, 0, amount)
- )
-
- if frontToBack {
- sinceID = beforeID
- } else {
- minID = beforeID
- }
-
- for attempts := 0; attempts < 5; attempts++ {
- if grabbed >= amount {
- // We got everything we needed.
- break
- }
-
- items, stop, err := t.grabFunction(
- ctx,
- t.timelineID,
- maxID,
- sinceID,
- minID,
- // Don't grab more than we need to.
- amount-grabbed,
- )
- if err != nil {
- // Grab function already checks for
- // db.ErrNoEntries, so if an error
- // is returned then it's a real one.
- return nil, err
- }
-
- if stop || len(items) == 0 {
- // No items left.
- break
- }
-
- // Set next query parameters.
- if frontToBack {
- // Page down.
- maxID = items[len(items)-1].GetID()
- if maxID <= beforeID {
- // Can't go any further.
- break
- }
- } else {
- // Page up.
- minID = items[0].GetID()
- if minID >= behindID {
- // Can't go any further.
- break
- }
- }
-
- for _, item := range items {
- ok, err := t.filterFunction(ctx, t.timelineID, item)
- if err != nil {
- if !errors.Is(err, db.ErrNoEntries) {
- // Real error here.
- return nil, err
- }
- log.Warnf(ctx, "errNoEntries while filtering item %s: %s", item.GetID(), err)
- continue
- }
-
- if ok {
- filtered = append(filtered, item)
- grabbed++ // count this as grabbed
- }
- }
- }
-
- return filtered, nil
-}
-
-func (t *timeline) IndexAndPrepareOne(ctx context.Context, statusID string, boostOfID string, accountID string, boostOfAccountID string) (bool, error) {
- t.Lock()
- defer t.Unlock()
-
- postIndexEntry := &indexedItemsEntry{
- itemID: statusID,
- boostOfID: boostOfID,
- accountID: accountID,
- boostOfAccountID: boostOfAccountID,
- }
-
- if inserted, err := t.items.insertIndexed(ctx, postIndexEntry); err != nil {
- return false, gtserror.Newf("error inserting indexed: %w", err)
- } else if !inserted {
- // Entry wasn't inserted, so
- // don't bother preparing it.
- return false, nil
- }
-
- preparable, err := t.prepareFunction(ctx, t.timelineID, statusID)
- if err != nil {
- return true, gtserror.Newf("error preparing: %w", err)
- }
- postIndexEntry.prepared = preparable
-
- return true, nil
-}
-
-func (t *timeline) Len() int {
- t.Lock()
- defer t.Unlock()
-
- if t.items == nil || t.items.data == nil {
- // indexedItems hasnt been initialized yet.
- return 0
- }
-
- return t.items.data.Len()
-}
-
-func (t *timeline) OldestIndexedItemID() string {
- t.Lock()
- defer t.Unlock()
-
- if t.items == nil || t.items.data == nil {
- // indexedItems hasnt been initialized yet.
- return ""
- }
-
- e := t.items.data.Back()
- if e == nil {
- // List was empty.
- return ""
- }
-
- return e.Value.(*indexedItemsEntry).itemID
-}