🚀 Fleet AHV — Sơ đồ & Chuẩn đặt tên
Cập nhật 11/07/2026. Nguồn chuẩn: /home/claudeproxy/Claude/ahv_fleet_runbook.md trên orchestrator. Giờ hệ thống toàn fleet = Asia/Ho_Chi_Minh (+7); riêng DB PostgreSQL giữ UTC nội bộ.
Chuẩn đặt tên (BẮT BUỘC)
- Node =
n<octet-cuối-IP>— 1:1 với IP, không bao giờ nhầm. Node mới .200 → n200. - Backend =
{tính-năng}-{node}(external =ext-{provider}). Token tính năng: tryon, gen, swap, upscale, video, remove, rembg, scan, nsfw. - Category (API user gọi — KHÔNG đổi): try_on, ahv_image, faceswap, faceswap_video, upscale, remove_object, remove_background, scan_objects, nsfw_check + external.
Sơ đồ node hiện tại (5 node GPU nhà + pod RunPod + orchestrator)
| Node | IP | GPU | Backend đang chạy | Ghi chú |
|---|---|---|---|---|
| n131 | 103.253.21.131 | 48G RTX PRO 5000 | tryon-131(6), gen-131(3), video-131(3), swap-131(30), upscale-131(10), swapprem-131(2) | Workhorse; node duy nhất đủ VRAM cho premium video (pilot). video slots=3 khớp --workers 3 — xem mục 5 |
| n81 | 103.253.21.81 | 24G RTX PRO 4000 | tryon-81(2), gen-81(1), swapprem-81(1) | FLUX-ONLY (12/07: fsimage+fsvideo stopped — backend disabled thì service phải tắt; bật video-81 = start faceswap-video TRƯỚC). Là node DỰ PHÒNG video |
| n239 | 103.253.21.239 | 24G RTX PRO 4000 | tryon-239(2), gen-239(1), swapprem-239(1), video-239 TẮT | Node nguồn rsync; fsimage stopped 12/07 (mồ côi 8.2G gây OOM tryon) |
| n183 | 178.63.96.183 | 20G (Hetzner) | remove-9b-183(:2034), swap-183(20), upscale-183(8), stt-183(:2036), rembg/scan/nsfw-183(:2026) | Node phụ trợ — KHÔNG chạy flux |
| n22 | 161.248.81.22 | 24G RTX PRO 4000 | tryon-22(2), swapprem-22(1) | Vào trận 11/07 giữa sự cố quá tải; đủ 4 stack trên disk. Chạm trần công suất 145W lúc tải nặng — TỰ phục hồi |
| pod ada1 | 213.173.108.25 | RTX 4000 Ada (RunPod) | tryon-vn2-ada1(1) | $0.28/h. Chạm trần 130W lúc tải nặng — TỰ phục hồi |
| pod scale1 | (đổi mỗi lần bung) | RTX 4000 Ada (RunPod) | tryon-vn2-scale1(1) | Do autoscaler tự bung/tự trả (mục 9). Tắt lúc vắng là BÌNH THƯỜNG |
| pod music | 69.30.85.239 | RTX A5000 (RunPod) | music_gen, music_cover, tts | $0.27/h. ssh cổng 22198 |
| Orchestrator | 15.235.200.66 | — | FastAPI :2022, PostgreSQL, JOB_WORKERS=80×4 | SPOF — cần dự phòng khi scale. Là chính máy đang chạy AHV AI — thao tác local, KHÔNG ssh vòng về |
⚠️ Số trong ngoặc là slots. Bảng này rất dễ lạc hậu — nguồn sự thật là
curl -s -u admin:Anhyeuem http://127.0.0.1:2022/admin/api/backends. Đối chiếu lại mỗi khi cân đội hình.
Nguyên tắc thiết kế: (1) mọi node đủ mọi stack trên DISK — cái gì "nóng" trên VRAM do vận hành quyết theo tải; (2) thêm tính năng = thêm 1 stack + 1 category backend, không sửa kiến trúc; (3) thêm node = chạy onboard script; (4) mọi chuyển dịch kiểu "bật mới song song → verify job thật → tắt cũ", không gián đoạn.
Quy tắc VRAM & Xếp hình
- flux (FLUX.2-klein-9B NF4, phục vụ cả /tryon và /generate trên :1994) load-peak 14.7G, steady ~13G.
- fs-image 8.8G; video 9.5G; diffusion remove ~3.5G; upscale nằm trong fs-image.
- Node 24G: CHỈ flux + phụ ≤5G. flux + fs-image = OOM (thiếu 300MB — đã đo thật). Node 20G: không chạy flux. Chỉ n131 48G ôm được nhiều.
- Thứ tự bật trên node ghép: flux load TRƯỚC (cần peak), dịch vụ khác SAU.
- Mọi node flux PHẢI có drop-in
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True(chống VRAM phình → 503). - Video (inswapper) slot = free_VRAM/1.5G VÀ ≤3-4/node-24G (giới hạn phiên NVENC) — quá slot = lỗi "NVENC stream encode failed" / ONNX init OOM (đo thật 11-12/07).
- Service bloat theo giờ chạy (flux >20G, fsvideo phình ~2-8h/lần khi tải nặng) → restart node đó lúc vắng khi free <2G.
- Backend enabled ⇔ service running — backend tắt thì TẮT service (mồ côi ăn VRAM: n81 15.5G + n239 8.2G phát hiện 12/07); backend bật thì service PHẢI chạy (video-81 4 job fail 12/07).
- KHÔNG sync
config.pygiữa các node (BASE_URL per-node — sự cố 12/07: 76 job link 404 trong 40 phút). Chỉ sync code (server.py/pipeline.py). - KHÔNG tải file lớn (model) trên node đang production — bão hòa băng thông làm HTTP/SSH treo (sự cố 11h15 12/07). Tải lúc drain/khuya hoặc --limit-rate.
- GPU 100% util thì nâng slots là VÔ ÍCH (thrash) — chỉ có thêm node hoặc kịch bản ưu tiên.
Lệnh vận hành nhanh (từ orchestrator)
# Backend API (basic auth admin:***)
GET/POST/PUT http://127.0.0.1:2022/admin/api/backends # POST=thêm, PUT=sửa {category,name,...}
# Bật tính năng X trên node N
systemctl start <unit> → ufw allow port → curl test → POST backend slots nhỏ → theo dõi job thật → tăng slots
# Tắt an toàn: PUT enabled=false TRƯỚC (drain) → systemctl stop
# Restart flux 1 node an toàn (flux :1994 phục vụ CẢ tryon + gen!):
# disable cả tryon-X lẫn gen-X → drain ~20s → restart → health 200 → enable lại
- KHÔNG restart orchestrator lúc tải cao (fail job đang chạy) — đổi env qua systemd drop-in, restart lúc khuya vắng.
- SSH mọi node: key
gpu_admin(password login đã TẮT trên n22, nên tắt dần các node cũ). Key tạm onboard phải xoá sau khi xong. - Admin panel Server: danh sách node ở
/opt/ai-orchestrator-2022/collect_server_metrics.py— thêm node mới vào SERVERS.
⚡ Playbook sự cố (đã kiểm chứng thực chiến)
1. try_on quá tải (khách kêu chậm)
Ngưỡng vào kịch bản: p50>58s & p95>90s & running>90 building 2 nhịp — HOẶC khách kêu + p50>60s kéo dài. Lưu ý: p50/p95 vọt nhưng running thấp (<30) = burst tự tan (mẫu ~20h18 hằng đêm), KHÔNG can thiệp.
BẬC 1: PUT enabled=false gen-81 + gen-239 # dồn GPU n81/n239 cho try_on
BẬC 2: PUT enabled=false swap-131 # dồn n131 (faceswap vẫn chạy qua swap-183)
TAN (p50<40 & running<25): bật lại NGƯỢC bậc: swap-131 → gen-239 → gen-81
Thực chiến 10-11/07: p50 94s→29.5s trong ~90 phút (BẬC 1+2 + đưa n22 vào cấp tốc), 0 job mất.
2. 503 hàng loạt cùng 1 backend (≥3/nhịp)
= OOM node đó. Xem nvidia-smi; flux phình VRAM → restart flux node đó (quy trình drain ở trên); kiểm expandable_segments.
3. Link ảnh 404
Nguyên nhân kinh điển: (a) BASE_URL node trỏ sai IP (rsync đè config.py — bài học n22); (b) retention xoá sớm (đã chuẩn 24h mọi node). File thật luôn nằm trên node XỬ LÝ — cứu link đã phát bằng cách rsync files/ sang node có IP trong link.
4. disconnect/502 lẻ tẻ
1 lần đơn lẻ = transient, bỏ qua sau khi probe health OK. CÙNG backend ≥2/15 phút → probe, chết/chậm → restart service node đó.
5. Video quá tải / lỗi NVENC-ONNX
Sơ đồ chốt 28/07 — video KHÔNG ngồi chung node với flux try_on: chạy trên video-131 (48G, slots 3).
video-81 là dự phòng (đang TẮT); video-239 và video-22 TẮT + systemctl disable. Lý do: flux try_on ăn ~14G,
faceswap-video ăn ~7.5G — chung một card 24G chỉ còn ~2.6G thở → OOM hàng loạt lúc đỉnh
(27/07: 543 job try_on chết vì OOM trên n22, dứt đúng phút n22 stop faceswap-video lúc 23:07).
⚠️ BẮT BUỘC LÀM NÓNG trước khi bật backend video: job đầu sau khi start service mất ~193s
(source_detect nạp model 133s), job sau chỉ ~37s. Bật backend ngay = khách đầu tiên chờ 193s.
Quy trình đúng: start service → health 200 → gửi 1 job thật để nạp model → mới PUT enabled=true →
chờ nó xong 1 job thật từ hàng đợi → mới tắt node cũ (giữ luôn ≥2 backend video sống).
⚠️ /health 200 KHÔNG có nghĩa node còn sống: 28/07 video-239 có cặp ffmpeg treo 18.3 giờ,
service --workers 1 nên bị khoá hoàn toàn — mọi job timeout thành 502 trong khi /health vẫn trả 200.
Đã cài /usr/local/bin/ffmpeg_watchdog.sh (cron */5, giết ffmpeg >1200s) trên cả 4 node video 239/131/81/22.
Nghi node video hỏng thì probe bằng job thật, đừng tin /health.
Node nào NVENC/ONNX >3/5phút → giảm slot node đó (free/1.5G) hoặc restart faceswap-video node đó (drain→restart→re-enable). Thực chiến 11/07: demand 75 → 3 node slot-theo-VRAM → 0 fail.
⚠️ slots PHẢI khớp số worker THẬT của service (bài học 28/07 22:50). video-131 khai
slots=8 trong khi service chạy --workers 3 → job thứ 4–8 xếp hàng bên trong service,
orchestrator không nhìn thấy, và thời gian chờ đó bị tính vào duration_sec. Hậu quả: có job
1 khung hình mất 341 giây mà dashboard không hề báo nghẽn. Sửa slots 8→3 (nóng, không cần restart)
cho kết quả đo được:
số job chồng nhau trước sửa sau sửa
1 63 s 38 s
2 54 s 36 s
3 76-79 s 31 s
4 176-341 s 50 s (đỉnh 80 s) ← giảm hơn 4 lần
hàng đợi video sau khi sửa = 0 (không đánh đổi bằng việc bắt khách chờ)
Dấu hiệu nhận biết KHÔNG phải là số worker — uvicorn 1 worker vẫn chạy song song qua threadpool,
nên đừng suy ra "1 worker thì slots phải = 1" (try_on trên node 131 khai slots=6, service 1 worker, vẫn 8 giây đều).
Dấu hiệu thật là tương quan chồng-lấn ↔ độ trễ. Cách đo (thay <TASK>):
with v as (select started_at, finished_at, duration_sec from jobs
where task='<TASK>' and status='succeeded' and finished_at > now() - interval '60 min'),
o as (select a.duration_sec,
(select count(*) from v b
where b.started_at < a.finished_at and b.finished_at > a.started_at) k
from v a)
select least(k,8) chong_lan, count(*) n, round(avg(duration_sec)::numeric,1) tb,
round(max(duration_sec)::numeric,1) mx
from o group by 1 order by 1;
Đã soát toàn hệ thống 28/07 23:05: faceswap / upscale / remove_object phẳng theo chồng lấn (slots cao vẫn an toàn vì job chỉ 2–3 giây); ahv_image nhích nhẹ nhưng mẫu quá ít để kết luận. Chỉ video bị lỗi này.
🐞 CHƯA SỬA — job video ra 1 khung hình nhưng báo succeeded (chẩn đoán 29/07 02:00).
Tần suất 3 lần/24h trên 1028 job (0,3%). Khách đợi ~5,5 phút rồi nhận video 1 khung, không tiếng, mà hệ thống
báo thành công nên không ai biết. Chuỗi log trên video-131:
Decoder frame read timeout (60s), killing ffmpeg
WATCHDOG: killing stuck ffmpeg pid=... age=327s
CUVID decode failed, falling back to stream decode
Stream decode: 1 frames
NVENC failed (rc=-9, write_ok=False) ← rc=-9 = bị watchdog SIGKILL
libx264 recovery encode: 1 frames in 328.15s
DONE: 1 frames ← ghi succeeded
Nguyên nhân: đường giải mã có timeout 60s (server.py dòng 604) chạy đúng; đường mã hóa NVENC
không có timeout nào → chỉ có watchdog nền FSV_FFMPEG_MAX_AGE=300 (dòng 286) cứu, nên mất ~5,5 phút.
⚠️ BẪY: ĐỪNG hạ FSV_FFMPEG_MAX_AGE xuống ~90s (sửa được bằng biến môi trường nên rất dễ làm).
Video dài HỢP LỆ có job 1265 giây (21 phút) và nó giữ một tiến trình ffmpeg suốt thời gian đó → hạ ngưỡng
tuổi là giết oan video dài. Sửa đúng cần code: (1) timeout cho thao tác ghi vào bộ mã hóa, tính theo
tiến độ chứ không theo tuổi tiến trình; (2) khi đường cứu hộ chỉ vớt được 1 khung thì trả lỗi,
đừng trả succeeded. Cách tìm lại ca lỗi:
select to_char(finished_at + interval '7 hours','DD/MM HH24:MI') xong,
round(duration_sec::numeric,1) giay, result->'meta'->>'frames' khung
from jobs where task='faceswap_video' and status='succeeded'
and (result->'meta'->>'frames')::int <= 2
and finished_at > now() - interval '24 hours' order by finished_at desc;
6. Job kẹt / metric running phồng ở đỉnh
Watchdog 900s chạy trễ dưới tải (coroutine đói — mục Audit 6/7). Job không leak vĩnh viễn (recover lúc boot). Fix triệt để cần sửa server.py + restart orchestrator lúc vắng (đang chờ duyệt).
7. Node thành "hố đen" — queue flux đầy request ma (17/07)
Dấu hiệu: health node queue_size cao bất thường (≥10) so với slots, mọi job qua node đó siêu chậm, các node khác rảnh. Nguyên nhân: request timeout phía orchestrator nhưng flux vẫn giữ chỗ trong queue.
1. PUT enabled=false các backend của node (tryon-X, swapprem-X, gen-X...)
2. Chờ 45-60s cho job thật đang render xong
3. ssh node: systemctl restart flux-klein-tryon
4. Chờ health model_loaded:true → PUT enabled=true gắn lại TẤT CẢ backend vừa tắt
LƯU Ý: làm XONG TRỌN node này rồi mới sang node khác — không để category nào 0 backend dù 1 phút.
8. Restart orchestrator — quy tắc sống còn (bài học 85 job 17/07, cập nhật 28/07)
Từ 27/07 restart KHÔNG còn giết job khách nữa. recover_stale_jobs() lúc boot: job đang
running và tạo trong vòng 30 phút → trả về queued + đẩy lại hàng đợi; job cũ hơn/mồ côi → failed.
Dùng advisory lock pg_try_advisory_lock(918273645) nên trong 4 worker chỉ 1 worker chạy hồi phục —
không bao giờ re-queue trùng. Đã verify 4 lần (11/15/7/3 job in-flight, không mất job nào).
# Đếm in-flight trước khi restart (chỉ để biết bao nhiêu job sẽ bị re-queue):
SELECT status, count(*) FROM jobs WHERE status IN ('queued','running') GROUP BY status;
# LƯU Ý: chỉ có 'queued' và 'running'. KHÔNG có status 'processing' — đừng lọc theo nó.
Vẫn nên tránh restart lúc tải cao: job bị re-queue phải chạy lại từ đầu (khách đợi thêm 1 lượt) và cả 4 worker cùng lúc mất kết nối đang mở. Cần đổi env → dùng systemd drop-in, restart lúc vắng.
Onboard node GPU mới
bash /home/claudeproxy/Claude/onboard_node.sh <IP> # (node đã nhận key gpu_admin)
Script tự làm: rsync 4 stack từ n239, copy systemd units, libnvinfer, set BASE_URL/FSV_PUBLIC_BASE=IP mới, expandable_segments, ufw. SAU KHI rsync ALL_DONE — 2 việc BẮT BUỘC (bài học n22 10-11/07):
- Chạy lại bước set BASE_URL — rsync ĐÈ config.py bằng bản node nguồn → link sẽ trỏ sai IP.
- Kiểm Python: venv build cho python3.10; node OS mới (3.12) phải
apt install python3.10(deadsnakes) +ln -sf /usr/bin/python3.10 venv/bin/python3.
Rồi: start flux → health 200 → POST backend tryon-<octet> slots 2 → theo dõi job thật (link đúng IP!) → xoá key tạm onboard_tmp → thêm vào SERVERS admin panel → tắt password SSH.
Giám sát & Ngưỡng
Kiến trúc 3 tầng (từ 16/07 — vận hành THEO NGƯỠNG, không theo giờ): (1) scripts/auto_bac.sh crontab root */2ph — vượt ngưỡng (stuck>150s hoặc ≥8 job >120s/10ph) tự BẬC1+BẬC2, yên 25ph tự trả FULL (log /var/log/auto_bac.log); (2) scripts/alert_metrics.sh — nguồn số cho chuông Monitor của phiên AI (75s/nhịp) réo AI vào xử + nhắn anh kết quả; (3) cron trực AI 12ph = backup + leo thang (cách ly node, role-swap). Im lặng khi xanh; chỉ nhắn khi sự cố/đã-xử/cần-quyết. Đếm in-flight PHẢI gồm status running.
⚠️ GIỚI HẠN của auto_bac.sh — ĐỪNG mất công vá (đã kiểm chứng 28/07): nó chỉ thấy job chậm
(STUCK đếm queued/running, DMG đếm job succeeded >120s). Job chết vì OOM fail tức thì nên
vô hình với cả hai. Nhưng thêm điều kiện OOM vào trigger cũng VÔ ÍCH: log 27/07 cho thấy valve đã
nổ 41 lần liên tục 18:00–23:00 mà 543 job vẫn chết OOM — van đã mở hết cỡ sẵn. Lý do gốc: BẬC1/BẬC2 chỉ
tắt được gen/swap, không giải phóng nổi VRAM mà service faceswap-video đang giữ. Cách duy nhất chữa OOM
là gỡ service video khỏi node chạy flux try_on (xem mục 5).
- Khung tải VN (+7): sáng 7-9h, trưa 11-14h, tối 19-22h nặng nhất (+burst ~20h18 tự tan), volume đang TĂNG từng ngày kể cả khuya.
- Lỗi INPUT — KHÔNG tính sự cố hệ thống: no-face, mask trống, 400/404, NSFW_BLOCKED, "Failed to download image" (storage 5.223.78.252 xoá input ngay — lỗi try_on nhiều nhất), "image file is truncated".
- Đọc node lỗi bằng field
backendtrong error JSON (tên chuẩn) — ĐỪNG tin text "VN2 try-on" (label cũ hardcode). - DB timestamp = UTC (giờ máy = VN+7). Node xử lý ≠ node lưu file.
9. Pod RunPod mồ côi & autoscaler thất bại trong im lặng (29/07)
⚠️ Mỗi ca trực phải ĐỐI CHIẾU danh sách pod RunPod với danh sách backend. Pod không nằm trong
backend nào = đốt tiền không công. Đêm 29/07 bắt được pod ahv-scale-1 chạy khỏe, qua canary gate,
nhưng không có trong cấu hình — 1,8 giờ không nhận một job nào.
RP=$(grep -oP '(?<=RUNPOD_API_KEY=).*' /home/claudeproxy/Claude/.secrets/runpod.env)
curl -s -X POST "https://api.runpod.io/graphql?api_key=$RP" -H "Content-Type: application/json" \
-d '{"query":"query { myself { pods { id name desiredStatus costPerHr runtime { uptimeInSeconds ports { ip publicPort } } } clientBalance } }"}'
# rồi so với: curl -s -u admin:Anhyeuem http://127.0.0.1:2022/admin/api/backends
Hai lỗi chồng nhau trong rp_autoscale.sh (đã vá 29/07, backup .bak-regfix-29-07):
- Nguyên nhân —
$PODrỗng mang hai nghĩa: thật sự không có pod nào, HOẶC gọi API RunPod thất bại/quá hạn 20s. Script cũ không phân biệt được → một lần API chập chờn là tưởng đội hình trống và dựng pod TRÙNG. Đã sửa: đọc phản hồi thô trước, không thấy khóa"pods"thì bỏ qua nhịp thay vì đoán bừa. - Hậu quả — bước đăng ký dùng
POST(chỉ tạo mới) trong khi đường SCALE-DOWN cố ý giữ lại entry (chỉenabled=false). Nên từ lần scale-up thứ hai trở điPOSTluôn bị từ chối"already exists", script bỏ qua lỗi và vẫn ghiSCALE-UP DONE→ mọi lần tự tăng công suất đều thất bại trong im lặng. Nguy hiểm hơn tiền: lúc có sóng tải, hệ thống tưởng đã có thêm máy mà thực ra không có gì. Đã sửa:POST→ nếu trùng thìPUT→ kiểm chứng thật sự có trong danh sách backend → nếu vẫn không vào thì tựpodTerminate.
Bài học chung: đừng tin mã trả về, hãy kiểm chứng trạng thái cuối. Log ghi "DONE" không có nghĩa là xong.
Ngưỡng hiện tại: scale-up khi C>190 VÀ PREV>190 VÀ không có pod ahv-scale-*;
scale-down khi C<120 VÀ PREV<120. Log: /var/log/rp_autoscale.log.
Đặt tên pod: pod bung TAY phải đặt tên khác ahv-scale-* (ví dụ ahv-extra-1),
nếu không autoscaler sẽ terminate nhầm.
10. Service systemd hỏng âm thầm — kiểm NRestarts (29/07)
ai-orchestrator.service (cổng 8080) trỏ ExecStart tới /usr/bin/uvicorn không tồn tại
→ status=203/EXEC, systemd thử lại mãi: NRestarts=735657. Nó chưa từng chạy được lần nào nhưng vẫn
enabled, và gần như chắc chắn là nguồn làm tràn journal. Service thật đang phục vụ là
ai-orchestrator-2022 (NRestarts=0). Đã disable --now.
# soát định kỳ toàn máy — unit nào restart bất thường
systemctl list-units --type=service --state=activating --no-pager
for U in $(systemctl list-unit-files --type=service --state=enabled --no-pager | awk '{print $1}' | grep '\.service$'); do
N=$(systemctl show "$U" -p NRestarts --value 2>/dev/null); [ "${N:-0}" -gt 100 ] && echo "$U NRestarts=$N";
done
📚 Bài học sự cố (đọc trước khi vận hành)
| Ngày | Sự cố | Bài học |
|---|---|---|
| 04-05/07 | remove_object LaMa → diffusion | Nâng cấp kiểu song song + fallback; đo A/B 10 case trước khi chốt |
| 06/07 | Link 404 hàng loạt | 2 lỗi chồng nhau: retention 1h quá ngắn (→24h) + BASE_URL .81 sai. Fix cả gốc lẫn onboard script |
| 06/07 | Audit 2 vòng | Bug latent (FSV_PUBLIC_BASE .183, flux .183 còn enabled) phải quét chủ động, đừng chờ nổ |
| 10-11/07 | try_on quá tải kỷ lục (p50 108s, p95 912s) | 3 node bão hòa thật sự → BẬC 1+2 + n22 cấp tốc. 3 bug khi onboard gấp: rsync đè BASE_URL, python 3.12 vs venv 3.10, log DONE giả do kill vòng lặp (luôn dùng exit-code marker) |
| 11/07 | Video SPOF: drain n131 test → 305 job video "no backend" | Video giờ ≥2 node (video-131+video-239); TRƯỚC khi rút node khỏi category 1-node phải bật dự phòng; test GPU ban ngày chỉ khi có node gánh |
| 11-12/07 | Video đỉnh trưa 70+ concurrent, NVENC/ONNX fail | Slot video = free/1.5G và ≤3-4/node (NVENC session limit); fsvideo bloat theo giờ → restart lúc vắng |
| 12/07 | Premium ảnh deploy — 3 bug tự bắt trong audit | (a) KHÔNG sync config.py giữa node (BASE_URL — 76 job 404/40min); (b) service mồ côi ăn VRAM 2 node (n81 15.5G, n239 8.2G → OOM tryon-239); (c) backend enabled ⇔ service running. Tải model lớn trên node production làm nghẽn băng thông (HTTP/SSH treo ~10min) |
| 09-10/07 | Đánh giá FLUX 4B vs 9B | NF4 trên card này: 4B KHÔNG nhanh hơn 9B (nghẽn băng thông), chất lượng thấp hơn → gen/try_on giữ 9B; 4B chỉ đáng cho head_swap (VRAM 4.2G); giảm bước 8→4 mới là đòn bẩy tốc độ thật (distilled) |
| 13/07 | SLA thật của khách | Job xong sau ~120s = khách ĐÃ BỎ (anh chốt chuẩn chờ tối đa 120s, target p50 ~25s). Metric thiệt hại = số job >120s + số job fail, KHÔNG phải "0 lỗi hệ thống". 13/07: 26% job try_on >120s dù "thành công" |
| 14-15/07 | Chuẩn hoá driver fleet + n81 crash | Cả 4 node flux lên driver 610.43.02 + expandable_segments → 0 OOM từ đó. Node vừa reboot cần warm 30-60 phút (avg/job cao giả) — đo per-node trước khi kết luận node hỏng. Purge sạch driver cũ (metapackage remove để sót 16 pkg conflict) |
| 15/07 | Trùng task 5 lần + cron session chết lặng lẽ | Chuỗi ScheduleWakeup tự nối đẻ orphan → cấm. Cron session-only CHẾT khi phiên reset không báo → mọi phòng thủ sống còn phải nằm TRÊN SERVER (system crontab), phiên AI chỉ là tầng leo thang |
| 16/07 | Sóng trưa lọt lưới (185 job >120s) | Hạ bậc phản ứng theo nhịp trực = quá chậm với sóng dồn cụm → watchdog auto_bac.sh */2ph trên orchestrator: stuck>150s hoặc ≥8 job >120s/10ph → tự BẬC1+2, yên 25ph tự trả full. Anh chốt: vận hành THEO NGƯỠNG, không auto theo khung giờ |
| 17/07 | Sóng kỷ lục 15-24 job/ph ×100ph (601 fail + 1.4k >120s) | Chuỗi khuếch đại: chậm → timeout → orchestrator gửi lại → queue flux đầy 20 → 503 "Queue full" → job khách CHẾT. Fix: workers.py retry ≤6×20s re-pick node khi Queue full (đã deploy). Queue flux tồn request "ma" (client bỏ nhưng flux giữ chỗ) → node thành hố đen: cách ly → restart flux → gắn lại |
| 17/07 | 2 bài học đo lường + 1 tai nạn restart | (a) In-flight = status running (queued/processing chỉ có khi backlog) — đếm thiếu running làm check "idle" mù → restart orchestrator giết ~85 job đang chạy. (b) p50 bucket hiện tại bị survivorship bias (job chậm chưa xong chưa được đếm) — chỉ kết luận sóng rút khi running thấp VÀ arrival giảm. (c) Slots lệch (n131=6) làm 1 node ôm 14 job xếp hàng trong khi node khác rảnh → mọi node try_on slots=2 |
| 18/07 | Premium try_on 1/0 + rollout sót node | try_on premium:0=4 bước (~12s render), premium:1=8 bước (~23s), steps tường minh được tôn trọng (workers.py dòng ~895). Rollout cuốn chiếu bị đứt phiên giữa chừng ĐÃ BỎ SÓT n131 còn FORCE_TRYON_STEPS=4 → khách premium nhận hàng 4 bước — audit stress mới lộ. Quy tắc: sau mọi rollout nhiều node PHẢI quét lại drop-in TẤT CẢ node |
| 18/07 | Stress 16-job lộ lỗ hổng retry premium | Đường head_swap premium (đơn + multi) chưa có đợi-thử-lại → 2 job chết "Queue full" tức thì trong stress. Fix: _call_prem_retry (4 lần×12s re-pick). BÀI HỌC: mọi đường gọi flux ĐỀU phải qua retry — kiểm bằng grep _call_backend không được gọi trực tiếp cho category chạy flux |
| 18/07 | Service mồ côi đợt 2 | flux-kontext (n239 port 1993, 0 backend dùng — bom OOM nếu bị gọi) + faceswap-restore (n81, boot-enabled) → disable cả 2. Quy tắc audit định kỳ: systemctl running vs backends enabled phải khớp 1-1 |
| 18/07 | Xóa nhiều vật thể + hệ ảnh 9B | remove_object nhận mask_urls[] (gộp OR ≤10 mask); flow scan→tick→xóa hoàn chỉnh. /generate của flux = img2img theo prompt → pilot khôi phục ảnh cũ + làm đẹp + đổi tóc (anh chốt dùng 9B cho restore). Deploy orchestrator = deploy_wait.sh nohup chờ 0-inflight (gồm status running) |
Việc treo chờ quyết: NODE MỚI (cấp thiết — 3 ngày liên tiếp sóng vượt trần 4 node ~12 job/ph), mở bán premium video, retention bảng tasks, label "VN2" hardcode, cache input chống download-fail, orchestrator dự phòng (SPOF), retry queue-full cho gen/ahv_image (mới làm cho try_on). ĐÃ XONG 13-17/07: premium multi 2 người (live), music_gen v1 (code giữ, backend tắt theo lệnh anh), driver fleet 610, watchdog theo ngưỡng, retry queue-full try_on.
AHV AI Orchestrator v2.0
Hệ thống điều phối AI đa tác vụ – quản lý hàng đợi, phân phối backend GPU, webhook callback, và giám sát thời gian thực.
Stack
Python 3.10+ · FastAPI · Uvicorn (uvloop + httptools) · PostgreSQL (psycopg async) · Systemd
Architecture
Job Processing Flow
- Client gửi
POST /v1/jobsvớitask,payload,webhook_url - Job lưu DB với status
queued, trả vềjob_id+predict_time(EMA) - Webhook phase 1: gửi event
queued - Worker lấy job, chọn backend GPU (round-robin + semaphore)
- Webhook phase 2: gửi event
started - Backend xử lý xong → trả kết quả
- Webhook phase 3: gửi event
finishedvới URL + meta - EMA predictor tự học từ duration thực tế
Installation & Setup
Requirements
- Python 3.10+
- PostgreSQL 14+
- Nginx (reverse proxy, SSL)
- Linux with Systemd
Quick Start
cd /opt/ai-orchestrator-2022
pip install -r requirements.txt
# Create database
sudo -u postgres createdb ahv_orchestrator
sudo -u postgres psql -c "CREATE USER ahv_user WITH PASSWORD 'ahv_pass_2026';"
sudo -u postgres psql -c "GRANT ALL ON DATABASE ahv_orchestrator TO ahv_user;"
# Run (development)
uvicorn server:app --host 0.0.0.0 --port 2022 --reload
# Run (production)
sudo systemctl start ai-orchestrator-2022
sudo systemctl enable ai-orchestrator-2022
Service Management
sudo systemctl status ai-orchestrator-2022 # View status
sudo systemctl restart ai-orchestrator-2022 # Restart (~2s) — job in-flight được re-queue, xem mục 8
sudo systemctl reload ai-orchestrator-2022 # SIGHUP → uvicorn thay 4 worker kiểu CUỐN (restart_all)
# Cổng 2022 do tiến trình cha giữ nên KHÔNG đóng (không refuse
# kết nối), NHƯNG request đang chạy trên worker bị giết vẫn đứt.
# => "không rơi kết nối", KHÔNG phải "không gián đoạn job".
journalctl -u ai-orchestrator-2022 -f # Live logs
Environment Variables
Database
| Variable | Default | Description |
|---|---|---|
DB_HOST | localhost | PostgreSQL host |
DB_PORT | 5432 | PostgreSQL port |
DB_NAME | ahv_orchestrator | Database name |
DB_USER | ahv_user | Database user |
DB_PASSWORD | ahv_pass_2026 | Database password |
API & Auth
| Variable | Default | Description |
|---|---|---|
API_KEY | anhyeuem | API key for clients |
ADMIN_PASS | Anhyeuem | Admin panel password (Basic Auth) |
Timeout & Performance
| Variable | Default | Description |
|---|---|---|
REQUEST_TIMEOUT | 300 | Backend call timeout (seconds) |
CONNECT_TIMEOUT | 15 | Connection timeout (seconds) |
JOB_MAX_RUNTIME | 900 | Max job runtime before watchdog kills it |
MAX_QUEUE_SIZE | 1000 | Maximum queue capacity |
JOB_WORKERS | 40 | Concurrent worker count |
Webhook
| Variable | Default | Description |
|---|---|---|
WEBHOOK_TIMEOUT | 12 | Webhook delivery timeout (seconds) |
WEBHOOK_RETRIES | 3 | Max retry attempts |
WEBHOOK_BACKOFF_BASE | 0.6 | Exponential backoff base (seconds) |
WEBHOOK_SECRET | "" | HMAC-SHA256 signing secret |
EMA Predictor
| Variable | Default | Description |
|---|---|---|
PREDICT_EMA_ALPHA | 0.25 | Learning rate (0–1, higher = more responsive) |
PREDICT_MIN_SEC | 3 | Minimum predicted time (seconds) |
Database: Table jobs
Main table storing all job records. Each API call creates one row.
| Column | Type | Description |
|---|---|---|
job_id | TEXT PK | UUID unique identifier |
task | TEXT indexed | Task type: faceswap, try_on, ahv_image, ... |
payload | JSON | Client-provided parameters |
webhook_url | TEXT | Callback URL |
status | TEXT indexed | queued · running · succeeded · failed · rejected |
progress | INTEGER | 0–100 |
result | JSON | Success result: {url, filename, meta} |
error | JSON | Error details: {code, message} |
created_at | TIMESTAMP | Job creation time |
started_at | TIMESTAMP | Processing start time |
finished_at | TIMESTAMP | Completion time |
duration_sec | FLOAT | Actual processing duration (seconds) |
predict_time | INTEGER | Predicted duration from EMA (seconds) |
eta_utc | TIMESTAMP | Estimated completion time |
attempts | INTEGER | Retry attempt count |
expires_at | TIMESTAMP | Auto-delete after this time |
Indexes: idx_jobs_created, idx_jobs_expires, idx_jobs_status_created
Database: Table ema_predictions
Stores Exponential Moving Average values for time prediction per task variant.
| Column | Type | Description |
|---|---|---|
key | TEXT PK | Task variant key (e.g. faceswap:up1:ms1080) |
value | FLOAT | Current EMA estimate (seconds) |
count | INTEGER | Number of samples learned |
updated_at | TIMESTAMP | Last update time |
EMA Formula: new = alpha × observed + (1 - alpha) × old. Alpha = 0.25 means 25% weight on new observations.
Database: Table webhook_logs
Tracks failed webhook deliveries for debugging. Auto-cleaned after 7 days.
| Column | Type | Description |
|---|---|---|
id | SERIAL PK | Auto-increment ID |
job_id | TEXT indexed | Related job |
event | TEXT | queued · started · finished |
url | TEXT | Webhook URL that failed |
attempt | INTEGER | Which retry attempt |
status_code | INTEGER | HTTP status code returned |
error | TEXT | Error message |
created_at | TIMESTAMP | Log timestamp |
API: Jobs
POST /v1/jobs — Create Job
Creates a new async processing job.
curl -X POST http://15.235.200.66:2022/v1/jobs \
-H "Content-Type: application/json" \
-H "X-API-Key: anhyeuem" \
-d '{
"task": "faceswap",
"payload": {
"target_url": "https://example.com/photo.jpg",
"source_url": "https://example.com/face.jpg"
},
"webhook_url": "https://your-server.com/webhook"
}'
Response
{
"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "queued",
"predict_time": 10,
"eta_utc": "2026-06-14T10:00:10Z"
}
GET /v1/jobs/{job_id} — Get Job Status
Success Response
{
"job_id": "a1b2c3d4-...",
"task": "faceswap",
"status": "succeeded",
"progress": 100,
"result": {
"url": "http://15.235.200.66:2022/uploads/result.jpg",
"filename": "result.jpg",
"meta": {"width": 1080, "height": 1920, "backend": "FSVN1"}
},
"duration_sec": 3.5
}
Error Response
{
"job_id": "a1b2c3d4-...",
"status": "failed",
"error": {"code": 502, "message": "Backend timeout after 300s"}
}
API: Upload & NSFW
POST /upload — Upload File
curl -X POST http://15.235.200.66:2022/upload -F "file=@photo.jpg"
{"url": "http://15.235.200.66:2022/uploads/abc123.jpg", "filename": "abc123.jpg"}
POST /v1/nsfw-check — NSFW Check
curl -X POST http://15.235.200.66:2022/v1/nsfw-check \
-H "Content-Type: application/json" \
-d '{"image_url": "https://example.com/photo.jpg", "strict": "medium"}'
{"status": "ok", "nsfw_score": 0.02, "is_nsfw": false, "threshold": 0.5, "label": "safe"}
API: Health & Diagnostics
| Endpoint | Description |
|---|---|
GET /healthz | {"ok": true, "status": "healthy", "queue_size": 5} |
GET /diag/backends | All backend nodes: slots, in-flight, enabled, last_error |
GET /diag/queue | {"queued": 10, "running": 5} |
GET /stats | Recent jobs list (with ?limit= and ?task= filter) |
GET /daily-stats | Daily aggregation (timezone-aware via ?tz=) |
GET /events | SSE stream for realtime dashboard updates |
API: Admin Statistics
Auth Required: All admin endpoints require Basic Auth (username: admin, password: ADMIN_PASS)
| Endpoint | Description |
|---|---|
GET /admin/stats/overview | Summary: total jobs, success rate, avg duration, queue status |
GET /admin/stats/hourly | Hourly traffic breakdown + avg response time |
GET /admin/stats/by-kind | Job distribution by task type |
GET /admin/stats/daily-trend | 7-day trend (stacked area chart data) |
GET /admin/stats/top-users | Top API users by webhook URL |
GET /admin/stats/top-errors | Most common error messages |
GET /admin/stats/backend-perf | Per-backend performance metrics |
GET /admin/stats/export | Export jobs data (?fmt=json or csv) |
GET /admin/stats/live | Live counters: queued, running, succeeded, failed |
GET /admin/stats/webhook-failures | Failed webhook delivery logs |
DELETE /admin/stats/webhook-failures | Purge all webhook failure logs |
Query params: ?tz=Asia/Ho_Chi_Minh, ?days=7, ?limit=20
API: Backend Management
| Endpoint | Method | Body |
|---|---|---|
/admin/api/backends | GET | — |
/admin/api/backends | POST | {"category", "name", "url", "slots", "enabled"} |
/admin/api/backends | PUT | {"category", "name", "url", "slots", "enabled", "new_name"} |
/admin/api/backends | DELETE | {"category", "name"} |
/admin/api/retry | GET / POST | Get/set retry counts per task type |
/admin/api/tryon-settings | GET / POST | Virtual Try-On default settings |
/admin/api/cx-settings | GET / POST | CX Image model settings |
Tasks: Faceswap & Upscale
faceswap
Swap a face onto a target image. Timeout: 180s
Required: target_url, source_url
faceswap_multi
Swap multiple faces. Timeout: 180s
Required: target_url, source_urls[]
faceswap_video
Swap face in video, preserves audio. Timeout: 600s
Required: target_url, source_url
upscale
AI image enhancement + upscaling. Timeout: 300s
Required: target_url or image_url
Optional Parameters
| Param | Default | Description |
|---|---|---|
max_side | 1080 | Max output dimension (px) |
max_faces | 0 (all) | Limit number of faces to swap |
upscale | 1 | Enable upscaling (0/1) |
restore_face | 1 | Enable face restoration (0/1) |
sharpen | 0.12 | Sharpening strength (0–1) |
NSFW Protection: All face tasks automatically run NSFW check. Violations result in status: "rejected" with code NSFW_BLOCKED.
Tasks: Image Generation
ahv_image
Smart-routed text-to-image / image-to-image. Timeout: 300s
Backends: VN2 GPU, CX API, FlowProxy (auto-selected)
gg_image
Google Imagen-4 via FlowProxy. Timeout: 300s
Async polling: submit → poll → download
cx_image
CX/GPT Image API. Timeout: 120s
Direct API call with auto-upload
minimax_image
MiniMax image generation. Timeout: 120s
Supports I2I with image_url
Payload
| Param | Required | Description |
|---|---|---|
prompt | Yes | Text description of desired image |
image_url | No | Source image for image-to-image |
width / height | No | Output dimensions |
aspect_ratio | No | 1:1, 16:9, 9:16 |
model | No | Model override (backend-specific) |
steps | No | Inference steps |
guidance | No | Guidance scale |
seed | No | Random seed for reproducibility |
ahv_image routing: VN2 + no image_url → retries non-VN2. VN2 + image_url → VN2 GPU I2I. CX backend → CX API. FlowProxy → Imagen-4.
Tasks: Video Generation
ahv_video
Image-to-video via Google Veo / FlowProxy. Timeout: 600s
Required: prompt
minimax_video
MiniMax text/image-to-video. Timeout: 600s
Required: prompt
Payload
| Param | Required | Description |
|---|---|---|
prompt | Yes | Video description |
image_url | No | Source image (for I2V) |
duration | No | Video duration (default: 6s) |
aspect_ratio | No | 16:9, 9:16, 1:1 |
fps | No | Frames per second |
Tasks: Virtual Try-On
try_on
Virtual clothing try-on. Timeout: 300s
Smart routing: VN2 (sync) / PiAPI (async) / CX / FlowProxy
Payload
| Param | Required | Description |
|---|---|---|
person_image_url | Yes | Photo of the person (aliases: model_input, person_image) |
garment_image_url | Yes | Photo of garment (aliases: dress_input, garment_image) |
top_url | No | Upper garment (for split top/bottom) |
bottom_url | No | Lower garment (for split top/bottom) |
prompt | No | Additional instructions |
Smart Routing:
VN2 (port 1994) → Direct sync call (300s timeout)
PiAPI/Kling → Async submit + polling
CX → Multi-image prompt approach
FlowProxy → Imagen-4 with try-on prompt
Tasks: Image Tools
remove_object
Remove objects from image. Timeout: 120s
image_url, prompt (describe object)
scan_objects
Detect & label objects. Timeout: 60s
image_url
remove_background
Auto background removal. Timeout: 60s
image_url
nsfw_check
Content moderation check. Timeout: 30s
image_url, strict (low/medium/high)
Backend System
Multi-backend Architecture
Each task type has a category with multiple nodes (GPU servers):
Category: faceswap
├── FSVN1 (30 slots) → http://103.253.21.239:1993/faceswap
└── FSVN48G (30 slots) → http://178.63.96.183:1993/faceswap
Category: try_on
├── TryOn-VN2 (3 slots) → http://103.253.21.239:1994/tryon
└── TryOn-HZ1 (3 slots) → http://178.63.96.183:1994/tryon
Selection Algorithm
- Iterate enabled backends in order
- Pick first backend with free semaphore slot
- If all busy → pick least-loaded backend
- Disabled backends → skipped entirely
Backend Class Properties
| Property | Type | Description |
|---|---|---|
name | str | Display name (e.g. "FSVN1") |
url | str | GPU endpoint URL |
slots | int | Max concurrent requests |
enabled | bool | Active/inactive toggle |
api_key | str | Per-node API key (MiniMax, CX, ...) |
sem | Semaphore | Runtime concurrency limiter |
total_done | int | Jobs processed (live counter) |
last_error | str | Most recent error (diagnostics) |
Hot Reload: Add/edit/delete backends via Admin UI or API — no service restart needed. Config persists in backends_config.json.
Webhook System
3-Phase Event Flow
Success Payload
{
"job_id": "a1b2c3d4-...",
"task": "faceswap",
"status": "succeeded",
"event": "finished",
"progress": 100,
"url": "http://15.235.200.66:2022/uploads/result.jpg",
"filename": "result.jpg",
"meta": {"backend": "FSVN1", "width": 1080, "height": 1920, "duration_sec": 3.5}
}
Error Payload
{
"job_id": "a1b2c3d4-...",
"status": "failed",
"event": "finished",
"error": {"code": 502, "message": "Backend timeout after 300s"}
}
NSFW Rejected Payload
{
"job_id": "a1b2c3d4-...",
"status": "rejected",
"event": "finished",
"error": {"code": "NSFW_BLOCKED", "message": "Content violates policy"}
}
Retry & Security
| Feature | Detail |
|---|---|
| Retry count | 3 attempts |
| Backoff | Exponential: 0.6s → 1.2s → 2.4s (+ random jitter) |
| Signature | X-Signature: sha256=<hex> (HMAC-SHA256, if WEBHOOK_SECRET set) |
| Failure logging | Saved to webhook_logs table, viewable in Admin UI |
| Auto cleanup | Logs older than 7 days automatically deleted |
Auth & Security
API Key Authentication
| Path Pattern | Auth Required |
|---|---|
/, /public/*, /upload, /healthz | No |
/stats, /events, /tasks | No |
/v1/jobs, /v1/nsfw-check | API Key (X-API-Key header) |
/admin/api/* | Basic Auth (admin) |
CORS
All origins allowed (*) with credentials support.
NSFW Protection
Auto-applied to: faceswap, faceswap_video, upscale, try_on.
Violation → status: "rejected", code: NSFW_BLOCKED
Web Interfaces
Landing Page
Product overview, features, pricing, code examples
Dashboard
Realtime monitoring via SSE, traffic charts, job list
Admin Panel
Backend management, retry config, stats, webhook logs
API Demo
Test all APIs directly in browser with live preview
API Docs
Complete API reference with examples & error codes
Wiki
This page — full system documentation
File Structure
/opt/ai-orchestrator-2022/
├── server.py # FastAPI app: routes, DB, queue, webhook, auth, SSE (~1800 lines)
├── workers.py # Task dispatch: backend calls, normalize responses (~1370 lines)
├── handlers.py # Helpers: NSFW, webhook retry, EMA, payload builders (~620 lines)
├── backends.py # Backend registry: config, node management, selection (~430 lines)
├── backends_config.json # Backend node config (persists across restarts)
├── .env # Local environment overrides
├── requirements.txt # Python dependencies
├── README.md # Project documentation
│
├── public/
│ ├── index.html # Landing page
│ ├── dashboard.html # Realtime monitoring dashboard
│ ├── admin.html # Admin panel (backend management)
│ ├── webhook-demo.html # API testing tool
│ ├── docs-webhook.html # API documentation
│ ├── wiki.html # System wiki (this page)
│ └── uploads/ # Uploaded files directory
│
└── Systemd: /etc/systemd/system/ai-orchestrator-2022.service
File Responsibilities
| File | Lines | Role |
|---|---|---|
server.py | ~1800 | Main app: FastAPI routes, SQLAlchemy models, asyncio job queue, webhook delivery, auth middleware, SSE events, admin stats endpoints, watchdog |
workers.py | ~1370 | Task dispatch: route task → backend GPU, HTTP calls, response normalization, async polling (PiAPI, FlowProxy, MiniMax) |
handlers.py | ~620 | Helpers: NSFW detection, webhook retry with backoff, EMA time prediction, payload validation & building |
backends.py | ~430 | Backend management: load/save config, Backend class, round-robin selection, semaphore concurrency control |
Deployment & Operations
Systemd Service Configuration
[Service]
ExecStart=uvicorn server:app --host 0.0.0.0 --port 2022
--workers 4 --loop uvloop --http httptools
--timeout-keep-alive 75 --limit-concurrency 2000
--backlog 8192 --proxy-headers
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=1
TimeoutStopSec=10
KillSignal=SIGINT
LimitNOFILE=65535
Performance Tuning
| Optimization | Detail |
|---|---|
| uvloop + httptools | ~10% throughput improvement over stdlib |
| 4 Uvicorn workers | Multi-core CPU utilization |
| 40 Job workers | 40 concurrent job processing |
| DB pool | size=20, max_overflow=40 |
| Graceful reload | systemctl reload = zero-downtime (SIGHUP) |
| Fast restart | ~2 seconds (SIGINT + TimeoutStopSec=10) |
| CTE queries | Admin stats use Common Table Expressions |
Automatic Maintenance
| Mechanism | Interval | Action |
|---|---|---|
| Stuck job watchdog | Every 30s | Jobs running > 900s → force fail |
| Expired job cleanup | Every 60s | Jobs past expires_at → delete |
| Webhook log cleanup | Every 60s | Logs older than 7 days → delete |
Monitoring Commands
# Health check
curl http://15.235.200.66:2022/healthz
# Queue status
curl http://15.235.200.66:2022/diag/queue
# Backend health
curl http://15.235.200.66:2022/diag/backends
# Live logs
journalctl -u ai-orchestrator-2022 -f
Developer Guide
⚠ Mandatory Dev Rules (BẮT BUỘC)
Áp dụng cho mọi việc triển khai tính năng / dev / bugfix. Không được báo "pass"/"xong" nếu chưa làm đủ:
- Dùng Superpowers để triển khai (brainstorming → writing-plans → TDD → code-review → verification-before-completion).
- Audit + review + stress test thật 2 VÒNG mới được báo pass — hai vòng độc lập, có bằng chứng (output thật), không tự nhận pass khi chưa chạy kiểm chứng.
- Nếu có giao diện (UI): phải test bằng trình duyệt thật (Playwright/Chromium) trên cả mobile lẫn desktop thật.
- Mọi test phải mô phỏng như người dùng thật — đi đúng luồng người dùng, không mock cho qua.
Lý do: ưu tiên độ ổn định và trải nghiệm người dùng tuyệt đối — "người dùng là quan trọng nhất".
Adding a New Backend Node
- Go to Admin Panel → select category
- Click "Add Backend"
- Enter: name, URL, slots, API key (if needed)
- Enable → backend is ready immediately (no restart needed)
Adding a New Task Type
Step 1: Add dispatch function in workers.py
async def _dispatch_new_task(payload: dict) -> dict:
"""Call backend, return normalized result."""
prompt = payload.get("prompt")
if not prompt:
raise HTTPException(400, detail="prompt required")
backend = pick_backend("new_task")
async with backend.sem:
async with httpx.AsyncClient(timeout=300) as client:
resp = await client.post(backend.url, json={"prompt": prompt})
data = resp.json()
return {"ok": True, "url": data["result_url"], "meta": {"backend": backend.name}}
Step 2: Register in dispatch_task()
# In workers.py, inside dispatch_task()
if task == "new_task":
return await _dispatch_new_task(payload)
Step 3: Add fallback estimate in server.py
PREDICT_FALLBACKS = {
...
"new_task": 30, # default prediction: 30 seconds
}
Step 4: Add backend category & configure nodes via Admin UI
Processing Flow
Coding Conventions
| Convention | Detail |
|---|---|
| Normalized response | All tasks return: {ok, url, filename, meta, error} |
| Semaphore guard | Always use async with backend.sem: for concurrency |
| Error handling | Use BackendHTTPException(502, message, backend_name) |
| Async polling | Pattern: submit → poll every 3s → download → re-upload |
| NSFW check | Add at start of dispatch for user-facing image tasks |
| Retry | Only on 502 errors (backend issues), never on 4xx (client errors) |
Tip: Test new task types via API Demo page before deploying to production.