mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 15:17:14 +00:00
perf(clients): batch the client record lookup in bulk operations
BulkResetTraffic resolved every address with its own GetRecordByEmail call, one SELECT per email, purely to find the disabled clients it has to re-enable. Resetting 30 clients issued 30 queries before the batched transaction even started, while BulkAdjust, BulkDelete and BulkSetEnable next to it already loaded their rows with a single chunked IN query. Those three carried a verbatim copy each of both the trim/dedupe loop and the chunked record load, so the reuse is the fix: trimmedUniqueEmails now delegates to the existing uniqueNonEmptyStrings, and clientRecordsByEmail holds the one chunked lookup all four call sites share. A DB failure during the lookup now aborts the reset instead of being swallowed per email; a missing row is still skipped, as before. The new test drives BulkResetTraffic with 3 and with 30 emails and fails unless both issue the same number of SELECTs against clients.
This commit is contained in:
@@ -344,36 +344,16 @@ func (s *ClientService) BulkAdjust(inboundSvc *InboundService, emails []string,
|
||||
|
||||
addExpiryMs := int64(addDays) * 24 * 60 * 60 * 1000
|
||||
|
||||
seen := map[string]struct{}{}
|
||||
cleanEmails := make([]string, 0, len(emails))
|
||||
for _, e := range emails {
|
||||
e = strings.TrimSpace(e)
|
||||
if e == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[e]; ok {
|
||||
continue
|
||||
}
|
||||
seen[e] = struct{}{}
|
||||
cleanEmails = append(cleanEmails, e)
|
||||
}
|
||||
cleanEmails := trimmedUniqueEmails(emails)
|
||||
if len(cleanEmails) == 0 {
|
||||
return result, false, nil
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
|
||||
var records []model.ClientRecord
|
||||
for _, batch := range chunkStrings(cleanEmails, sqlInChunk) {
|
||||
var rows []model.ClientRecord
|
||||
if err := db.Where("email IN ?", batch).Find(&rows).Error; err != nil {
|
||||
return result, false, err
|
||||
}
|
||||
records = append(records, rows...)
|
||||
}
|
||||
recordsByEmail := make(map[string]*model.ClientRecord, len(records))
|
||||
for i := range records {
|
||||
recordsByEmail[records[i].Email] = &records[i]
|
||||
recordsByEmail, err := clientRecordsByEmail(db, cleanEmails)
|
||||
if err != nil {
|
||||
return result, false, err
|
||||
}
|
||||
|
||||
skippedReasons := map[string]string{}
|
||||
@@ -894,38 +874,22 @@ type BulkDeleteReport struct {
|
||||
func (s *ClientService) BulkDelete(inboundSvc *InboundService, emails []string, keepTraffic bool) (BulkDeleteResult, bool, error) {
|
||||
result := BulkDeleteResult{}
|
||||
|
||||
seen := map[string]struct{}{}
|
||||
cleanEmails := make([]string, 0, len(emails))
|
||||
for _, e := range emails {
|
||||
e = strings.TrimSpace(e)
|
||||
if e == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[e]; ok {
|
||||
continue
|
||||
}
|
||||
seen[e] = struct{}{}
|
||||
cleanEmails = append(cleanEmails, e)
|
||||
}
|
||||
cleanEmails := trimmedUniqueEmails(emails)
|
||||
if len(cleanEmails) == 0 {
|
||||
return result, false, nil
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
|
||||
var records []model.ClientRecord
|
||||
for _, batch := range chunkStrings(cleanEmails, sqlInChunk) {
|
||||
var rows []model.ClientRecord
|
||||
if err := db.Where("email IN ?", batch).Find(&rows).Error; err != nil {
|
||||
return result, false, err
|
||||
}
|
||||
records = append(records, rows...)
|
||||
recordsByEmail, err := clientRecordsByEmail(db, cleanEmails)
|
||||
if err != nil {
|
||||
return result, false, err
|
||||
}
|
||||
recordsByEmail := make(map[string]*model.ClientRecord, len(records))
|
||||
tombstoneEmails := make([]string, 0, len(records))
|
||||
for i := range records {
|
||||
recordsByEmail[records[i].Email] = &records[i]
|
||||
tombstoneEmails = append(tombstoneEmails, records[i].Email)
|
||||
tombstoneEmails := make([]string, 0, len(recordsByEmail))
|
||||
for _, email := range cleanEmails {
|
||||
if recordsByEmail[email] != nil {
|
||||
tombstoneEmails = append(tombstoneEmails, email)
|
||||
}
|
||||
}
|
||||
tombstoneClientEmails(tombstoneEmails)
|
||||
|
||||
@@ -1579,36 +1543,16 @@ type BulkSetEnableReport struct {
|
||||
func (s *ClientService) BulkSetEnable(inboundSvc *InboundService, emails []string, enable bool) (BulkSetEnableResult, bool, error) {
|
||||
result := BulkSetEnableResult{}
|
||||
|
||||
seen := map[string]struct{}{}
|
||||
cleanEmails := make([]string, 0, len(emails))
|
||||
for _, e := range emails {
|
||||
e = strings.TrimSpace(e)
|
||||
if e == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[e]; ok {
|
||||
continue
|
||||
}
|
||||
seen[e] = struct{}{}
|
||||
cleanEmails = append(cleanEmails, e)
|
||||
}
|
||||
cleanEmails := trimmedUniqueEmails(emails)
|
||||
if len(cleanEmails) == 0 {
|
||||
return result, false, nil
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
|
||||
var records []model.ClientRecord
|
||||
for _, batch := range chunkStrings(cleanEmails, sqlInChunk) {
|
||||
var rows []model.ClientRecord
|
||||
if err := db.Where("email IN ?", batch).Find(&rows).Error; err != nil {
|
||||
return result, false, err
|
||||
}
|
||||
records = append(records, rows...)
|
||||
}
|
||||
recordsByEmail := make(map[string]*model.ClientRecord, len(records))
|
||||
for i := range records {
|
||||
recordsByEmail[records[i].Email] = &records[i]
|
||||
recordsByEmail, err := clientRecordsByEmail(db, cleanEmails)
|
||||
if err != nil {
|
||||
return result, false, err
|
||||
}
|
||||
|
||||
skippedReasons := map[string]string{}
|
||||
|
||||
Reference in New Issue
Block a user