summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2023-08-22 15:41:51 +0200
committerLibravatar GitHub <noreply@github.com>2023-08-22 15:41:51 +0200
commit94d16631bc0b6b5aa2900a5b059cd80c524c3eb9 (patch)
tree680fb0bf37b4d2b926824ef3091a5db7daa18b43
parent[feature] Make log format configurable (#2130) (diff)
downloadgotosocial-94d16631bc0b6b5aa2900a5b059cd80c524c3eb9.tar.xz
[performance] Rework home timeline query to use cache more (#2148)
-rw-r--r--internal/db/bundb/timeline.go51
-rw-r--r--internal/db/bundb/timeline_test.go33
2 files changed, 68 insertions, 16 deletions
diff --git a/internal/db/bundb/timeline.go b/internal/db/bundb/timeline.go
index 1230a84d4..f63937bc1 100644
--- a/internal/db/bundb/timeline.go
+++ b/internal/db/bundb/timeline.go
@@ -19,11 +19,13 @@ package bundb
import (
"context"
+ "errors"
"fmt"
"time"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
@@ -101,22 +103,39 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI
q = q.Order("status.id ASC")
}
- // Subquery to select target (followed) account
- // IDs from follows owned by given accountID.
- subQ := t.db.
- NewSelect().
- TableExpr("? AS ?", bun.Ident("follows"), bun.Ident("follow")).
- Column("follow.target_account_id").
- Where("? = ?", bun.Ident("follow.account_id"), accountID)
-
- // Use the subquery in a WhereGroup here to specify that we want EITHER
- // - statuses posted by accountID itself OR
- // - statuses posted by accounts that accountID follows
- q = q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
- return q.
- Where("? = ?", bun.Ident("status.account_id"), accountID).
- WhereOr("? IN (?)", bun.Ident("status.account_id"), subQ)
- })
+ // As this is the home timeline, it should be
+ // populated by statuses from accounts followed
+ // by accountID, and posts from accountID itself.
+ //
+ // So, begin by seeing who accountID follows.
+ // It should be a little cheaper to do this in
+ // a separate query like this, rather than using
+ // a join, since followIDs are cached in memory.
+ follows, err := t.state.DB.GetAccountFollows(
+ gtscontext.SetBarebones(ctx),
+ accountID,
+ )
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ return nil, gtserror.Newf("db error getting follows for account %s: %w", accountID, err)
+ }
+
+ // Extract just the accountID from each follow.
+ targetAccountIDs := make([]string, len(follows)+1)
+ for i, f := range follows {
+ targetAccountIDs[i] = f.TargetAccountID
+ }
+
+ // Add accountID itself as a pseudo follow so that
+ // accountID can see its own posts in the timeline.
+ targetAccountIDs[len(targetAccountIDs)-1] = accountID
+
+ // Select only statuses authored by
+ // accounts with IDs in the slice.
+ q = q.Where(
+ "? IN (?)",
+ bun.Ident("status.account_id"),
+ bun.In(targetAccountIDs),
+ )
if err := q.Scan(ctx, &statusIDs); err != nil {
return nil, err
diff --git a/internal/db/bundb/timeline_test.go b/internal/db/bundb/timeline_test.go
index 2bc9c9d0d..e5a78dfd1 100644
--- a/internal/db/bundb/timeline_test.go
+++ b/internal/db/bundb/timeline_test.go
@@ -24,6 +24,7 @@ import (
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/ap"
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/util"
@@ -156,6 +157,38 @@ func (suite *TimelineTestSuite) TestGetHomeTimeline() {
suite.checkStatuses(s, id.Highest, id.Lowest, 16)
}
+func (suite *TimelineTestSuite) TestGetHomeTimelineNoFollowing() {
+ var (
+ ctx = context.Background()
+ viewingAccount = suite.testAccounts["local_account_1"]
+ )
+
+ // Remove all of viewingAccount's follows.
+ follows, err := suite.state.DB.GetAccountFollows(
+ gtscontext.SetBarebones(ctx),
+ viewingAccount.ID,
+ )
+
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ for _, f := range follows {
+ if err := suite.state.DB.DeleteFollowByID(ctx, f.ID); err != nil {
+ suite.FailNow(err.Error())
+ }
+ }
+
+ // Query should work fine; though far
+ // fewer statuses will be returned ofc.
+ s, err := suite.db.GetHomeTimeline(ctx, viewingAccount.ID, "", "", "", 20, false)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.checkStatuses(s, id.Highest, id.Lowest, 5)
+}
+
func (suite *TimelineTestSuite) TestGetHomeTimelineWithFutureStatus() {
var (
ctx = context.Background()