T
Trinh Digital
Xây dựng Hệ thống

Database Migration: Chuyển 10M records từ MySQL sang PostgreSQL không downtime

Trinh Digital · · 8 phút đọc

Case study database migration này ghi lại hành trình chúng tôi giúp một công ty logistics chuyển 10 triệu records từ MySQL sang PostgreSQL — và điều quan trọng nhất: zero downtime. Hệ thống vẫn phục vụ 3.000 users đồng thời trong suốt quá trình migration.

Bối cảnh dự án

Khách hàng: Công ty logistics vận chuyển hàng hóa, hoạt động tại 25 tỉnh thành Việt Nam.

Hệ thống:

  • Ứng dụng quản lý vận đơn (Laravel PHP)
  • 3.000 users đồng thời (nhân viên kho, tài xế, khách hàng)
  • 10 triệu vận đơn, 2 triệu khách hàng, 500.000 tuyến đường
  • Database MySQL 5.7 trên VPS 16GB RAM
  • Hoạt động 24/7 (không có “giờ nghỉ” cho maintenance)

Tại sao phải chuyển sang PostgreSQL?

Vấn đề với MySQL 5.7PostgreSQL giải quyết
JSON search chậm (không index được JSON)JSONB với GIN index — query JSON nhanh 50x
Không có partial indexPartial index giảm dung lượng index 60%
Full-text search tiếng Việt kémpg_trgm + unaccent hỗ trợ tốt tiếng Việt
Window functions hạn chếWindow functions đầy đủ cho báo cáo
GIS support yếuPostGIS — tìm kho gần nhất, tính khoảng cách
Concurrent writes lock tableMVCC — ghi không block đọc
End-of-life MySQL 5.7 (10/2023)PostgreSQL 16 — support đến 2028

Yêu cầu:

  • Zero downtime migration
  • Data integrity 100% (không mất record nào)
  • Rollback plan (quay lại MySQL nếu có vấn đề)
  • Hoàn thành trong 2 tuần

Chiến lược: Dual-Write Migration

Chúng tôi chọn chiến lược Dual-Write thay vì Big Bang migration:

Big Bang (không chọn)

MySQL → Stop app → Export data → Import PostgreSQL → Start app
Downtime: 4–8 giờ
Risk: Cao — nếu lỗi phải rollback toàn bộ

Dual-Write (đã chọn)

Phase 1: MySQL (primary) → Replicate → PostgreSQL (shadow)
Phase 2: App ghi vào cả MySQL + PostgreSQL
Phase 3: Đọc từ PostgreSQL, verify với MySQL
Phase 4: PostgreSQL (primary), tắt MySQL
Downtime: 0
Risk: Thấp — rollback bất cứ lúc nào

Quy trình chi tiết: 4 giai đoạn

Giai đoạn 1: Chuẩn bị (3 ngày)

Ngày 1–2: Schema conversion

Chuyển đổi schema MySQL → PostgreSQL:

MySQLPostgreSQLLưu ý
AUTO_INCREMENTSERIAL / BIGSERIALTương đương
TINYINT(1)BOOLEANMySQL dùng 0/1, PostgreSQL dùng true/false
DATETIMETIMESTAMPPostgreSQL hỗ trợ timezone
ENUMVARCHAR + CHECK constraintHoặc tạo custom TYPE
JSONJSONBJSONB indexable, nhanh hơn JSON
UNSIGNED INTINT + CHECK (col >= 0)PostgreSQL không có UNSIGNED
DOUBLEDOUBLE PRECISIONTương đương
TEXT (utf8mb4)TEXTPostgreSQL mặc định UTF-8

Công cụ sử dụng: pgloader — tự động convert schema và migrate data.

Ngày 3: Initial data load

# pgloader tự động chuyển schema + data
pgloader mysql://user:pass@localhost/logistics \
         postgresql://user:pass@pgserver/logistics
  • 10 triệu records: load trong 45 phút
  • Verify: so sánh row count tất cả bảng
  • Kết quả: 100% rows match

Giai đoạn 2: Dual-Write Setup (3 ngày)

Ngày 4–5: Code changes

Thêm dual-write layer vào application:

// Simplified concept
class DualWriteRepository {
    public function create($data) {
        // Ghi vào MySQL (primary)
        $mysqlResult = $this->mysql->create($data);

        // Async ghi vào PostgreSQL
        Queue::dispatch(new SyncToPostgres($data));

        return $mysqlResult;
    }
}

Ngày 6: Verification system

Tạo hệ thống so sánh tự động:

  • Cron job mỗi 5 phút: so sánh 1.000 random records giữa MySQL và PostgreSQL
  • Alert nếu có sai lệch
  • Dashboard hiển thị sync status real-time

Giai đoạn 3: Shadow Read + Verify (5 ngày)

Ngày 7–11: Chạy parallel

  • App vẫn đọc từ MySQL (primary)
  • Đồng thời đọc từ PostgreSQL (shadow) — so sánh kết quả
  • Log mọi sai lệch

Kết quả verify:

NgàyRecords so sánhSai lệchNguyên nhân
Ngày 7288.00012Timezone conversion
Ngày 8288.0003Floating point precision
Ngày 9288.0000
Ngày 10288.0000
Ngày 11288.0000

Sai lệch ngày 7–8 đều do khác biệt data type giữa MySQL và PostgreSQL → fix trong schema conversion.

Giai đoạn 4: Cutover (2 ngày)

Ngày 12: Switch read to PostgreSQL

  • 10:00 — Chuyển read traffic sang PostgreSQL (1% canary)
  • 11:00 — Tăng lên 10%
  • 14:00 — Tăng lên 50%
  • 17:00 — 100% read từ PostgreSQL
  • Write vẫn vào cả MySQL + PostgreSQL (safety net)

Ngày 13: Confirm + decommission MySQL

  • 24 giờ 100% PostgreSQL — zero issues
  • Tắt dual-write, PostgreSQL là primary duy nhất
  • Giữ MySQL read-only thêm 7 ngày (rollback insurance)
  • Ngày 20: Tắt MySQL server

Kết quả

Performance

MetricMySQL 5.7PostgreSQL 16Cải thiện
Query trung bình45ms12ms3.75x
JSON search2.800ms35ms80x
Báo cáo phức tạp15 giây2.1 giây7x
Concurrent writes800/giây2.400/giây3x
GIS query (tìm kho)N/A (manual)8msMới
Full-text search VN500ms25ms20x

Business impact

MetricTrướcSauThay đổi
Thời gian tạo vận đơn3.2 giây0.8 giây-75%
Báo cáo hàng ngày45 phút5 phút-89%
Tìm kho gần nhấtManual (15 phút)Tự động (< 1 giây)Tự động hóa
Downtime trong migration0 phútZero downtime
Data loss0 records100% integrity

Chi phí

Hạng mụcChi phí
Tư vấn + planning15 triệu VND
Implementation (2 tuần)35 triệu VND
PostgreSQL server (VPS 16GB)2.5 triệu VND/tháng
Tổng one-time50 triệu VND
Tiết kiệm hàng tháng~8 triệu VND (giảm nhân sự xử lý báo cáo thủ công)
ROIHoàn vốn trong 6.3 tháng

Bài học kinh nghiệm

1. Đừng “Big Bang” migration

Downtime migration cho hệ thống 24/7 là không chấp nhận được. Dual-write tốn công hơn nhưng an toàn hơn 100x. Chi phí thêm cho dual-write setup (~30% tổng project) là xứng đáng.

2. Data type differences quan trọng hơn bạn nghĩ

MySQL TINYINT(1) → PostgreSQL BOOLEAN. MySQL UNSIGNED INT → PostgreSQL INT + CHECK constraint. Mỗi khác biệt nhỏ đều có thể gây data corruption nếu bỏ qua.

3. Verify, verify, verify

So sánh data giữa 2 database liên tục trong 5 ngày. Đừng tin rằng “tool migration xử lý hết” — luôn verify bằng automated comparison.

4. Giữ rollback option

Giữ MySQL chạy thêm 7 ngày sau cutover. Chi phí: ~600.000 VND. Giá trị: vô giá (peace of mind).

5. Timing matters

Cutover nên bắt đầu vào ngày traffic thấp (thứ 7). Tăng dần từ 1% → 10% → 50% → 100%. Không bao giờ switch 0% → 100% một lần.

FAQ — Câu hỏi thường gặp

Migration database mất bao lâu?

Tùy quy mô: Database < 1GB (vài triệu records): 1–2 tuần. Database 1–10GB: 2–4 tuần. Database > 10GB: 4–8 tuần. Thời gian chủ yếu cho testing và verification, không phải cho việc copy data.

Có nhất thiết phải đổi database không?

Không. Nếu MySQL đang hoạt động tốt và bạn không cần tính năng đặc biệt của PostgreSQL, đừng đổi. Migration chỉ nên làm khi có lý do business rõ ràng (như case study này: cần JSONB, GIS, và better concurrency).

Kết luận

Database migration từ MySQL sang PostgreSQL là dự án phức tạp nhưng hoàn toàn khả thi với zero downtime nếu có chiến lược đúng. Dual-write approach tuy tốn công hơn Big Bang, nhưng loại bỏ rủi ro downtime và data loss.

Nếu doanh nghiệp bạn đang cân nhắc migration database hoặc cần tư vấn xây dựng hệ thống với kiến trúc database phù hợp, hãy liên hệ Trinh Digital để được tư vấn chi tiết.

#case study#database#PostgreSQL#migration
Chia sẻ: Z

Sẵn sàng chuyển đổi số cùng Trinh Digital?

Liên hệ ngay để nhận tư vấn miễn phí. Đội ngũ chuyên gia sẽ phân tích nhu cầu và đề xuất giải pháp tối ưu.

Zalo