Câu hỏi thực nghiệm của bài: khi self host n8n queue mode trên một VPS, cần dựng đúng những gì, và cấu hình mặc định có mâu thuẫn nào khiến worker chậm trước cả khi tải tăng?
Kết luận từ tài liệu chính hãng, chưa phải từ phép đo: file Docker Compose mẫu của n8n gồm 6 service (Postgres, Redis, main, worker và 2 task runner). Mỗi worker mặc định nhận 10 job song song, trong khi pool kết nối Postgres mặc định chỉ là 2. Một response lớn đang truyền qua Redis có thể chiếm tới 96 MiB RAM (64 MiB x 1,5). Bài không đưa số thông lượng hay độ trễ hàng đợi p50/p90/p99. Tôi chưa tìm được số đo công khai nào có đủ 2 nguồn và ghi rõ cấu hình máy, nên mục cuối hướng dẫn bạn tự đo trên máy của mình.
Bài chỉ bàn phần cài đặt và vận hành worker. Kiến trúc agent chăm sóc khách hàng chạy trên n8n đã có ở bài Xây agent chăm sóc khách hàng với n8n, không lặp lại ở đây.
Đừng trả tiền thừa cho SaaS AI — Nhận lộ trình công cụ & chi phí tối ưu trong 10 giây
Mô tả bài toán của doanh nghiệp bạn (CSKH Zalo OA, Coding, RAG tài liệu nội bộ, Báo cáo P&L). Hệ thống cố vấn của 3A sẽ đề xuất bộ công cụ đã qua kiểm thử thực chiến.
Mọi dữ kiện trong bài lấy từ tài liệu chính hãng của n8n. Danh sách link kèm ngày đọc nằm ở mục Nguồn tham khảo cuối bài.
Self host n8n queue mode chạy những tiến trình nào?
Ở chế độ mặc định, một tiến trình n8n vừa nhận trigger vừa chạy workflow. Bật EXECUTIONS_MODE=queue thì việc tách ra: main nhận trigger và webhook rồi đẩy ID execution vào Redis, worker lấy job từ Redis, đọc dữ liệu workflow từ Postgres, chạy xong thì ghi kết quả lại Postgres.
| Thành phần | Vai trò | Bắt buộc? | Nguồn |
|---|---|---|---|
| PostgreSQL | Lưu workflow, credential, lịch sử execution | Có. Docs ghi queue mode với SQLite "không được khuyến nghị", hệ phân tán trên SQLite "không được hỗ trợ" | Docs queue mode |
| Redis | Hàng đợi job (Bull) | Có | Docs queue mode |
Main (n8n) | Giao diện, trigger, đưa job vào hàng đợi | Có | Docs queue mode |
Worker (n8n worker) | Chạy execution | Có, từ 1 trở lên | Docs queue mode |
Webhook processor (n8n webhook) | Nhận /webhook/* sau load balancer | Không, dùng khi webhook nhiều | Docs queue mode |
Task runner (n8nio/runners) | Chạy node Code ở tiến trình riêng | Có trong file mẫu chính hãng | Compose n8n-hosting |
n8n postgres docker compose: file mẫu chính hãng có gì?
Khối dưới đây chép nguyên văn file docker-compose.yml trong repo n8n-io/n8n-hosting, thư mục withPostgresAndWorker, tải ngày 19/10/2026 lúc 11h10 GMT+7. Tôi không sửa dòng nào. Giá trị thật để trong file .env cùng thư mục, gồm N8N_VERSION, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, POSTGRES_NON_ROOT_USER, POSTGRES_NON_ROOT_PASSWORD, ENCRYPTION_KEY, RUNNERS_AUTH_TOKEN. 3A chưa chạy file này trên VPS.
volumes:
db_storage:
n8n_storage:
redis_storage:
x-shared: &shared
restart: always
image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
- DB_POSTGRESDB_USER=${POSTGRES_NON_ROOT_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_NON_ROOT_PASSWORD}
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- QUEUE_HEALTH_CHECK_ACTIVE=true
- N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY}
- N8N_RUNNERS_MODE=external
- N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}
- N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0
- OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS=true
volumes:
- n8n_storage:/home/node/.n8n
depends_on:
redis:
condition: service_healthy
postgres:
condition: service_healthy
x-runner: &runner
restart: always
image: n8nio/runners:${N8N_VERSION}
environment:
- N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}
services:
postgres:
image: postgres:18
restart: always
environment:
- POSTGRES_USER
- POSTGRES_PASSWORD
- POSTGRES_DB
- POSTGRES_NON_ROOT_USER
- POSTGRES_NON_ROOT_PASSWORD
# postgres:18 moved its default data dir. Pinning it keeps the volume
# mount stable across major versions.
- PGDATA=/var/lib/postgresql/data
volumes:
- db_storage:/var/lib/postgresql/data
- ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh
healthcheck:
test: ['CMD-SHELL', 'pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
restart: always
volumes:
- redis_storage:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 5s
retries: 10
n8n:
<<: *shared
ports:
- 5678:5678
n8n-runner:
<<: *runner
environment:
- N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}
- N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679
depends_on:
- n8n
n8n-worker:
<<: *shared
command: worker
depends_on:
- n8n
n8n-worker-runner:
<<: *runner
environment:
- N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}
- N8N_RUNNERS_TASK_BROKER_URI=http://n8n-worker:5679
depends_on:
- n8n-workerBa điểm trong file mẫu dễ bị bỏ qua khi tự viết compose:
1. Main và worker dùng chung một anchor &shared. Nhờ vậy N8N_ENCRYPTION_KEY luôn giống nhau. Docs ghi rõ khoá mã hoá của main phải được chia sẻ cho mọi worker và webhook processor; lệch khoá thì worker không giải mã được credential. 2. OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS=true đẩy cả lần bấm chạy thử trong editor sang worker. Giá trị mặc định của biến này là false. 3. Postgres 18 đổi thư mục dữ liệu mặc định, file mẫu ghim PGDATA=/var/lib/postgresql/data để volume không lệch khi nâng bản lớn.
n8n queue mode redis: biến môi trường nào cần đặt?
Bảng dưới lấy từ trang biến môi trường queue mode và database của n8n. Cột "Mặc định" là giá trị docs công bố, không phải giá trị tôi khuyến nghị.
| Biến | Mặc định | Ghi chú vận hành |
|---|---|---|
EXECUTIONS_MODE | không đặt (chế độ thường) | Đặt queue trên main và mọi worker |
QUEUE_BULL_REDIS_HOST / _PORT | localhost / 6379 | Trong compose là tên service redis |
QUEUE_BULL_REDIS_PASSWORD | trống | Nên đặt nếu Redis không nằm trong mạng nội bộ của Docker |
QUEUE_BULL_REDIS_TIMEOUT_THRESHOLD | 10000 ms | Hết ngưỡng này mà Redis vẫn không phản hồi thì n8n thoát |
QUEUE_BULL_REDIS_CLUSTER_NODES | trống | Đặt biến này thì n8n bỏ qua HOST và PORT |
QUEUE_HEALTH_CHECK_ACTIVE | false | Bật để có /healthz và /healthz/readiness |
QUEUE_WORKER_LOCK_DURATION | 60000 ms | Thời gian worker giữ lease một job |
QUEUE_WORKER_STALLED_INTERVAL | 30000 ms | Chu kỳ quét job kẹt, đặt 0 là tắt |
N8N_GRACEFUL_SHUTDOWN_TIMEOUT | 30 giây | Thay cho QUEUE_WORKER_TIMEOUT đã bỏ từ bản 1.22.0 |
N8N_WEBHOOK_RESPONSE_RELAY_SIZE_MAX | 64 MiB | Redis cần khoảng 1,5 lần giá trị này cho mỗi response đang truyền |
DB_POSTGRESDB_POOL_SIZE | 2 | Số kết nối Postgres song song mỗi tiến trình |
DB_POSTGRESDB_CONNECTION_TIMEOUT | 20000 ms | Hết hạn khi chờ kết nối |
Giới hạn N8N_WEBHOOK_RESPONSE_RELAY_SIZE_MAX cũng áp cho kết quả tool của node MCP Trigger. Nếu bạn dùng n8n làm MCP server (xem MCP là gì), tool trả về file lớn sẽ chạm trần này trước.
n8n worker scaling: concurrency bao nhiêu là vừa?
Docs công bố ba dữ kiện về concurrency:
- Mặc định mỗi worker chạy
10job song song. - n8n khuyến nghị đặt concurrency từ
5trở lên. - Đặt concurrency thấp nhưng chạy nhiều worker có thể làm cạn pool kết nối database, gây chậm và lỗi.
Đổi concurrency bằng tham số của lệnh worker. Cú pháp lấy từ docs queue mode, 3A chưa chạy thử:
# Chưa chạy thử. Ghi đè command của service n8n-worker
n8n worker --concurrency=5Chỗ tôi muốn đo nhất là khoảng cách giữa 10 job mỗi worker và pool Postgres mặc định 2 kết nối. Docs không nêu 10 job tranh nhau 2 kết nối thì độ trễ ghi execution tăng bao nhiêu, nên bài không suy ra con số nào. Cách tự kiểm số kết nối đang dùng nằm ở mục cuối.
Thêm worker thì phải thêm cả runner. Trang task runner của n8n ghi: "When using Queue mode, each worker needs to have its own sidecar container for task runners." File mẫu chỉ có 1 cặp n8n-worker và n8n-worker-runner, runner trỏ cố định tới http://n8n-worker:5679. Vì vậy docker compose up -d --scale n8n-worker=3 sẽ tạo 3 worker nhưng vẫn chỉ có 1 runner, trái với yêu cầu trên. Docs không nêu worker thiếu runner sẽ báo lỗi gì, bài không suy đoán. Cách đúng theo docs là khai báo từng cặp worker và runner thành service riêng. Khối dưới do tôi viết theo mẫu của file gốc, chưa chạy thử:
# Chưa chạy thử
n8n-worker-2:
<<: *shared
command: worker
depends_on:
- n8n
n8n-worker-runner-2:
<<: *runner
environment:
- N8N_RUNNERS_AUTH_TOKEN=${RUNNERS_AUTH_TOKEN}
- N8N_RUNNERS_TASK_BROKER_URI=http://n8n-worker-2:5679
depends_on:
- n8n-worker-2Cũng theo trang đó, nếu đặt OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS=false thì main tự chạy lần chạy thử và cần runner riêng. Docs không khuyến nghị tắt offload khi chạy production.
Tự đo self host n8n queue mode trên máy của bạn như thế nào?
Bài không có bảng thông lượng. Trang Measure performance của n8n có 2 benchmark mẫu chạy trên máy AWS c5a và MySQL. Cấu hình này khác file compose mẫu (Postgres, 1 worker), và ngoài n8n chưa có nguồn thứ hai đo lại, nên bài không chép số. Dưới đây là 4 lệnh để bạn tự xem cụm của mình, kèm ngưỡng đối chiếu lấy từ docs. Các lệnh này chưa chạy trên cụm thật của 3A.
# Chưa chạy thử trên cụm của 3A
# 1. RAM và CPU từng container, chạy trong lúc có tải
docker stats --no-stream
# 2. Số kết nối Postgres đang mở
docker compose exec postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" \n -c "SELECT count(*) FROM pg_stat_activity WHERE datname = current_database();"
# 3. RAM Redis đang dùng
docker compose exec redis redis-cli INFO memory | grep used_memory_human
# 4. Worker đã nối được Postgres và Redis chưa (cần QUEUE_HEALTH_CHECK_ACTIVE=true)
docker compose exec n8n-worker node -e \n "fetch('http://localhost:5678/healthz/readiness').then(r => console.log(r.status))"| Lệnh | Chỉ số cần xem | Ngưỡng đối chiếu theo docs |
|---|---|---|
docker stats | Cột MEM USAGE của 6 container | Docs không công bố mức RAM cho cụm này. Ghi lại để so giữa các lần đổi cấu hình |
pg_stat_activity | Số kết nối đang mở | DB_POSTGRESDB_POOL_SIZE (mặc định 2) nhân số tiến trình n8n. Docs cảnh báo nhiều worker concurrency thấp có thể làm cạn pool |
redis-cli INFO memory | used_memory_human | Mỗi response đang truyền cần khoảng 1,5 lần N8N_WEBHOOK_RESPONSE_RELAY_SIZE_MAX, mặc định 64 MiB nên tới 96 MiB |
/healthz/readiness trên worker | Mã HTTP | Docs: endpoint của worker server, báo kết nối DB và Redis của worker đã sẵn sàng. Cổng mặc định QUEUE_HEALTH_CHECK_PORT là 5678 |
Nếu bạn muốn tự đo thông lượng và p50/p90/p99 của độ trễ hàng đợi, có thể dùng khung dưới đây. Đây là cấu hình gợi ý, 3A chưa chạy khung này nên bài không có kết quả nào đi kèm:
# Chưa chạy thử
Region : AWS ap-southeast-1 (Singapore)
Phiên bản : n8n 2.43.7, postgres:18, redis:7-alpine (theo compose mẫu)
Máy bắn tải : 1 VM riêng cùng region, k6 gọi webhook
Workflow mẫu : Webhook -> Code (JSON 2 KB) -> Postgres INSERT -> Respond
Cỡ mẫu : N = 30 lượt cho mỗi cấu hình, mỗi lượt 1.000 request
Biến thay đổi : số worker {1, 2, 4} x concurrency {5, 10} x POOL_SIZE {2, 10}
Chỉ số : executions/phút; độ trễ hàng đợi p50 / p90 / p99; tỉ lệ lỗiGiới hạn của phép đo và rủi ro vận hành
Cảnh báo kỹ thuật
Giới hạn áp dụng. Mọi số trong bài này là giá trị mặc định do n8n công bố, không phải kết quả đo. Bài không có số thông lượng hay độ trễ. Đừng dùng bài này để ước tính số VPS cần mua.
Giới hạn của phép đo khi bạn tự đo theo khung trên:
- Workflow mẫu chỉ có 4 node. Workflow gọi API ngoài (OpenAI, Zalo, Google Sheets) bị chặn bởi độ trễ mạng của bên thứ ba, nên kết quả sẽ khác.
- Đo trong cùng region ap-southeast-1. Người dùng gọi webhook từ Việt Nam sẽ cộng thêm độ trễ mạng từ Việt Nam sang Singapore, chưa đo.
- N = 30 lượt đủ để ước lượng p50 và p90. Với p99, mỗi lượt cần ít nhất vài trăm mẫu thì đuôi phân phối mới ổn định, nên mỗi lượt bắn 1.000 request.
Rủi ro và chi phí ẩn khi chạy production:
| Rủi ro | Nguồn | Hậu quả | Cách giảm |
|---|---|---|---|
Binary data ở chế độ filesystem | Docs: n8n không hỗ trợ queue mode với binary data lưu filesystem | Worker không đọc được file do main ghi | Dùng S3 (docs khuyến nghị) hoặc database |
| Response lớn đi qua Redis | Docs: cần khoảng 1,5 lần RELAY_SIZE_MAX mỗi response | Mặc định 64 MiB thì một response tốn tới 96 MiB RAM Redis | Giảm N8N_WEBHOOK_RESPONSE_RELAY_SIZE_MAX hoặc bật offload (từ 2.34.0) |
| Redis mất kết nối | Docs: quá QUEUE_BULL_REDIS_TIMEOUT_THRESHOLD thì n8n thoát | Main và worker cùng dừng | restart: always, healthcheck, Redis có persistence |
| Lệch phiên bản giữa main và worker | Docs multi-main yêu cầu cùng phiên bản | Lỗi khó đoán sau khi nâng cấp từng phần | Ghim N8N_VERSION trong .env, nâng cấp cả cụm |
| Multi-main | Docs: chỉ có ở bản Enterprise tự host | Bản community chỉ có 1 main, main chết thì UI và trigger dừng | Chấp nhận 1 main, giám sát /healthz |
| Màn hình Settings > Workers | Docs: Enterprise mới xem được trên bản tự host | Bản community không thấy worker nào đang sống | Dùng /healthz và /metrics của worker |
Chi phí ẩn dễ quên nhất là RAM. So với chạy một tiến trình, file mẫu queue mode thêm 1 Postgres, 1 Redis, 1 worker và 2 runner, tức 5 container. Docs không công bố mức RAM cho cụm 6 container này. Cách tự đo: chạy docker stats --no-stream mỗi 10 giây trong lúc có tải, rồi lấy p90 của cột MEM USAGE.
Khi nào chưa nên bật n8n queue mode?
Queue mode giải quyết một việc: tách việc nhận trigger khỏi việc chạy workflow để thêm worker khi tải tăng. Nếu một tiến trình n8n đang chạy ổn và bạn chưa thấy execution xếp hàng, chi phí vận hành thêm Redis, Postgres và các cặp worker-runner lớn hơn lợi ích.
Ba dấu hiệu nên chuyển sang queue mode, đều kiểm được trên máy đang chạy:
1. Webhook trả về chậm khi có workflow nặng chạy cùng lúc. 2. Editor chậm hoặc treo trong giờ cao điểm. 3. Bạn cần khởi động lại tiến trình chạy workflow mà không làm gián đoạn trigger.
Nếu chưa có cả ba dấu hiệu, hãy chuyển từ SQLite sang Postgres trước. Bước này cũng là điều kiện bắt buộc của queue mode, làm trước thì lúc bật queue mode chỉ còn thêm Redis và worker.
Nguồn tham khảo
Nguồn tham khảo
Ghi chú
Cả 7 liên kết chính thức đều được kiểm tra phản hồi HTTP 200 lúc 11h10 ngày 19/10/2026.
| Tài liệu / Nguồn | Nội dung đối chiếu | Liên kết chính hãng |
|---|---|---|
| n8n Docs | Enable queue mode | scaling/enable-queue-mode |
| n8n Docs | Queue mode environment variables | basic-config/queue-mode |
| n8n Docs | Database environment variables | basic-config/database |
| n8n Docs | Set up task runners | set-up-task-runners |
| n8n Docs | Measure performance | scaling/measure-performance |
| GitHub n8n-hosting | File Compose mẫu withPostgresAndWorker | docker-compose.yml |
| GitHub n8n-io/n8n | Bản phát hành mới nhất n8n@2.43.7 | n8n@2.43.7 |
- Quang Minh, từ phòng đo kiểm 3A.



