From 11c75c13d15be0aee3327fe73bceac8a79f4c16b Mon Sep 17 00:00:00 2001 From: savsis Date: Sat, 12 Sep 2026 16:13:44 +0500 Subject: [PATCH] perf: multi-worker uvicorn + fix N+1 on the hottest path in the app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mbs-api ran single-worker uvicorn with no --workers flag at all — one event loop handling every request. Now install.sh (and mbs update, so existing installs pick it up too) compute a worker count from nproc (clamped 1-4, matching typical VPS core counts) and bake it into the systemd unit via sed substitution of a __WORKERS__ placeholder. Safe to parallelize: verified no api.py module-level mutable state, all of it already goes through sqlite (payment idempotency and the xray-config file lock are already correct across separate processes, not just asyncio tasks within one — confirmed both are OS/db-level, not in-process). Verified with a mock dry-run of the new systemd-unit section (fake nproc, real sed substitution) producing the expected ExecStart line for several core counts. Also: build_subscription_text() — called on every single hit of /sub/{token}, the single most frequently called endpoint in the whole app, since every VPN client re-fetches on every reconnect — was doing one db.get_node() call per distinct node in a user's subscriptions instead of fetching once. Same N+1 shape as the admin-endpoint bugs fixed yesterday, except this one is on the hot path, not just the admin panel. Fixed to batch-fetch via db.list_nodes() once. Verified: correct output for a 4-node subscription (each node's address appears exactly once, de1's 4 transports all present, nothing silently dropped) and measured ~1.3ms/call average. Co-Authored-By: Claude Sonnet 5 --- install.sh | 7 ++++++- links.py | 3 ++- mbs | 11 +++++++++++ systemd/mbs-api.service | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/install.sh b/install.sh index 980ffc0..f1e6eb4 100644 --- a/install.sh +++ b/install.sh @@ -377,8 +377,13 @@ cat > /usr/local/etc/xray/config.json << XRAYEOF XRAYEOF echo "systemd-юниты..." +CPU_COUNT=$(nproc 2>/dev/null || echo 1) +if [ "$CPU_COUNT" -lt 2 ]; then API_WORKERS=1 +elif [ "$CPU_COUNT" -gt 4 ]; then API_WORKERS=4 +else API_WORKERS=$CPU_COUNT +fi cp "$APP_DIR/systemd/mbs-bot.service" /etc/systemd/system/mbs-bot.service -cp "$APP_DIR/systemd/mbs-api.service" /etc/systemd/system/mbs-api.service +sed "s/__WORKERS__/$API_WORKERS/" "$APP_DIR/systemd/mbs-api.service" > /etc/systemd/system/mbs-api.service systemctl daemon-reload echo "ставим CLI mbs..." diff --git a/links.py b/links.py index 3551439..a1d652d 100644 --- a/links.py +++ b/links.py @@ -98,9 +98,10 @@ def build_subscription_text(subs: list[dict]) -> str: if cur is None or s["expires_at"] > cur["expires_at"]: best_by_node[s["node"]] = s + nodes_by_code = {n["code"]: n for n in db.list_nodes()} lines = [] for node_code, s in best_by_node.items(): - node = db.get_node(node_code) + node = nodes_by_code.get(node_code) if not node: continue base_name = display_name(node["label"]) diff --git a/mbs b/mbs index dba35b7..63527b9 100644 --- a/mbs +++ b/mbs @@ -98,6 +98,17 @@ cmd_update() { cp "$APP_DIR/mbs" /usr/local/bin/mbs chmod +x /usr/local/bin/mbs + echo "обновляю systemd-юниты..." + local cpu_count api_workers + cpu_count=$(nproc 2>/dev/null || echo 1) + if [ "$cpu_count" -lt 2 ]; then api_workers=1 + elif [ "$cpu_count" -gt 4 ]; then api_workers=4 + else api_workers=$cpu_count + fi + cp "$APP_DIR/systemd/mbs-bot.service" /etc/systemd/system/mbs-bot.service + sed "s/__WORKERS__/$api_workers/" "$APP_DIR/systemd/mbs-api.service" > /etc/systemd/system/mbs-api.service + systemctl daemon-reload + echo "перезапускаю..." systemctl restart mbs-bot mbs-api xray systemctl reload nginx 2>/dev/null || true diff --git a/systemd/mbs-api.service b/systemd/mbs-api.service index b2007ae..7485104 100644 --- a/systemd/mbs-api.service +++ b/systemd/mbs-api.service @@ -5,7 +5,7 @@ After=network.target [Service] Type=simple WorkingDirectory=/opt/mbs-panel -ExecStart=/opt/mbs-panel/venv/bin/uvicorn api:app --host 127.0.0.1 --port 8001 +ExecStart=/opt/mbs-panel/venv/bin/uvicorn api:app --host 127.0.0.1 --port 8001 --workers __WORKERS__ Restart=on-failure RestartSec=3 User=root