perf: exclude inactive catalog rows from report summary lookups
This commit is contained in:
@@ -228,6 +228,13 @@ func (r *analyticsRepo) Aggregate(ctx context.Context, kind business.AnalyticsKi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if metricFilter {
|
if metricFilter {
|
||||||
|
// Only channels present in the report can contribute to its summary.
|
||||||
|
// Keep zero-activity catalog rows out of the aggregate IN list; large
|
||||||
|
// catalogs would otherwise exhaust MySQL's prepared-statement parameters.
|
||||||
|
factKeys := make([]business.AnalyticsKey, 0, len(rowMap))
|
||||||
|
for key := range rowMap {
|
||||||
|
factKeys = append(factKeys, key)
|
||||||
|
}
|
||||||
if f.IncludesInactiveRows() {
|
if f.IncludesInactiveRows() {
|
||||||
if err := includeInactiveAnalyticsRows(db, scope, f, rowMap); err != nil {
|
if err := includeInactiveAnalyticsRows(db, scope, f, rowMap); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -243,9 +250,11 @@ func (r *analyticsRepo) Aggregate(ctx context.Context, kind business.AnalyticsKi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if withGlobal {
|
if withGlobal {
|
||||||
keys := make([]business.AnalyticsKey, 0, len(rowMap))
|
keys := make([]business.AnalyticsKey, 0, len(factKeys))
|
||||||
for key := range rowMap {
|
for _, key := range factKeys {
|
||||||
keys = append(keys, key)
|
if rowMap[key] != nil {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Deduplicate identities across all matched channels, before pagination.
|
// Deduplicate identities across all matched channels, before pagination.
|
||||||
// Summing per-channel counts would double-count shared players.
|
// Summing per-channel counts would double-count shared players.
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package business
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -130,6 +132,32 @@ func TestAnalyticsNegativeFiltersIncludeInactiveAuthorizedChannels(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAnalyticsInactiveCatalogDoesNotExpandSummaryParameters(t *testing.T) {
|
||||||
|
db, repo, scope, filter := prepareAnalyticsData(t)
|
||||||
|
rows := make([]map[string]any, 2000)
|
||||||
|
for i := range rows {
|
||||||
|
rows[i] = map[string]any{"id": 1000 + i, "source_db": "lx", "account": "inactive", "promote_level": 1, "parent_id": 0, "top_promote_id": 0, "busier_id": 14}
|
||||||
|
}
|
||||||
|
if err := db.Table("tab_promote").CreateInBatches(rows, 200).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var excessive atomic.Bool
|
||||||
|
if err := db.Callback().Row().After("gorm:row").Register("check_summary_parameters", func(tx *gorm.DB) {
|
||||||
|
if strings.Contains(tx.Statement.SQL.String(), "COUNT(DISTINCT") && len(tx.Statement.Vars) > 100 {
|
||||||
|
excessive.Store(true)
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
scope.Viewer, scope.IncludeSub = business.UserScope{All: true}, false
|
||||||
|
no := false
|
||||||
|
filter.HasRegister, filter.HasPay = &no, &no
|
||||||
|
data, err := repo.Aggregate(context.Background(), business.ChannelAnalytics, scope, filter)
|
||||||
|
if err != nil || len(data.Rows) < len(rows) || excessive.Load() || data.GlobalCounts.Register != 0 || portraitTestMoney(data.GlobalPay) != "0.00" {
|
||||||
|
t.Fatalf("inactive rows bloated the summary query or changed totals: rows=%d excessive=%v err=%v", len(data.Rows), excessive.Load(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAnalyticsRowsOnlyComeFromSelectedPeriod(t *testing.T) {
|
func TestAnalyticsRowsOnlyComeFromSelectedPeriod(t *testing.T) {
|
||||||
db, repo, scope, filter := prepareAnalyticsData(t)
|
db, repo, scope, filter := prepareAnalyticsData(t)
|
||||||
for _, sql := range []string{
|
for _, sql := range []string{
|
||||||
|
|||||||
Reference in New Issue
Block a user