mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
perf(inbounds): batch client identities for stats enrichment (#6929)
backfillClientStats loaded every column of every client once per inbound, although enrichment only reads UUID, email and subscription ID. Each inbound list read (panel list, the master's node traffic poll, Xray config builds, bots) repeated that wide join per inbound. Identities now come from one narrow clients/client_inbounds projection per 400 inbounds, honouring the supplied transaction; stats output, sibling-traffic append order and the #6436 clients-table source of truth are unchanged. Closes #6926
This commit is contained in:
@@ -24,48 +24,47 @@ type CopyClientsResult struct {
|
||||
Errors []string `json:"errors"`
|
||||
}
|
||||
|
||||
// enrichClientStats resolves each inbound's clients from the clients table
|
||||
// once, fills in the UUID/SubId fields on the preloaded ClientStats, and tops
|
||||
// up rows owned by a sibling inbound (shared-email mode — the row is keyed on
|
||||
// email so it only preloads on its owning inbound). Reading identity from the
|
||||
// clients table keeps /inbounds/list in sync with the running Xray config
|
||||
// when the embedded settings JSON is stale (#6436).
|
||||
// enrichClientStats refreshes identities from clients while preserving sibling-owned traffic.
|
||||
// The clients table, not the embedded settings JSON, matches the running Xray config (#6436).
|
||||
func (s *InboundService) enrichClientStats(db *gorm.DB, inbounds []*model.Inbound) {
|
||||
if len(inbounds) == 0 {
|
||||
return
|
||||
}
|
||||
clientsByInbound := s.backfillClientStats(db, inbounds)
|
||||
for i, inbound := range inbounds {
|
||||
clients := clientsByInbound[i]
|
||||
for _, inbound := range inbounds {
|
||||
clients := clientsByInbound[inbound.Id]
|
||||
if len(clients) == 0 || len(inbound.ClientStats) == 0 {
|
||||
continue
|
||||
}
|
||||
cMap := make(map[string]model.Client, len(clients))
|
||||
cMap := make(map[string]clientStatsIdentity, len(clients))
|
||||
for _, c := range clients {
|
||||
cMap[strings.ToLower(c.Email)] = c
|
||||
}
|
||||
for j := range inbound.ClientStats {
|
||||
email := strings.ToLower(inbound.ClientStats[j].Email)
|
||||
if c, ok := cMap[email]; ok {
|
||||
inbound.ClientStats[j].UUID = c.ID
|
||||
inbound.ClientStats[j].UUID = c.UUID
|
||||
inbound.ClientStats[j].SubId = c.SubID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// backfillClientStats tops up each inbound's preloaded ClientStats with rows
|
||||
// owned by a sibling inbound: client_traffics is keyed on email, so a client
|
||||
// attached to several inbounds has one row that only preloads on the inbound
|
||||
// it was created on. Returns the clients-table clients per inbound for reuse
|
||||
// (not the embedded settings JSON, which can lag the live UUID — #6436).
|
||||
func (s *InboundService) backfillClientStats(db *gorm.DB, inbounds []*model.Inbound) [][]model.Client {
|
||||
clientsByInbound := make([][]model.Client, len(inbounds))
|
||||
// backfillClientStats loads identities once, then appends missing sibling-owned traffic.
|
||||
func (s *InboundService) backfillClientStats(db *gorm.DB, inbounds []*model.Inbound) map[int][]clientStatsIdentity {
|
||||
ids := make([]int, 0, len(inbounds))
|
||||
for _, inbound := range inbounds {
|
||||
ids = append(ids, inbound.Id)
|
||||
}
|
||||
clientsByInbound, err := s.clientService.listStatsIdentitiesForInbounds(db, ids)
|
||||
if err != nil {
|
||||
logger.Warning("backfillClientStats:", err)
|
||||
return nil
|
||||
}
|
||||
seenByInbound := make([]map[string]struct{}, len(inbounds))
|
||||
missing := make(map[string]struct{})
|
||||
for i, inbound := range inbounds {
|
||||
clients, _ := s.clientService.ListForInbound(db, inbound.Id)
|
||||
clientsByInbound[i] = clients
|
||||
clients := clientsByInbound[inbound.Id]
|
||||
seen := make(map[string]struct{}, len(inbound.ClientStats))
|
||||
for _, st := range inbound.ClientStats {
|
||||
if st.Email != "" {
|
||||
@@ -105,7 +104,7 @@ func (s *InboundService) backfillClientStats(db *gorm.DB, inbounds []*model.Inbo
|
||||
byEmail[strings.ToLower(st.Email)] = st
|
||||
}
|
||||
for i, inbound := range inbounds {
|
||||
for _, c := range clientsByInbound[i] {
|
||||
for _, c := range clientsByInbound[inbound.Id] {
|
||||
if c.Email == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user