perf(clients): scale-audit remaining client/inbound endpoints to 200k

Drive every client/inbound/group endpoint at 100k-200k clients on PostgreSQL and fix the latent issues found in previously-unbenchmarked paths:

- enrichClientStats: chunk the email IN lookup (was an unchunked bind that crashed past 65535 clients without traffic rows, taking down GetInbounds/GetInboundDetail/GetAllInbounds)

- GetOnlineClients: add the missing nil-process guard its siblings already have, so ListPaged no longer panics before xray starts

- GetClientTrafficByEmail: read UUID/subId from the indexed clients table instead of parsing the inbound's full settings JSON (439ms to ~1.5ms, flat in N)

- BulkResetTraffic: replace the per-email serialized loop with one chunked bulk UPDATE in a single transaction

- DelDepleted: delegate to the already-batched BulkDelete instead of deleting each depleted client one by one

Adds a postgres-gated full endpoint sweep plus an A/B benchmark, and SQLite correctness tests for the changed methods.
This commit is contained in:
MHSanaei
2026-06-04 21:32:15 +02:00
parent d1e733b9e9
commit d3db828b46
5 changed files with 483 additions and 36 deletions
+51 -28
View File
@@ -1747,14 +1747,43 @@ func (s *ClientService) BulkResetTraffic(inboundSvc *InboundService, emails []st
if len(emails) == 0 {
return 0, nil
}
count := 0
for _, email := range emails {
if _, err := s.ResetTrafficByEmail(inboundSvc, email); err != nil {
return count, err
seen := map[string]struct{}{}
cleanEmails := make([]string, 0, len(emails))
for _, e := range emails {
e = strings.TrimSpace(e)
if e == "" {
continue
}
count++
if _, ok := seen[e]; ok {
continue
}
seen[e] = struct{}{}
cleanEmails = append(cleanEmails, e)
}
return count, nil
if len(cleanEmails) == 0 {
return 0, nil
}
affected := 0
err := submitTrafficWrite(func() error {
db := database.GetDB()
return db.Transaction(func(tx *gorm.DB) error {
for _, batch := range chunkStrings(cleanEmails, sqlInChunk) {
res := tx.Model(xray.ClientTraffic{}).
Where("email IN ?", batch).
Updates(map[string]any{"enable": true, "up": 0, "down": 0})
if res.Error != nil {
return res.Error
}
affected += int(res.RowsAffected)
}
return nil
})
})
if err != nil {
return 0, err
}
return affected, nil
}
func (s *ClientService) CreateGroup(name string) error {
@@ -3334,33 +3363,27 @@ func (s *ClientService) DelDepleted(inboundSvc *InboundService) (int, bool, erro
return 0, false, nil
}
emails := make(map[string]struct{}, len(rows))
seen := make(map[string]struct{}, len(rows))
emails := make([]string, 0, len(rows))
for _, r := range rows {
if r.Email != "" {
emails[r.Email] = struct{}{}
if r.Email == "" {
continue
}
if _, ok := seen[r.Email]; ok {
continue
}
seen[r.Email] = struct{}{}
emails = append(emails, r.Email)
}
if len(emails) == 0 {
return 0, false, nil
}
needRestart := false
deleted := 0
for email := range emails {
var rec model.ClientRecord
if err := db.Where("email = ?", email).First(&rec).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
continue
}
return deleted, needRestart, err
}
nr, err := s.Delete(inboundSvc, rec.Id, false)
if err != nil {
return deleted, needRestart, err
}
if nr {
needRestart = true
}
deleted++
res, needRestart, err := s.BulkDelete(inboundSvc, emails, false)
if err != nil {
return res.Deleted, needRestart, err
}
return deleted, needRestart, nil
return res.Deleted, needRestart, nil
}
func (s *ClientService) ResetAllClientTraffics(inboundSvc *InboundService, id int) error {