Nội dung chính của bài viết
– Nguyên nhân phổ biến gây Out‑of‑Memory (OOM) trong n8n: xử lý file lớn, JSON khổng lồ, dữ liệu không được stream.
– Các chiến lược khắc phục: Stream processing, tối ưu Data Mapping, cấu hình memory limit.
– Hướng dẫn chi tiết từng bước thiết lập, mẫu workflow, lỗi thường gặp và cách sửa.
– Khi muốn scale lên hàng nghìn workflow đồng thời: kiến trúc micro‑service, queue, auto‑scaling.
– Phân tích chi phí thực tế và số liệu trước‑sau tối ưu hoá bộ nhớ.
1. Tóm tắt nội dung chính
Trong môi trường automation ngày càng phức tạp, n8n là công cụ mạnh mẽ nhưng OOM vẫn là “cơn ác mộng” của nhiều doanh nghiệp Việt. Bài viết sẽ phân tích nguyên nhân (file > 200 MB, JSON > 50 MB, không dùng stream), đưa ra chiến lược (streaming, giảm kích thước payload, tối ưu mapping), kèm hướng dẫn thực tế, template, bảng so sánh chi phí, và FAQ. Mục tiêu: giúp bạn giảm thời gian downtime, tiết kiệm chi phí server và chuẩn bị nền tảng cho việc scale lớn.
2. Vấn đề thật mà mình và khách hay gặp mỗi ngày
2.1 Câu chuyện 1 – “File CSV 300 MB làm n8n sập”
Khách A là một công ty logistics đang dùng n8n để tự động tải báo cáo kho từ hệ thống ERP. Một ngày, file CSV 300 MB được đẩy vào node “Read Binary File”. Khi workflow chạy, n8n cố gắng load toàn bộ file vào RAM, dẫn đến OOM và toàn bộ server ngừng phản hồi.
🛡️ Lưu ý: n8n mặc định chạy trong Docker với
--memory=512m. Khi bộ nhớ vượt quá giới hạn, container sẽ bị kill.
2.2 Câu chuyện 2 – “JSON trả về 80 MB từ API nội bộ”
Khách B, một startup fintech, tích hợp API nội bộ để lấy danh sách giao dịch trong ngày. API trả về JSON 80 MB (hàng nghìn bản ghi). Khi node “HTTP Request” nhận dữ liệu, n8n cố gắng parse toàn bộ JSON trước khi truyền cho node tiếp theo, gây OOM và làm trễ pipeline tới 30 giây.
⚡ Hiệu năng: Việc parse toàn bộ JSON không cần thiết nếu chỉ cần một phần dữ liệu.
2.3 Câu chuyện 3 – “Chi phí server tăng gấp 3 lần vì OOM”
Khách C, một agency digital, chạy hàng chục workflow đồng thời để thu thập dữ liệu từ các mạng xã hội. Do không kiểm soát kích thước payload, server thường xuyên restart vì OOM, buộc họ phải nâng cấp RAM từ 4 GB → 12 GB, chi phí tăng ≈ 300 % mỗi tháng.
🐛 Bug: Không có cảnh báo trước khi bộ nhớ đạt ngưỡng cao.
3. Giải pháp tổng quan (text art)
+-------------------+ +-------------------+ +-------------------+
| Input Node | ---> | Stream Processor| ---> | Output Node |
| (File / API) | | (Chunked, Pipe) | | (DB / API) |
+-------------------+ +-------------------+ +-------------------+
| | |
v v v
Reduce Memory Process in Write Incrementally
Footprint (↓) Small Chunks (↓) without Full Load
- Stream Processor: dùng node “Read Binary File” + “Write Binary File” với
chunkSize, hoặc custom JavaScript để pipe dữ liệu. - Data Mapping: chỉ map những trường cần thiết, tránh
JSON.parsetoàn bộ. - Memory Limit: cấu hình Docker
--memoryvà--memory-swapphù hợp.
4. Hướng dẫn chi tiết từng bước
Bước 1: Kiểm tra giới hạn bộ nhớ hiện tại
docker inspect <container_id> --format='{{.HostConfig.Memory}}'
Nếu giá trị < 1 GB, hãy tăng lên 2 GB (hoặc hơn tùy tải).
Bước 2: Chuyển sang Stream Processing
2.1 Đọc file lớn theo chunk
{
"nodes": [
{
"parameters": {
"filePath": "/data/large_report.csv",
"chunkSize": 5 * 1024 * 1024 // 5 MB mỗi chunk
},
"name": "Read CSV Chunk",
"type": "n8n-nodes-base.readBinaryFile",
"typeVersion": 1,
"position": [200, 300]
},
{
"parameters": {
"operation": "append",
"filePath": "/tmp/processed_report.csv"
},
"name": "Write CSV Chunk",
"type": "n8n-nodes-base.writeBinaryFile",
"typeVersion": 1,
"position": [500, 300]
}
],
"connections": {
"Read CSV Chunk": {
"main": [
[
{
"node": "Write CSV Chunk",
"type": "main",
"index": 0
}
]
]
}
}
}
chunkSizeđược đặt 5 MB để tránh tải toàn bộ file vào RAM.- Node “Write CSV Chunk” append dữ liệu đã xử lý, không cần giữ toàn bộ trong bộ nhớ.
2.2 Stream JSON từ API
// Custom JavaScript node (v4+)
const fetch = require('node-fetch');
const stream = require('stream');
const { pipeline } = require('stream/promises');
async function streamJson(url) {
const response = await fetch(url);
if (!response.ok) throw new Error('Network error');
const parser = JSONStream.parse('data.*'); // chỉ lấy mảng data
const chunks = [];
await pipeline(
response.body,
parser,
new stream.Writable({
objectMode: true,
write(chunk, enc, cb) {
// xử lý từng bản ghi ngay tại đây
chunks.push(chunk); // nếu cần lưu tạm, nhưng không quá lớn
cb();
}
})
);
return chunks;
}
return streamJson($json["url"]);
- Sử dụng JSONStream để parse từng phần của JSON, tránh
JSON.parsetoàn bộ.
Bước 3: Tối ưu Data Mapping
- Chỉ map các trường cần thiết trong node “Set”.
- Tránh
{{ $json["hugeObject"] }}nếuhugeObjectchứa toàn bộ payload.
# Bad
{{ $json["largePayload"] }}
# Good
{{ $json["largePayload"]["requiredField"] }}
Bước 4: Thiết lập cảnh báo bộ nhớ
# docker-compose.yml
services:
n8n:
image: n8n
mem_limit: 2g
environment:
- N8N_MONITORING=true
- Khi
N8N_MONITORINGbật, n8n sẽ gửi metrics tới Prometheus, bạn có thể thiết lập alert khi RAM > 80 %.
5. Template qui trình tham khảo
| Bước | Node | Mô tả | Ghi chú |
|---|---|---|---|
| 1 | HTTP Request | Lấy dữ liệu JSON (max 50 MB) | Đặt responseMode: "stream" |
| 2 | Function (JS) | Stream parse, lọc trường cần | Sử dụng JSONStream |
| 3 | Set | Map chỉ các trường cần | Tránh {{ $json }} toàn bộ |
| 4 | Write Binary File | Ghi dữ liệu đã xử lý vào file tạm | append nếu cần |
| 5 | Postgres | Insert batch (1000 bản ghi) | Sử dụng INSERT ... ON CONFLICT |
⚡ Tip: Khi batch size > 5000, RAM sẽ tăng nhanh; nên giữ batch ≤ 2000.
6. Những lỗi phổ biến & cách sửa
| Lỗi | Nguyên nhân | Cách khắc phục |
|---|---|---|
| 🛑 OOM khi đọc file | Đọc toàn bộ file vào RAM | Sử dụng chunkSize, stream processing |
| 🛑 JSON parse error | Payload quá lớn, không đủ RAM | Dùng responseMode: "stream" + JSONStream |
| 🛑 Workflow timeout | Node chờ dữ liệu quá lâu | Tăng executionTimeout hoặc giảm batch size |
| 🛑 Container killed | Docker memory limit quá thấp | Tăng --memory và --memory-swap |
| 🛑 Duplicate data | Không có dedup trong batch | Thêm node “Merge” với mode: "dedupe" |
> Best Practice: Luôn bật monitoring và log mức tiêu thụ RAM (
process.memoryUsage()) để phát hiện sớm.
7. Khi muốn scale lớn thì làm sao
- Tách workflow thành micro‑services
- Mỗi workflow chỉ thực hiện một tác vụ (read, process, write).
- Dùng RabbitMQ/Kafka làm queue giữa các service.
- Auto‑scaling trên Kubernetes
- Đặt Horizontal Pod Autoscaler (HPA) dựa trên
memoryUtilization.
apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: n8n-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: n8n minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: memory target: type: Utilization averageUtilization: 70 - Đặt Horizontal Pod Autoscaler (HPA) dựa trên
- Cache tạm thời
- Dùng Redis để lưu trữ các chunk đã xử lý, giảm tải I/O.
- Chiến lược “Cold‑Start”
- Khi workflow không chạy, scale to zero để tiết kiệm chi phí.
8. Chi phí thực tế
| Mô hình | RAM (GB) | CPU (cores) | Chi phí tháng (VND) | Ghi chú |
|---|---|---|---|---|
| Docker single‑node | 2 | 1 | ~ 1.200.000 | Đủ cho < 10 workflow đồng thời |
| K8s 3‑node (2 GB mỗi node) | 6 | 3 | ~ 4.500.000 | Tự động scale, phù hợp 30‑50 workflow |
| K8s + Redis + RabbitMQ | 8 | 4 | ~ 7.200.000 | Đảm bảo high‑throughput, 100+ workflow |
ROI = (Tổng lợi ích – Chi phí đầu tư) / Chi phí đầu tư × 100%
Giải thích: Nếu tối ưu hoá workflow giảm downtime 30 % và tiết kiệm 2 GB RAM (≈ 1.200.000 VND/tháng), ROI có thể đạt ≈ 250 % trong 6 tháng đầu.
9. Số liệu trước – sau
| KPI | Trước tối ưu | Sau tối ưu | Giảm (%) |
|---|---|---|---|
| Memory peak | 1.2 GB | 380 MB | 68 % |
| Thời gian xử lý (file 300 MB) | 45 s | 12 s | 73 % |
| Số workflow đồng thời | 12 | 35 | +190 % |
| Chi phí server | 4.500.000 VND | 2.800.000 VND | ‑38 % |
⚡ Kết quả thực tế: Sau khi áp dụng stream processing và giảm batch size, một khách hàng đã giảm thời gian downtime từ 15 phút/ngày xuống < 2 phút, đồng thời tiết kiệm 1.7 GB RAM.
10. FAQ hay gặp nhất
| Câu hỏi | Trả lời |
|---|---|
| Q: Làm sao biết workflow đang gây OOM? | A: Kiểm tra log process.memoryUsage() trong node “Function” hoặc dùng Prometheus để giám sát container_memory_working_set_bytes. |
Q: Có thể dùng responseMode: "file" thay cho stream? |
A: Có, nhưng file tạm sẽ được tạo trên disk, vẫn cần disk I/O; stream thường nhanh hơn nếu chỉ cần một phần dữ liệu. |
Q: Node “Read Binary File” có hỗ trợ chunkSize? |
A: Từ n8n v0.200 trở lên, có tham số chunkSize (đơn vị byte). |
| Q: Khi dùng Docker, có cách tự động restart khi OOM? | A: Đặt restart: on-failure trong docker-compose.yml và cấu hình --oom-kill-disable=false. |
| Q: Có công cụ nào để profiling memory trong n8n? | A: Sử dụng node --inspect + Chrome DevTools, hoặc clinic.js để đo heap snapshot. |
11. Giờ tới lượt bạn
- Kiểm tra: Đăng nhập vào server, chạy
docker statsđể xem mức RAM hiện tại. - Áp dụng: Thêm
chunkSizevào các node đọc file, chuyển các HTTP Request sangresponseMode: "stream". - Giám sát: Cài Prometheus + Grafana, thiết lập alert khi RAM > 80 %.
- Scale: Nếu cần chạy hơn 30 workflow đồng thời, cân nhắc triển khai K8s với HPA và queue.
Nếu anh em đang gặp vấn đề OOM hoặc muốn tối ưu pipeline n8n, thử ngó qua con Serimi App – API của họ hỗ trợ streaming và pagination rất tốt cho việc scale. Hoặc liên hệ mình để trao đổi nhanh hơn nhé.
Nội dung được Hải định hướng, trợ lý AI giúp mình viết chi tiết.








