3A.COM.VN

Agent & Tự Động Hóa

Self host n8n queue mode: cấu hình PostgreSQL, Redis và worker với 6 container

Self host n8n queue mode theo file mẫu chính hãng cần 6 container. Mỗi worker mặc định chạy 10 job song song nhưng pool Postgres mặc định chỉ 2 kết nối. Bài không có số đo thông lượng hay p50/p90/p99, chỉ dẫn lệnh để bạn tự đo.

Sản phẩm trong bài:
n8nPostgreSQL
Quang Minh· Trưởng ban Nghiên cứu & Đo kiểm AI12 phút đọc20/10/20260 lượt xem
Self host n8n queue mode: cấu hình PostgreSQL, Redis và worker với 6 container
n8nPostgreSQL

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](/bai-viet/xay-agent-cham-soc-khach-hang-voi-n8n), không lặp lại ở đây.

Dự án nội bộ · 3A AI Stack Advisor

Đừ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ầnVai tròBắt buộc?Nguồn
PostgreSQL
Lưu workflow, credential, lịch sử executionCó. 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
RedisHàng đợi job (Bull)CóDocs queue mode
Main (`n8n`)
Giao diện, trigger, đưa job vào hàng đợiCóDocs queue mode
Worker (`n8n worker`)
Chạy executionCó, từ 1 trở lênDocs queue mode
Webhook processor (`n8n webhook`)
Nhận `/webhook/*` sau load balancerKhông, dùng khi webhook nhiềuDocs queue mode
Task runner (`n8nio/runners`)
Chạy node Code ở tiến trình riêngCó trong file mẫu chính hãngCompose 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.

yaml
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-worker

Ba đ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ếnMặc địnhGhi 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ốngNên đặt nếu Redis không nằm trong mạng nội bộ của Docker
`QUEUE_BULL_REDIS_TIMEOUT_THRESHOLD``10000` msHế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` msThời gian worker giữ lease một job
`QUEUE_WORKER_STALLED_INTERVAL``30000` msChu kỳ quét job kẹt, đặt `0` là tắt
`N8N_GRACEFUL_SHUTDOWN_TIMEOUT`
`30` giâyThay cho `QUEUE_WORKER_TIMEOUT` đã bỏ từ bản 1.22.0
`N8N_WEBHOOK_RESPONSE_RELAY_SIZE_MAX`
`64` MiBRedis 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` msHế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ì](/bai-viet/mcp-la-gi-huong-dan-cho-nguoi-moi)), 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 `10` job song song.
  • n8n khuyến nghị đặt concurrency từ `5` trở 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ử:

bash
# Chưa chạy thử. Ghi đè command của service n8n-worker
n8n worker --concurrency=5

Chỗ 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ử:

yaml
# 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-2

Cũ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](https://docs.n8n.io/deploy/host-n8n/configure-n8n/scaling/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.

bash
# 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ệnhChỉ số cần xemNgưỡng đối chiếu theo docs
`docker stats`Cột MEM USAGE của 6 containerDocs 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 workerMã HTTPDocs: 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:

text
# 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ỗi

Giớ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 roNguồnHậu quảCách giảm
Binary data ở chế độ `filesystem`Docs: n8n không hỗ trợ queue mode với binary data lưu filesystemWorker không đọc được file do main ghiDùng S3 (docs khuyến nghị) hoặc `database`
Response lớn đi qua RedisDocs: cần khoảng 1,5 lần `RELAY_SIZE_MAX` mỗi responseMặc định 64 MiB thì một response tốn tới 96 MiB RAM RedisGiảm `N8N_WEBHOOK_RESPONSE_RELAY_SIZE_MAX` hoặc bật offload (từ 2.34.0)
Redis mất kết nốiDocs: quá `QUEUE_BULL_REDIS_TIMEOUT_THRESHOLD` thì n8n thoátMain và worker cùng dừng`restart: always`, healthcheck, Redis có persistence
Lệch phiên bản giữa main và workerDocs multi-main yêu cầu cùng phiên bảnLỗi khó đoán sau khi nâng cấp từng phầnGhim `N8N_VERSION` trong `.env`, nâng cấp cả cụm
Multi-mainDocs: chỉ có ở bản Enterprise tự hostBản community chỉ có 1 main, main chết thì UI và trigger dừngChấp nhận 1 main, giám sát `/healthz`
Màn hình Settings > WorkersDocs: Enterprise mới xem được trên bản tự hostBản community không thấy worker nào đang sốngDù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

Cả 7 link dưới đây được mở lại ngày 19/10/2026 lúc 11h10 GMT+7, đều trả HTTP 200.

  • n8n Docs, Enable queue mode: [docs.n8n.io/.../scaling/enable-queue-mode](https://docs.n8n.io/deploy/host-n8n/configure-n8n/scaling/enable-queue-mode) (đọc 19/10/2026)
  • n8n Docs, Queue mode environment variables: [docs.n8n.io/.../use-environment-variables/queue-mode](https://docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/use-environment-variables/queue-mode) (đọc 19/10/2026)
  • n8n Docs, Database environment variables: [docs.n8n.io/.../use-environment-variables/database](https://docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/use-environment-variables/database) (đọc 19/10/2026)
  • n8n Docs, Set up task runners: [docs.n8n.io/.../set-up-task-runners](https://docs.n8n.io/deploy/host-n8n/configure-n8n/set-up-task-runners) (đọc 19/10/2026)
  • n8n Docs, Measure performance: [docs.n8n.io/.../scaling/measure-performance](https://docs.n8n.io/deploy/host-n8n/configure-n8n/scaling/measure-performance) (đọc 19/10/2026)
  • GitHub n8n-io/n8n-hosting, file compose `withPostgresAndWorker`: [docker-compose.yml](https://github.com/n8n-io/n8n-hosting/blob/main/docker-compose/withPostgresAndWorker/docker-compose.yml) (tải 19/10/2026)
  • GitHub n8n-io/n8n, bản phát hành mới nhất lúc viết: [n8n@2.43.7, ngày 16/10/2026](https://github.com/n8n-io/n8n/releases/tag/n8n%402.43.7) (đọc 19/10/2026)

— Quang Minh, từ phòng đo kiểm 3A.

— Quang Minh, từ phòng đo kiểm 3A.

Bài liên quan