[feature/performance] Store account stats in separate table (#2831)

* [feature/performance] Store account stats in separate table, get stats from remote

* test account stats

* add some missing increment / decrement calls

* change stats function signatures

* rejig logging a bit

* use lock when updating stats
This commit is contained in:
tobi
2024-04-16 13:10:13 +02:00
committed by GitHub
parent f79d50b9b2
commit 3cceed11b2
43 changed files with 1285 additions and 450 deletions

View File

@ -82,18 +82,26 @@ func (p *Processor) PinCreate(ctx context.Context, requestingAccount *gtsmodel.A
return nil, errWithCode
}
// Get a lock on this account.
unlock := p.state.AccountLocks.Lock(requestingAccount.URI)
defer unlock()
if !targetStatus.PinnedAt.IsZero() {
err := errors.New("status already pinned")
return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error())
}
pinnedCount, err := p.state.DB.CountAccountPinned(ctx, requestingAccount.ID)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking number of pinned statuses: %w", err))
// Ensure account stats populated.
if requestingAccount.Stats == nil {
if err := p.state.DB.PopulateAccountStats(ctx, requestingAccount); err != nil {
err = gtserror.Newf("db error getting account stats: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
}
pinnedCount := *requestingAccount.Stats.StatusesPinnedCount
if pinnedCount >= allowedPinnedCount {
err = fmt.Errorf("status pin limit exceeded, you've already pinned %d status(es) out of %d", pinnedCount, allowedPinnedCount)
err := fmt.Errorf("status pin limit exceeded, you've already pinned %d status(es) out of %d", pinnedCount, allowedPinnedCount)
return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error())
}
@ -103,6 +111,17 @@ func (p *Processor) PinCreate(ctx context.Context, requestingAccount *gtsmodel.A
return nil, gtserror.NewErrorInternalError(err)
}
// Update account stats.
*requestingAccount.Stats.StatusesPinnedCount++
if err := p.state.DB.UpdateAccountStats(
ctx,
requestingAccount.Stats,
"statuses_pinned_count",
); err != nil {
err = gtserror.Newf("db error updating stats: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
if err := p.c.InvalidateTimelinedStatus(ctx, requestingAccount.ID, targetStatusID); err != nil {
err = gtserror.Newf("error invalidating status from timelines: %w", err)
return nil, gtserror.NewErrorInternalError(err)
@ -128,16 +147,45 @@ func (p *Processor) PinRemove(ctx context.Context, requestingAccount *gtsmodel.A
return nil, errWithCode
}
// Get a lock on this account.
unlock := p.state.AccountLocks.Lock(requestingAccount.URI)
defer unlock()
if targetStatus.PinnedAt.IsZero() {
// Status already not pinned.
return p.c.GetAPIStatus(ctx, requestingAccount, targetStatus)
}
// Ensure account stats populated.
if requestingAccount.Stats == nil {
if err := p.state.DB.PopulateAccountStats(ctx, requestingAccount); err != nil {
err = gtserror.Newf("db error getting account stats: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
}
targetStatus.PinnedAt = time.Time{}
if err := p.state.DB.UpdateStatus(ctx, targetStatus, "pinned_at"); err != nil {
err = gtserror.Newf("db error unpinning status: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
// Update account stats.
//
// Clamp to 0 to avoid funny business.
*requestingAccount.Stats.StatusesPinnedCount--
if *requestingAccount.Stats.StatusesPinnedCount < 0 {
*requestingAccount.Stats.StatusesPinnedCount = 0
}
if err := p.state.DB.UpdateAccountStats(
ctx,
requestingAccount.Stats,
"statuses_pinned_count",
); err != nil {
err = gtserror.Newf("db error updating stats: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
if err := p.c.InvalidateTimelinedStatus(ctx, requestingAccount.ID, targetStatusID); err != nil {
err = gtserror.Newf("error invalidating status from timelines: %w", err)
return nil, gtserror.NewErrorInternalError(err)