[performance] Rework home timeline query to use cache more (#2148)

This commit is contained in:
tobi 2023-08-22 15:41:51 +02:00 committed by GitHub
parent 4ae16bce8c
commit 94d16631bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 67 additions and 15 deletions

View file

@ -19,11 +19,13 @@
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"time" "time"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext" "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log" "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") q = q.Order("status.id ASC")
} }
// Subquery to select target (followed) account // As this is the home timeline, it should be
// IDs from follows owned by given accountID. // populated by statuses from accounts followed
subQ := t.db. // by accountID, and posts from accountID itself.
NewSelect(). //
TableExpr("? AS ?", bun.Ident("follows"), bun.Ident("follow")). // So, begin by seeing who accountID follows.
Column("follow.target_account_id"). // It should be a little cheaper to do this in
Where("? = ?", bun.Ident("follow.account_id"), accountID) // 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)
}
// Use the subquery in a WhereGroup here to specify that we want EITHER // Extract just the accountID from each follow.
// - statuses posted by accountID itself OR targetAccountIDs := make([]string, len(follows)+1)
// - statuses posted by accounts that accountID follows for i, f := range follows {
q = q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery { targetAccountIDs[i] = f.TargetAccountID
return q. }
Where("? = ?", bun.Ident("status.account_id"), accountID).
WhereOr("? IN (?)", bun.Ident("status.account_id"), subQ) // 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 { if err := q.Scan(ctx, &statusIDs); err != nil {
return nil, err return nil, err

View file

@ -24,6 +24,7 @@
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
@ -156,6 +157,38 @@ func (suite *TimelineTestSuite) TestGetHomeTimeline() {
suite.checkStatuses(s, id.Highest, id.Lowest, 16) 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() { func (suite *TimelineTestSuite) TestGetHomeTimelineWithFutureStatus() {
var ( var (
ctx = context.Background() ctx = context.Background()