[performance] Rework home timeline query to use cache more (#2148)
This commit is contained in:
parent
4ae16bce8c
commit
94d16631bc
|
@ -19,11 +19,13 @@ package bundb
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
|
|
||||||
"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()
|
||||||
|
|
Loading…
Reference in New Issue