fix(traffic): disable depleted clients by id instead of a second full scan

disableInvalidClients evaluated the depleted predicate twice per poll:
once to SELECT the rows (for xray removal and settings sync) and again in
the UPDATE that flips enable off — each a full client_traffics scan, the
second also re-running the cross-panel EXISTS subquery when global rows
exist.

The UPDATE now flips the already-collected rows by primary key in
sqlInChunk batches, sorted for stable lock order. Same rows, same
RowsAffected, half the scan cost; id-based matching also stays correct
for rows with empty emails.
This commit is contained in:
MHSanaei
2026-07-02 16:24:18 +02:00
parent fb1d055b06
commit 97588dd0b9
+18 -7
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"slices"
"strings" "strings"
"time" "time"
@@ -162,13 +163,23 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error)
} }
} }
result := tx.Model(xray.ClientTraffic{}). // Flip the rows already collected above by primary key instead of
Where(cond+" AND enable = ?", now, true). // re-evaluating the depleted predicate, which was a second full scan of
Update("enable", false) // client_traffics on every poll. Sorted ids keep the lock order stable.
err = result.Error ids := make([]int, 0, len(depletedRows))
count := result.RowsAffected for i := range depletedRows {
if err != nil { ids = append(ids, depletedRows[i].Id)
return needRestart, count, err }
slices.Sort(ids)
var count int64
for _, batch := range chunkInts(ids, sqlInChunk) {
result := tx.Model(xray.ClientTraffic{}).
Where("id IN ? AND enable = ?", batch, true).
Update("enable", false)
if result.Error != nil {
return needRestart, count, result.Error
}
count += result.RowsAffected
} }
if len(depletedEmails) > 0 { if len(depletedEmails) > 0 {