mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-18 02:26:11 +00:00
perf(scale): speed up traffic, auto-renew, and node bulk ops at 50k-100k clients
Local hot paths: - autoRenewClients: replace the O(clients x expired) inner scan with an email->traffic map lookup (quadratic at scale). - node traffic sync: scope the client_traffics email-membership query to the snapshot's emails instead of plucking the whole table every poll. - add a (expiry_time, reset) index for the per-tick auto-renew filter. - SQLite: add cache_size/mmap_size/temp_store pragmas (env-tunable); keep the single-file DELETE journal and synchronous=FULL defaults. - scale benchmarks now run on SQLite too via XUI_SCALE_TEST=1 (shared setupScaleDB/resetScaleTables helpers), not just Postgres. Node paths: - bulk add/delete/adjust on a node-attached inbound folded one HTTP RPC per client; above nodeBulkPushThreshold (32) mark the node dirty and let one ReconcileNode push converge it instead of O(M) sequential round-trips. Small ops keep the live per-client path. Also hoist nodePushPlan out of the per-email delete loop. - ReconcileNode skips inbounds whose wire payload is unchanged (per-tag fingerprint on Remote), guarded by node-side tag presence so a restarted node is still re-seeded. Tests: auto-renew multi-inbound correctness, node-path dispatch (large ops fold to dirty, small ops push live) via a manager runtime override seam, and reconcile delta-skip.
This commit is contained in:
@@ -3,6 +3,8 @@ package runtime
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -71,6 +73,10 @@ type Remote struct {
|
||||
|
||||
mu sync.RWMutex
|
||||
remoteIDByTag map[string]int
|
||||
// pushedFP holds the fingerprint of the last inbound wire payload successfully
|
||||
// pushed, keyed by panel-side tag, so reconcile can skip re-sending an
|
||||
// unchanged inbound. Guarded by mu; dropped with the Remote on node config change.
|
||||
pushedFP map[string]string
|
||||
// supportsZstd is learned from the node's X-3x-Node-Caps response header; once
|
||||
// seen, config pushes to this node are zstd-compressed. Old nodes never set
|
||||
// it, so they keep receiving plain bodies (mixed-version safe).
|
||||
@@ -96,6 +102,7 @@ func NewRemote(n *model.Node, r NodeEgressResolver) *Remote {
|
||||
return &Remote{
|
||||
node: n,
|
||||
remoteIDByTag: make(map[string]int),
|
||||
pushedFP: make(map[string]string),
|
||||
egressResolver: r,
|
||||
}
|
||||
}
|
||||
@@ -432,6 +439,36 @@ func (r *Remote) UpdateInbound(ctx context.Context, oldIb, newIb *model.Inbound)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReconcileInbound pushes ib only when its wire payload differs from the last
|
||||
// successful push, or when the node no longer reports the tag (existsOnNode
|
||||
// false) — a node that dropped/restarted must still be re-seeded. Returns
|
||||
// whether a push actually happened. This turns a full-fleet reconcile from "send
|
||||
// every inbound's full settings" into "send only what changed".
|
||||
func (r *Remote) ReconcileInbound(ctx context.Context, ib *model.Inbound, existsOnNode bool) (bool, error) {
|
||||
fp := wireFingerprint(wireInbound(ib, r.node.Id))
|
||||
if existsOnNode {
|
||||
r.mu.RLock()
|
||||
prev, ok := r.pushedFP[ib.Tag]
|
||||
r.mu.RUnlock()
|
||||
if ok && prev == fp {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if err := r.UpdateInbound(ctx, ib, ib); err != nil {
|
||||
return false, err
|
||||
}
|
||||
r.mu.Lock()
|
||||
r.pushedFP[ib.Tag] = fp
|
||||
r.mu.Unlock()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// wireFingerprint hashes a wire payload so an unchanged inbound is cheap to detect.
|
||||
func wireFingerprint(v url.Values) string {
|
||||
sum := sha256.Sum256([]byte(v.Encode()))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func (r *Remote) AddUser(ctx context.Context, ib *model.Inbound, _ map[string]any) error {
|
||||
return r.UpdateInbound(ctx, ib, ib)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user