perf: reduce list query n+1
This commit is contained in:
@@ -36,13 +36,23 @@ func (s *tenant) List(ctx context.Context, filter *dto.TenantListFilter) (*reque
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
tenantIDs := make([]int64, 0, len(list))
|
||||
for _, t := range list {
|
||||
tenantIDs = append(tenantIDs, t.ID)
|
||||
}
|
||||
|
||||
// 批量统计关注数与内容数,避免逐条 Count。
|
||||
followerMap, err := s.countFollowers(ctx, tenantIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contentMap, err := s.countPublishedContents(ctx, tenantIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var data []dto.TenantProfile
|
||||
for _, t := range list {
|
||||
followers, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(t.ID)).Count()
|
||||
contents, _ := models.ContentQuery.WithContext(ctx).
|
||||
Where(models.ContentQuery.TenantID.Eq(t.ID), models.ContentQuery.Status.Eq(consts.ContentStatusPublished)).
|
||||
Count()
|
||||
|
||||
cfg := t.Config.Data()
|
||||
data = append(data, dto.TenantProfile{
|
||||
ID: t.ID,
|
||||
@@ -50,8 +60,8 @@ func (s *tenant) List(ctx context.Context, filter *dto.TenantListFilter) (*reque
|
||||
Avatar: cfg.Avatar,
|
||||
Bio: cfg.Bio,
|
||||
Stats: dto.Stats{
|
||||
Followers: int(followers),
|
||||
Contents: int(contents),
|
||||
Followers: int(followerMap[t.ID]),
|
||||
Contents: int(contentMap[t.ID]),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -159,29 +169,56 @@ func (s *tenant) ListFollowed(ctx context.Context, userID int64) ([]dto.TenantPr
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
if len(list) == 0 {
|
||||
return []dto.TenantProfile{}, nil
|
||||
}
|
||||
|
||||
tenantIDs := make([]int64, 0, len(list))
|
||||
tenantIDSet := make(map[int64]struct{}, len(list))
|
||||
for _, tu := range list {
|
||||
if _, ok := tenantIDSet[tu.TenantID]; ok {
|
||||
continue
|
||||
}
|
||||
tenantIDs = append(tenantIDs, tu.TenantID)
|
||||
tenantIDSet[tu.TenantID] = struct{}{}
|
||||
}
|
||||
|
||||
tblTenant, qTenant := models.TenantQuery.QueryContext(ctx)
|
||||
tenants, err := qTenant.Where(tblTenant.ID.In(tenantIDs...)).Find()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
tenantMap := make(map[int64]*models.Tenant, len(tenants))
|
||||
for _, t := range tenants {
|
||||
tenantMap[t.ID] = t
|
||||
}
|
||||
|
||||
// 批量统计关注数与内容数,避免逐条 Count。
|
||||
followerMap, err := s.countFollowers(ctx, tenantIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contentMap, err := s.countPublishedContents(ctx, tenantIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var data []dto.TenantProfile
|
||||
for _, tu := range list {
|
||||
// Fetch Tenant
|
||||
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tu.TenantID)).First()
|
||||
if err != nil {
|
||||
t, ok := tenantMap[tu.TenantID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Stats
|
||||
followers, _ := models.TenantUserQuery.WithContext(ctx).
|
||||
Where(models.TenantUserQuery.TenantID.Eq(tu.TenantID)).
|
||||
Count()
|
||||
contents, _ := models.ContentQuery.WithContext(ctx).
|
||||
Where(models.ContentQuery.TenantID.Eq(tu.TenantID), models.ContentQuery.Status.Eq(consts.ContentStatusPublished)).
|
||||
Count()
|
||||
|
||||
cfg := t.Config.Data()
|
||||
data = append(data, dto.TenantProfile{
|
||||
ID: t.ID,
|
||||
Name: t.Name,
|
||||
Avatar: "",
|
||||
Avatar: cfg.Avatar,
|
||||
Stats: dto.Stats{
|
||||
Followers: int(followers),
|
||||
Contents: int(contents),
|
||||
Followers: int(followerMap[tu.TenantID]),
|
||||
Contents: int(contentMap[tu.TenantID]),
|
||||
},
|
||||
IsFollowing: true,
|
||||
})
|
||||
@@ -202,3 +239,52 @@ func (s *tenant) GetModelByID(ctx context.Context, id int64) (*models.Tenant, er
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
type tenantStatRow struct {
|
||||
TenantID int64 `gorm:"column:tenant_id"`
|
||||
Total int64 `gorm:"column:total"`
|
||||
}
|
||||
|
||||
func (s *tenant) countFollowers(ctx context.Context, tenantIDs []int64) (map[int64]int64, error) {
|
||||
if len(tenantIDs) == 0 {
|
||||
return map[int64]int64{}, nil
|
||||
}
|
||||
|
||||
tbl, q := models.TenantUserQuery.QueryContext(ctx)
|
||||
var rows []tenantStatRow
|
||||
err := q.Where(tbl.TenantID.In(tenantIDs...)).
|
||||
Select(tbl.TenantID, tbl.ALL.Count().As("total")).
|
||||
Group(tbl.TenantID).
|
||||
Scan(&rows)
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
result := make(map[int64]int64, len(rows))
|
||||
for _, row := range rows {
|
||||
result[row.TenantID] = row.Total
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *tenant) countPublishedContents(ctx context.Context, tenantIDs []int64) (map[int64]int64, error) {
|
||||
if len(tenantIDs) == 0 {
|
||||
return map[int64]int64{}, nil
|
||||
}
|
||||
|
||||
tbl, q := models.ContentQuery.QueryContext(ctx)
|
||||
var rows []tenantStatRow
|
||||
err := q.Where(tbl.TenantID.In(tenantIDs...), tbl.Status.Eq(consts.ContentStatusPublished)).
|
||||
Select(tbl.TenantID, tbl.ALL.Count().As("total")).
|
||||
Group(tbl.TenantID).
|
||||
Scan(&rows)
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
result := make(map[int64]int64, len(rows))
|
||||
for _, row := range rows {
|
||||
result[row.TenantID] = row.Total
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user