Nghịch lý Laravel: Tại sao các hệ thống "khủng" lại quay lưng với Migration?
Chào anh em Viblo, lại là mình đây!
Bất cứ ai khi mới bước chân vào thế giới của Laravel đều được dạy về Migrations. Nó được ví như "Git dành cho cơ sở dữ liệu". Thay vì phải gửi file .sql cho nhau qua lại, anh em chỉ cần gõ php artisan make:migration, định nghĩa các cột bằng code PHP, rồi gõ php artisan migrate. Bùm! Cả team có chung một cấu trúc Database. Tiện lợi, sạch sẽ và cực kỳ thanh lịch.
Nhưng có một sự thật phũ phàng thế này: Khi dự án vượt qua giai đoạn startup, phình to thành một hệ thống Enterprise với hàng chục triệu bản ghi, hoặc khi bạn bước chân vào làm backend cho các công ty lớn, bạn sẽ thấy tính năng Migration dần bị... "cất vào tủ". Rất nhiều hệ thống lớn sử dụng Laravel nhưng lại cấm tuyệt đối việc chạy php artisan migrate trên Production.
Tại sao lại có nghịch lý này? Hôm nay, hãy cùng mình bóc tách những góc khuất của hệ thống lớn để hiểu lý do nhé!
1. Nỗi ám ảnh "Table Locking" (Khóa bảng) và Zero-Downtime
Đây là lý do lớn nhất và chí mạng nhất.
Hãy tưởng tượng bạn đang quản lý một bảng lưu trữ log giao dịch cho hệ thống vé tự động AFC, hoặc bảng đơn hàng ở Hasaki với hàng chục triệu, thậm chí hàng trăm triệu dòng dữ liệu. Sếp yêu cầu: "Thêm cho anh một cột discount_amount vào bảng orders nhé".
Nếu bạn hồn nhiên viết file Migration và chạy php artisan migrate trên Production, lệnh ALTER TABLE của MySQL sẽ được thực thi. Với những bảng khổng lồ, quá trình thêm cột này có thể mất từ vài chục phút đến vài tiếng. Tệ hơn nữa, MySQL sẽ khóa toàn bộ bảng (Table Lock) trong suốt thời gian đó. Không một truy vấn INSERT hay UPDATE nào lọt vào được. Toàn bộ hệ thống lăn ra chết (Downtime), khách hàng không thể đặt hàng, đối tác không thể đồng bộ dữ liệu.
Ở các hệ thống lớn, họ làm gì? Họ bắt buộc phải thực hiện Zero-downtime schema changes (Đổi cấu trúc không sập mạng). Thay vì dùng lệnh ALTER TABLE trực tiếp, họ phải dùng các công cụ chuyên dụng như gh-ost (của GitHub) hoặc pt-online-schema-change (của Percona). Những công cụ này sẽ âm thầm tạo một bảng copy, đồng bộ dữ liệu dần dần, rồi tráo đổi (swap) hai bảng cho nhau trong một phần ngàn giây. Và tất nhiên, Laravel Migration mặc định không hỗ trợ những trò ma thuật này.
2. Cuộc chiến phân quyền: Developer vs. DBA
Khi team chỉ có 3-5 người, Backend Dev kiêm luôn vai trò thiết kế Database. Nhưng ở các tập đoàn lớn, Cơ sở dữ liệu là tài sản vô giá, và nó được bảo vệ bởi một thế lực mang tên DBA (Database Administrator).
DBA là những người ăn ngủ với SQL, tối ưu từng cái Index, phân tích Execution Plan. Quy trình chuẩn ở hệ thống lớn là:
- Dev viết file .sql chứa câu lệnh thay đổi cấu trúc.
- Gửi file đó cho DBA review.
- DBA sẽ kiểm tra xem câu lệnh đó có gây full table scan không, có phá vỡ Index không.
- DBA tự tay chạy script đó bằng tool của họ vào thời điểm ban đêm (Maintenance Window).
Laravel Migration sử dụng cú pháp Blueprint của PHP (ví dụ: $table->string('name')->index()
. DBAs thường không thích (và cũng không có thời gian) đi đọc code PHP để dịch ngược ra SQL. Dù Laravel có lệnh php artisan migrate --pretend để in ra mã SQL, nó vẫn tạo ra một rào cản giao tiếp không đáng có giữa Dev và DBA.
3. Vượt ra khỏi giới hạn của RDBMS: Sharding và Partitioning
Khi database quá tải, việc nâng cấp RAM hay CPU (Scale up) là không đủ. Chúng ta bắt buộc phải Scale out bằng các kỹ thuật phức tạp như Table Partitioning (Chia nhỏ bảng theo tháng/năm) hoặc Database Sharding (Băm nhỏ dữ liệu ra nhiều server vật lý khác nhau).
Chẳng hạn, nếu bạn đang nghiên cứu và áp dụng các giải pháp như Vitess để phân mảnh (shard) cơ sở dữ liệu MySQL, mọi thứ sẽ vượt khỏi tầm kiểm soát của Laravel Migration.
Blueprint của Laravel được thiết kế cho các bảng phẳng (flat tables) truyền thống. Nó thiếu đi những hàm API mạnh mẽ để định nghĩa các cấu trúc phức tạp như:
- "Tạo bảng này và chia Partition theo Range của cột created_at".
- "Bảng này là một Sharded Table, sử dụng cột user_id làm Sharding Key".
Nếu cố đấm ăn xôi dùng Migration, bạn sẽ phải dùng DB::statement('nhét nguyên cục SQL thuần vào đây'). Vậy thì thà vứt luôn Migration đi và quản lý bằng file .sql từ đầu cho rảnh nợ!
4. Hệ thống Legacy (Di sản) và Đa ngôn ngữ
Không phải lúc nào dự án cũng mới tinh (Greenfield). Rất nhiều khi bạn phải dùng Laravel để kết nối vào một con Database đã tồn tại được 10 năm của công ty, nơi mà cấu trúc bảng đã được đóng đinh.
Hơn nữa, trong kiến trúc Microservices hiện đại, Backend không chỉ có PHP. Một hệ thống có thể có các service viết bằng Golang (để xử lý concurrency tốt hơn), Node.js (để làm realtime), và Laravel. Nếu bạn để Laravel nắm quyền chạy Migration, các service Golang hay Node.js kia sẽ bị phụ thuộc vòng vào service PHP.
Giải pháp thay thế: Thay vì để framework (Laravel) quản lý cấu trúc DB, các hệ thống lớn tách hẳn việc này ra thành một kho lưu trữ (Repository) riêng rẽ, độc lập với ngôn ngữ lập trình. Họ sử dụng các công cụ quản lý Database Versioning độc lập và mạnh mẽ hơn như Flyway hoặc Liquibase.
Lời kết
Viết những dòng này không phải để "dìm hàng" Laravel Migration. Nó vẫn là một tính năng tuyệt đỉnh và mình vẫn dùng nó hàng ngày cho các dự án cỡ vừa và nhỏ, hoặc trên môi trường Local/Staging để anh em trong team dễ dàng đồng bộ môi trường dev.
Tuy nhiên, với tư cách là một kỹ sư phần mềm, chúng ta cần hiểu rõ ranh giới của các công cụ. Migration sinh ra để phục vụ cho tốc độ phát triển (Development Speed). Nhưng khi hệ thống đạt đến một ngưỡng giới hạn nhất định về khối lượng dữ liệu, Sự ổn định (Stability) và Hiệu năng (Performance) mới là yếu tố quyết định. Lúc đó, việc "chia tay" với Migration trên Production là một bước lùi cần thiết để hệ thống tiến xa hơn.
All Rights Reserved