Merge pull request #754 from xvhuan/perf/admin-core-large-dataset

perf(admin): 优化后台大数据场景加载性能(仪表盘/用户/账号/Ops)
This commit is contained in:
Wesley Liddick
2026-03-04 15:15:13 +08:00
committed by GitHub
27 changed files with 1110 additions and 175 deletions

View File

@@ -31,6 +31,10 @@ func (s *OpsService) GetDashboardOverview(ctx context.Context, filter *OpsDashbo
filter.QueryMode = s.resolveOpsQueryMode(ctx, filter.QueryMode)
overview, err := s.opsRepo.GetDashboardOverview(ctx, filter)
if err != nil && shouldFallbackOpsPreagg(filter, err) {
rawFilter := cloneOpsFilterWithMode(filter, OpsQueryModeRaw)
overview, err = s.opsRepo.GetDashboardOverview(ctx, rawFilter)
}
if err != nil {
if errors.Is(err, ErrOpsPreaggregatedNotPopulated) {
return nil, infraerrors.Conflict("OPS_PREAGG_NOT_READY", "Pre-aggregated ops metrics are not populated yet")

View File

@@ -22,7 +22,14 @@ func (s *OpsService) GetErrorTrend(ctx context.Context, filter *OpsDashboardFilt
if filter.StartTime.After(filter.EndTime) {
return nil, infraerrors.BadRequest("OPS_TIME_RANGE_INVALID", "start_time must be <= end_time")
}
return s.opsRepo.GetErrorTrend(ctx, filter, bucketSeconds)
filter.QueryMode = s.resolveOpsQueryMode(ctx, filter.QueryMode)
result, err := s.opsRepo.GetErrorTrend(ctx, filter, bucketSeconds)
if err != nil && shouldFallbackOpsPreagg(filter, err) {
rawFilter := cloneOpsFilterWithMode(filter, OpsQueryModeRaw)
return s.opsRepo.GetErrorTrend(ctx, rawFilter, bucketSeconds)
}
return result, err
}
func (s *OpsService) GetErrorDistribution(ctx context.Context, filter *OpsDashboardFilter) (*OpsErrorDistributionResponse, error) {
@@ -41,5 +48,12 @@ func (s *OpsService) GetErrorDistribution(ctx context.Context, filter *OpsDashbo
if filter.StartTime.After(filter.EndTime) {
return nil, infraerrors.BadRequest("OPS_TIME_RANGE_INVALID", "start_time must be <= end_time")
}
return s.opsRepo.GetErrorDistribution(ctx, filter)
filter.QueryMode = s.resolveOpsQueryMode(ctx, filter.QueryMode)
result, err := s.opsRepo.GetErrorDistribution(ctx, filter)
if err != nil && shouldFallbackOpsPreagg(filter, err) {
rawFilter := cloneOpsFilterWithMode(filter, OpsQueryModeRaw)
return s.opsRepo.GetErrorDistribution(ctx, rawFilter)
}
return result, err
}

View File

@@ -22,5 +22,12 @@ func (s *OpsService) GetLatencyHistogram(ctx context.Context, filter *OpsDashboa
if filter.StartTime.After(filter.EndTime) {
return nil, infraerrors.BadRequest("OPS_TIME_RANGE_INVALID", "start_time must be <= end_time")
}
return s.opsRepo.GetLatencyHistogram(ctx, filter)
filter.QueryMode = s.resolveOpsQueryMode(ctx, filter.QueryMode)
result, err := s.opsRepo.GetLatencyHistogram(ctx, filter)
if err != nil && shouldFallbackOpsPreagg(filter, err) {
rawFilter := cloneOpsFilterWithMode(filter, OpsQueryModeRaw)
return s.opsRepo.GetLatencyHistogram(ctx, rawFilter)
}
return result, err
}

View File

@@ -38,3 +38,18 @@ func (m OpsQueryMode) IsValid() bool {
return false
}
}
func shouldFallbackOpsPreagg(filter *OpsDashboardFilter, err error) bool {
return filter != nil &&
filter.QueryMode == OpsQueryModeAuto &&
errors.Is(err, ErrOpsPreaggregatedNotPopulated)
}
func cloneOpsFilterWithMode(filter *OpsDashboardFilter, mode OpsQueryMode) *OpsDashboardFilter {
if filter == nil {
return nil
}
cloned := *filter
cloned.QueryMode = mode
return &cloned
}

View File

@@ -22,5 +22,13 @@ func (s *OpsService) GetThroughputTrend(ctx context.Context, filter *OpsDashboar
if filter.StartTime.After(filter.EndTime) {
return nil, infraerrors.BadRequest("OPS_TIME_RANGE_INVALID", "start_time must be <= end_time")
}
return s.opsRepo.GetThroughputTrend(ctx, filter, bucketSeconds)
filter.QueryMode = s.resolveOpsQueryMode(ctx, filter.QueryMode)
result, err := s.opsRepo.GetThroughputTrend(ctx, filter, bucketSeconds)
if err != nil && shouldFallbackOpsPreagg(filter, err) {
rawFilter := cloneOpsFilterWithMode(filter, OpsQueryModeRaw)
return s.opsRepo.GetThroughputTrend(ctx, rawFilter, bucketSeconds)
}
return result, err
}

View File

@@ -22,6 +22,10 @@ type UserListFilters struct {
Role string // User role filter
Search string // Search in email, username
Attributes map[int64]string // Custom attribute filters: attributeID -> value
// IncludeSubscriptions controls whether ListWithFilters should load active subscriptions.
// For large datasets this can be expensive; admin list pages should enable it on demand.
// nil means not specified (default: load subscriptions for backward compatibility).
IncludeSubscriptions *bool
}
type UserRepository interface {