瀏覽代碼

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.
MHSanaei 22 小時之前
父節點
當前提交
97588dd0b9
共有 1 個文件被更改,包括 18 次插入7 次删除
  1. 18 7
      internal/web/service/inbound_disable.go

+ 18 - 7
internal/web/service/inbound_disable.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"slices"
 	"strings"
 	"time"
 
@@ -162,13 +163,23 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error)
 		}
 	}
 
-	result := tx.Model(xray.ClientTraffic{}).
-		Where(cond+" AND enable = ?", now, true).
-		Update("enable", false)
-	err = result.Error
-	count := result.RowsAffected
-	if err != nil {
-		return needRestart, count, err
+	// Flip the rows already collected above by primary key instead of
+	// re-evaluating the depleted predicate, which was a second full scan of
+	// client_traffics on every poll. Sorted ids keep the lock order stable.
+	ids := make([]int, 0, len(depletedRows))
+	for i := range depletedRows {
+		ids = append(ids, depletedRows[i].Id)
+	}
+	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 {