mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-11 15:16:07 +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:
@@ -2,17 +2,12 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
func seedClientTraffics(t *testing.T, inboundId int, clients []model.Client) {
|
||||
@@ -37,14 +32,7 @@ func seedClientTraffics(t *testing.T, inboundId int, clients []model.Client) {
|
||||
// reachable from the REST API at 100k/200k clients, asserting none crash on the
|
||||
// PostgreSQL bind-parameter ceiling and logging the wall-clock cost of each.
|
||||
func TestAllAPIsPostgresScale(t *testing.T) {
|
||||
if strings.TrimSpace(os.Getenv("XUI_DB_DSN")) == "" || os.Getenv("XUI_DB_TYPE") != "postgres" {
|
||||
t.Skip("set XUI_DB_TYPE=postgres and XUI_DB_DSN to run the postgres scale benchmark")
|
||||
}
|
||||
xuilogger.InitLogger(logging.ERROR)
|
||||
if err := database.InitDB(""); err != nil {
|
||||
t.Fatalf("InitDB: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = database.CloseDB() })
|
||||
setupScaleDB(t)
|
||||
|
||||
svc := &ClientService{}
|
||||
inboundSvc := &InboundService{}
|
||||
@@ -56,9 +44,7 @@ func TestAllAPIsPostgresScale(t *testing.T) {
|
||||
for _, n := range sizes {
|
||||
t.Run(fmt.Sprintf("N=%d", n), func(t *testing.T) {
|
||||
db := database.GetDB()
|
||||
if err := db.Exec("TRUNCATE TABLE inbounds, clients, client_inbounds, client_traffics, client_groups RESTART IDENTITY CASCADE").Error; err != nil {
|
||||
t.Fatalf("truncate: %v", err)
|
||||
}
|
||||
resetScaleTables(t, db, "inbounds", "clients", "client_inbounds", "client_traffics", "client_groups")
|
||||
|
||||
clients := makeScaleClients(n)
|
||||
exp := time.Now().AddDate(1, 0, 0).UnixMilli()
|
||||
@@ -150,14 +136,7 @@ func TestAllAPIsPostgresScale(t *testing.T) {
|
||||
// old path (GetClientByEmail, which parses the inbound's entire settings JSON to
|
||||
// find one client) vs new path (UUID/subId read from the indexed clients table).
|
||||
func TestGetClientTrafficByEmailABScale(t *testing.T) {
|
||||
if strings.TrimSpace(os.Getenv("XUI_DB_DSN")) == "" || os.Getenv("XUI_DB_TYPE") != "postgres" {
|
||||
t.Skip("set XUI_DB_TYPE=postgres and XUI_DB_DSN to run the postgres scale benchmark")
|
||||
}
|
||||
xuilogger.InitLogger(logging.ERROR)
|
||||
if err := database.InitDB(""); err != nil {
|
||||
t.Fatalf("InitDB: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = database.CloseDB() })
|
||||
setupScaleDB(t)
|
||||
|
||||
svc := &ClientService{}
|
||||
inboundSvc := &InboundService{}
|
||||
@@ -179,9 +158,7 @@ func TestGetClientTrafficByEmailABScale(t *testing.T) {
|
||||
for _, n := range sizes {
|
||||
t.Run(fmt.Sprintf("N=%d", n), func(t *testing.T) {
|
||||
db := database.GetDB()
|
||||
if err := db.Exec("TRUNCATE TABLE inbounds, clients, client_inbounds, client_traffics RESTART IDENTITY CASCADE").Error; err != nil {
|
||||
t.Fatalf("truncate: %v", err)
|
||||
}
|
||||
resetScaleTables(t, db, "inbounds", "clients", "client_inbounds", "client_traffics")
|
||||
clients := makeScaleClients(n)
|
||||
ib := &model.Inbound{UserId: 1, Tag: fmt.Sprintf("ctbe-%d", n), Enable: true, Port: 40000, Protocol: model.VLESS, Settings: clientsSettings(t, clients)}
|
||||
if err := db.Create(ib).Error; err != nil {
|
||||
|
||||
Reference in New Issue
Block a user