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
+37 -8
View File
@@ -83,8 +83,17 @@ func (s *InboundService) enrichClientStats(db *gorm.DB, inbounds []*model.Inboun
emails = append(emails, e)
}
var extra []xray.ClientTraffic
if err := db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&extra).Error; err != nil {
logger.Warning("enrichClientStats:", err)
var loadErr error
for _, batch := range chunkStrings(emails, sqlInChunk) {
var page []xray.ClientTraffic
if err := db.Model(xray.ClientTraffic{}).Where("email IN ?", batch).Find(&page).Error; err != nil {
loadErr = err
break
}
extra = append(extra, page...)
}
if loadErr != nil {
logger.Warning("enrichClientStats:", loadErr)
} else {
byEmail := make(map[string]xray.ClientTraffic, len(extra))
for _, st := range extra {
@@ -3048,16 +3057,33 @@ func (s *InboundService) GetInboundsTrafficSummary() ([]InboundTrafficSummary, e
}
func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.ClientTraffic, err error) {
// Prefer retrieving along with client to reflect actual enabled state from inbound settings
t, client, err := s.GetClientByEmail(email)
db := database.GetDB()
var traffics []*xray.ClientTraffic
if err := db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error; err != nil {
logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err)
return nil, err
}
if len(traffics) == 0 {
return nil, nil
}
t := traffics[0]
if rec, rErr := s.clientService.GetRecordByEmail(db, email); rErr == nil && rec != nil {
c := rec.ToClient()
t.UUID = c.ID
t.SubId = c.SubID
return t, nil
}
t2, client, err := s.GetClientByEmail(email)
if err != nil {
logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err)
return nil, err
}
if t != nil && client != nil {
t.UUID = client.ID
t.SubId = client.SubID
return t, nil
if t2 != nil && client != nil {
t2.UUID = client.ID
t2.SubId = client.SubID
return t2, nil
}
return nil, nil
}
@@ -3386,6 +3412,9 @@ func (s *InboundService) MigrateDB() {
}
func (s *InboundService) GetOnlineClients() []string {
if p == nil {
return []string{}
}
return p.GetOnlineClients()
}