mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-26 09:52:14 +03:00
perf(clients): take one email snapshot per client fan-out, not one per inbound (#6091)
Create and Attach called the exported AddInboundClient once per target inbound, and that wrapper passes a nil email→subId map, so every iteration re-ran getAllEmailSubIDs -- a JSON_EACH expansion over the settings blob of every inbound in the panel. Adding one client to 24 inbounds on a panel with ~300 users meant 24 full expansions of ~7k rows to answer the same question. Hoist the snapshot above the loop and call the unexported addInboundClient with it, exactly as BulkAttach (client_bulk.go:63) and BulkCreate (client_bulk.go:1151) already do. The snapshot goes stale from the second inbound onward, but the identity being added is the same on every iteration, so its own entry can only ever match itself -- checkEmailsExistForClients accepts an email whose stored subId equals the incoming one, and an absent entry is accepted too. This is the database half of #6091. The dominant cost there is the other half -- one synchronous 10s-capped node round-trip per remote inbound, which multiplies again on chained nodes -- and that needs the push batched per node rather than per inbound; left for a separate change.
This commit is contained in:
84
internal/web/service/client_create_fanout_test.go
Normal file
84
internal/web/service/client_create_fanout_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
)
|
||||
|
||||
func TestCreateAcrossManyInboundsUsesOneEmailSnapshot(t *testing.T) {
|
||||
setupBulkDB(t)
|
||||
svc := &ClientService{}
|
||||
inboundSvc := &InboundService{}
|
||||
|
||||
const uuid = "bbbbbbbb-1111-2222-3333-555555555555"
|
||||
ids := make([]int, 0, 6)
|
||||
for i := range 6 {
|
||||
ib := mkInbound(t, 23001+i, model.VLESS, `{"clients":[]}`)
|
||||
ids = append(ids, ib.Id)
|
||||
}
|
||||
|
||||
if _, err := svc.Create(inboundSvc, &ClientCreatePayload{
|
||||
Client: model.Client{Email: "fan@x", ID: uuid, SubID: "sub-fan", Enable: true},
|
||||
InboundIds: ids,
|
||||
}); err != nil {
|
||||
t.Fatalf("Create across %d inbounds: %v", len(ids), err)
|
||||
}
|
||||
|
||||
if n := countClientRecords(t); n != 1 {
|
||||
t.Fatalf("client records = %d, want 1", n)
|
||||
}
|
||||
rec := lookupClientRecord(t, "fan@x")
|
||||
if rec.UUID != uuid || rec.SubID != "sub-fan" {
|
||||
t.Fatalf("record = {uuid:%q sub:%q}, want {%q sub-fan}", rec.UUID, rec.SubID, uuid)
|
||||
}
|
||||
for _, id := range ids {
|
||||
if !settingsHoldUUID(t, inboundSvc, id, uuid) {
|
||||
t.Fatalf("inbound %d settings missing the client", id)
|
||||
}
|
||||
}
|
||||
|
||||
linked, err := svc.GetInboundIdsForRecord(rec.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("GetInboundIdsForRecord: %v", err)
|
||||
}
|
||||
if len(linked) != len(ids) {
|
||||
t.Fatalf("linked inbounds = %d, want %d", len(linked), len(ids))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachAcrossManyInboundsUsesOneEmailSnapshot(t *testing.T) {
|
||||
setupBulkDB(t)
|
||||
svc := &ClientService{}
|
||||
inboundSvc := &InboundService{}
|
||||
|
||||
first := mkInbound(t, 23101, model.VLESS, `{"clients":[]}`)
|
||||
if _, err := svc.Create(inboundSvc, &ClientCreatePayload{
|
||||
Client: model.Client{Email: "att@x", ID: "cccccccc-1111-2222-3333-666666666666", SubID: "sub-att", Enable: true},
|
||||
InboundIds: []int{first.Id},
|
||||
}); err != nil {
|
||||
t.Fatalf("seed Create: %v", err)
|
||||
}
|
||||
rec := lookupClientRecord(t, "att@x")
|
||||
|
||||
ids := []int{first.Id}
|
||||
for i := range 4 {
|
||||
ib := mkInbound(t, 23102+i, model.VLESS, `{"clients":[]}`)
|
||||
ids = append(ids, ib.Id)
|
||||
}
|
||||
|
||||
if _, err := svc.Attach(inboundSvc, rec.Id, ids); err != nil {
|
||||
t.Fatalf("Attach across %d inbounds: %v", len(ids), err)
|
||||
}
|
||||
|
||||
if n := countClientRecords(t); n != 1 {
|
||||
t.Fatalf("client records after attach = %d, want 1", n)
|
||||
}
|
||||
linked, err := svc.GetInboundIdsForRecord(rec.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("GetInboundIdsForRecord: %v", err)
|
||||
}
|
||||
if len(linked) != len(ids) {
|
||||
t.Fatalf("linked inbounds = %d, want %d", len(linked), len(ids))
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,11 @@ func (s *ClientService) Create(inboundSvc *InboundService, payload *ClientCreate
|
||||
}
|
||||
}
|
||||
|
||||
emailSubIDs, sidErr := inboundSvc.getAllEmailSubIDs()
|
||||
if sidErr != nil {
|
||||
return false, sidErr
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
for _, ibId := range payload.InboundIds {
|
||||
inbound, getErr := inboundSvc.GetInbound(ibId)
|
||||
@@ -123,10 +128,10 @@ func (s *ClientService) Create(inboundSvc *InboundService, payload *ClientCreate
|
||||
if mErr != nil {
|
||||
return needRestart, mErr
|
||||
}
|
||||
nr, addErr := s.AddInboundClient(inboundSvc, &model.Inbound{
|
||||
nr, addErr := s.addInboundClient(inboundSvc, &model.Inbound{
|
||||
Id: ibId,
|
||||
Settings: string(settingsPayload),
|
||||
})
|
||||
}, emailSubIDs)
|
||||
if addErr != nil {
|
||||
return needRestart, addErr
|
||||
}
|
||||
@@ -615,6 +620,11 @@ func (s *ClientService) Attach(inboundSvc *InboundService, id int, inboundIds []
|
||||
clientWire.Flow = flow
|
||||
clientWire.UpdatedAt = time.Now().UnixMilli()
|
||||
|
||||
emailSubIDs, sidErr := inboundSvc.getAllEmailSubIDs()
|
||||
if sidErr != nil {
|
||||
return false, sidErr
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
for _, ibId := range inboundIds {
|
||||
if _, attached := have[ibId]; attached {
|
||||
@@ -632,10 +642,10 @@ func (s *ClientService) Attach(inboundSvc *InboundService, id int, inboundIds []
|
||||
if mErr != nil {
|
||||
return needRestart, mErr
|
||||
}
|
||||
nr, addErr := s.AddInboundClient(inboundSvc, &model.Inbound{
|
||||
nr, addErr := s.addInboundClient(inboundSvc, &model.Inbound{
|
||||
Id: ibId,
|
||||
Settings: string(settingsPayload),
|
||||
})
|
||||
}, emailSubIDs)
|
||||
if addErr != nil {
|
||||
return needRestart, addErr
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user