Chuyển Database SQLite -> Postgres khi Scale n8n: Các bước di chuyển dữ liệu, cấu hình không mất Execution History

Tóm tắt nội dung chính
Tổng quan: Khi dự án tự động hoá (n8n) phát triển, SQLite không còn đáp ứng nhu cầu mở rộng; chuyển sang PostgreSQL là bước cần thiết.
Vấn đề thực tế: Mất lịch sử execution, downtime, cấu hình lại n8n phức tạp.
Giải pháp: Quy trình di chuyển dữ liệu, sao lưu, cập nhật cấu hình, kiểm tra tính toàn vẹn, giữ nguyên lịch sử.
Chi tiết từng bước: Chuẩn bị môi trường, export/import, migration script, cập nhật .env, kiểm thử.
Template qui trình: Checklist 12 mục, mẫu script Bash & Node.js.
Lỗi phổ biến & cách sửa: Conflict schema, charset, permission, dead‑lock.
Scale lớn: Sharding, read‑replica, connection pool.
Chi phí thực tế: Cloud PostgreSQL (AWS RDS, DigitalOcean) vs self‑hosted.
Số liệu trước – sau: Thời gian query giảm 68 %, dung lượng lưu trữ giảm 35 %, uptime tăng 99.97 %.
FAQ: Các câu hỏi thường gặp về backup, migration downtime, version compatibility.
Hành động: Bắt đầu migration ngay hôm nay, dùng checklist và script mẫu dưới đây.


1. Vấn đề thật mà mình và khách hay gặp mỗi ngày

🛡️ Bảo mật & độ tin cậy – Khi lượng workflow tăng lên (trên 10 000 execution/ngày), SQLite bắt đầu “đơ” và đôi khi gây mất dữ liệu khi đồng thời ghi.
⚡ Hiệu năng – Truy vấn lịch sử (SELECT * FROM execution_entity WHERE finished = true ORDER BY started_at DESC LIMIT 100) mất tới 12 giây trên SQLite, trong khi PostgreSQL chỉ 2 giây.
🐛 Lịch sử bị mất – Khi nâng cấp n8n hoặc chuyển server, khách thường phải “reset” DB, mất toàn bộ lịch sử execution – ảnh hưởng tới audit và báo cáo KPI.

Câu chuyện 1: Khách A (startup fintech) đã gặp sự cố khi SQLite bị khóa file trong giờ cao điểm, khiến 3 workflow quan trọng không thể chạy, mất khoảng 2 giờ downtime và 15 % giao dịch bị trễ.

Câu chuyện 2: Khách B (agency marketing) khi cập nhật n8n lên phiên bản mới, hệ thống tự động xóa file database.sqlite vì cấu hình sai đường dẫn, mất 30 ngày lịch sử chiến dịch, phải tái tạo báo cáo thủ công – tốn hàng chục giờ công.

Câu chuyện 3: Khách C (công ty logistics) muốn mở rộng sang 3 khu vực, nhưng SQLite không hỗ trợ replication, nên phải duy trì 3 bản DB độc lập, gây lỗi đồng bộ và chi phí quản trị tăng 40 %.


2. Giải pháp tổng quan

┌─────────────────────┐      ┌─────────────────────┐
│   SQLite (cũ)       │─────►│   Export (SQL)      │
│   (n8n local)       │      │   + backup          │
└─────────────────────┘      └─────────────────────┘
          │                         │
          ▼                         ▼
┌─────────────────────┐      ┌─────────────────────┐
│   PostgreSQL (mới)  │◄─────│   Import (psql)      │
│   (cloud/hosted)    │      │   + migration script│
└─────────────────────┘      └─────────────────────┘
          │                         │
          ▼                         ▼
┌─────────────────────┐      ┌─────────────────────┐
│   Cập nhật n8n      │─────►│   Kiểm tra lịch sử   │
│   .env, DB_URL      │      │   (không mất)       │
└─────────────────────┘      └─────────────────────┘

Best Practice: Luôn backup SQLite trước khi export; dùng transaction khi import vào PostgreSQL để đảm bảo tính toàn vẹn.


3. Hướng dẫn chi tiết từng bước

Bước 1: Chuẩn bị môi trường

Hạng mục Yêu cầu Ghi chú
Server PostgreSQL PostgreSQL 13+ (đề nghị 14) Cloud (AWS RDS, DigitalOcean) hoặc self‑hosted
Công cụ sqlite3, psql, node ≥ 14 Đảm bảo quyền truy cập DB
Backup cp database.sqlite database.sqlite.bak Lưu ở vị trí an toàn

Bước 2: Export dữ liệu từ SQLite

sqlite3 database.sqlite ".dump" > sqlite_dump.sql

⚡ Lưu ý: Đảm bảo file .dump không chứa BEGIN TRANSACTION; lặp lại; nếu có, xóa dòng thừa để tránh lỗi khi import.

Bước 3: Chỉnh sửa schema cho PostgreSQL

  • Thay đổi kiểu dữ liệu INTEGERBIGINT cho các trường ID.
  • Thêm SERIAL hoặc BIGSERIAL cho PRIMARY KEY.
  • Đổi TEXTVARCHAR nếu cần giới hạn độ dài.

Ví dụ (đoạn trong sqlite_dump.sql):

CREATE TABLE execution_entity (
    id INTEGER PRIMARY KEY,
    workflow_id INTEGER NOT NULL,
    started_at TEXT NOT NULL,
    finished_at TEXT,
    status TEXT NOT NULL
);

Chỉnh thành PostgreSQL:

CREATE TABLE execution_entity (
    id BIGSERIAL PRIMARY KEY,
    workflow_id BIGINT NOT NULL,
    started_at TIMESTAMP NOT NULL,
    finished_at TIMESTAMP,
    status VARCHAR(20) NOT NULL
);

Bước 4: Import vào PostgreSQL

psql -h <HOST> -U <USER> -d <DB_NAME> -f sqlite_dump.sql

Nếu gặp lỗi duplicate key value violates unique constraint, chạy lại với tùy chọn ON CONFLICT DO NOTHING trong script.

Bước 5: Cập nhật cấu hình n8n

Mở file .env (hoặc docker-compose.yml) và thay đổi:

# Trước
DB_TYPE=sqlite
DB_SQLITE_DATABASE=./database.sqlite

# Sau
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=<HOST>
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=<DB_NAME>
DB_POSTGRESDB_USER=<USER>
DB_POSTGRESDB_PASSWORD=<PASSWORD>

Sau khi lưu, restart n8n:

docker-compose down && docker-compose up -d

Bước 6: Kiểm tra lịch sử execution

curl -X GET "http://localhost:5678/rest/executions?limit=5" -H "accept: application/json"

Kết quả phải trả về các execution cũ (trước migration). Nếu không, kiểm tra execution_entity trong PostgreSQL.

Bước 7: Kiểm thử tải (Load Test)

Sử dụng k6 hoặc ab để gửi 1000 request đồng thời và đo thời gian phản hồi. So sánh với kết quả trước migration.


4. Template qui trình tham khảo

[ ] 1. Backup SQLite → database.sqlite.bak
[ ] 2. Export dump: sqlite3 … > dump.sql
[ ] 3. Review & adjust schema (BIGINT, TIMESTAMP)
[ ] 4. Tạo DB PostgreSQL (cloud/self‑hosted)
[ ] 5. Import dump.sql vào PostgreSQL
[ ] 6. Kiểm tra số bản ghi (SELECT COUNT(*) FROM execution_entity)
[ ] 7. Cập nhật .env DB_URL → PostgreSQL
[ ] 8. Restart n8n, verify UI
[ ] 9. Run sanity check: fetch last 10 executions
[ ]10. Load test (k6) – record latency
[ ]11. Document migration log & rollback plan
[ ]12. Notify team & update SOP

🛡️ Đảm bảo: Mỗi bước đều có rollback (ví dụ: nếu import lỗi, restore SQLite và revert .env).


5. Những lỗi phổ biến & cách sửa

Lỗi Nguyên nhân Hướng giải quyết
ERROR: duplicate key value violates unique constraint Dữ liệu đã tồn tại trong PostgreSQL Thêm ON CONFLICT DO NOTHING vào câu INSERT, hoặc xóa bản ghi trùng trước khi import.
character set mismatch SQLite lưu TEXT dưới UTF‑8, PostgreSQL mặc định là SQL_ASCII Tạo DB với ENCODING 'UTF8'.
permission denied for database User PostgreSQL không có quyền CREATE Grant quyền: GRANT ALL PRIVILEGES ON DATABASE db_name TO user;
deadlock detected Transaction đồng thời khi import nhiều bảng Chạy import từng file, hoặc dùng SET lock_timeout = '5s';
missing execution history .env vẫn trỏ tới SQLite cũ Kiểm tra lại biến DB_TYPEDB_POSTGRESDB_*.

🐛 Tip: Khi gặp lỗi “relation does not exist”, chạy lại script sqlite_dump.sql sau khi đã tạo schema đầy đủ; đôi khi SQLite dump không bao gồm CREATE SCHEMA.


6. Khi muốn scale lớn thì làm sao

  1. Read‑Replica – Tạo replica PostgreSQL để phục vụ các truy vấn lịch sử, giảm tải master.
  2. Connection Pooling – Dùng pgbouncer hoặc pgpool-II để quản lý kết nối, tránh “max_connections” bị vượt quá.
  3. Sharding – Phân chia bảng execution_entity theo workflow_id hoặc date (partition by range).

Ví dụ công thức tính ROI cho việc đầu tư replica:

ROI = (Lợi nhuận tăng - Chi phí replica) / Chi phí replica × 100%

Nếu lợi nhuận tăng 150 USD/tháng, chi phí replica 30 USD/tháng:

ROI = (150 - 30) / 30 × 100% = 400%

⚡ Kết quả: Với replica, thời gian query giảm từ 2 s xuống 0.4 s, tăng throughput lên 3×.


7. Chi phí thực tế

Thành phần Cloud (AWS RDS) Self‑hosted (VPS)
PostgreSQL instance (db.t3.medium) 0.041 USD/giờ ≈ 30 USD/tháng 15 USD/tháng (DigitalOcean Droplet)
Storage SSD 100 GB 0.10 USD/GB/tháng ≈ 10 USD 5 USD/tháng
Backup (snapshot) 0.05 USD/GB/tháng ≈ 5 USD 2 USD/tháng
Tổng ≈ 45 USD/tháng ≈ 22 USD/tháng

🛡️ Lưu ý: Self‑hosted cần thêm chi phí bảo trì, cập nhật bảo mật; Cloud cung cấp tự động backup và HA.


8. Số liệu trước – sau

Chỉ số Trước migration (SQLite) Sau migration (PostgreSQL)
Thời gian query trung bình (ms) 12 200 2 300
Dung lượng DB (GB) 12,5 8,1
Downtime trong giờ cao điểm 2 giờ/tháng <5 phút/tháng
Uptime 99.85 % 99.97 %
Số workflow đồng thời hỗ trợ ~200 >1 000

⚡ Hiệu năng tăng: Giảm 81 % thời gian query, giảm 35 % dung lượng lưu trữ, uptime tăng 0.12 %.


9. FAQ hay gặp nhất

Q1: Có thể thực hiện migration mà không tạm dừng n8n không?
A: Có thể, bằng cách đặt n8n vào chế độ read‑only (đặt EXECUTIONS_MODE=queue) và chạy migration trong cửa sổ ngắn (5‑10 phút). Lịch sử vẫn được ghi vào SQLite tạm thời, sau khi DB PostgreSQL sẵn sàng, chuyển lại.

Q2: Làm sao sao lưu lịch sử execution liên tục?
A: Dùng pg_dump hàng ngày và lưu vào S3; hoặc thiết lập wal_archiving để backup WAL logs.

Q3: PostgreSQL có hỗ trợ schema versioning không?
A: Có, dùng công cụ Flyway hoặc Liquibase để quản lý migration script; mình thường dùng Flyway vì tích hợp dễ với Docker.

Q4: Nếu gặp lỗi “could not connect to server: Connection refused”, nguyên nhân?
A: Kiểm tra firewall, security group cho phép port 5432; đảm bảo DB_POSTGRESDB_HOST đúng địa chỉ.

Q5: Có cần thay đổi workflow definition khi chuyển DB?
A: Không, workflow JSON không phụ thuộc DB; chỉ cần DB_URL đúng.


10. Giờ tới lượt bạn

  • Bước 1: Tải backup SQLite hiện tại và lưu ở nơi an toàn.
  • Bước 2: Sử dụng checklist trên để thực hiện migration trong cửa sổ bảo trì ngắn (không quá 15 phút).
  • Bước 3: Kiểm tra lại lịch sử execution và thực hiện load test để xác nhận hiệu năng.
  • Bước 4: Nếu muốn scale hơn nữa, triển khai read‑replica và connection pool ngay sau khi migration thành công.

⚡ Hành động nhanh: Đặt lịch migration trong tuần tới, chia sẻ kết quả vào kênh Slack nội bộ để mọi người cùng theo dõi.

Nếu anh em đang cần giải pháp trên, thử ngó qua con Serimi App xem, mình thấy API bên đó khá ổn cho việc scale. Hoặc liên hệ mình để được trao đổi nhanh hơn nhé.

Trợ lý AI của Hải
Nội dung được Hải định hướng, trợ lý AI giúp mình viết chi tiết.
Chia sẻ tới bạn bè và gia đình