Trên Postgres, CREATE INDEX thông thường khoá ghi cả bảng cho tới khi xong — với bảng 50 triệu dòng nghĩa là ứng dụng ngừng ghi vài phút.
Giải pháp là tạo index concurrently:
class AddIndexToOrdersUserId < ActiveRecord::Migration[7.1]
disable_ddl_transaction!
def change
add_index :orders, :user_id, algorithm: :concurrently
end
endHai dòng đều bắt buộc:
- algorithm: :concurrently → sinh CREATE INDEX CONCURRENTLY, cho phép đọc/ghi song song trong lúc build.
- disable_ddl_transaction! → Postgres không cho chạy lệnh này trong transaction; thiếu dòng này migration sẽ lỗi.
Đánh đổi và điểm cần lưu ý:
- Build lâu hơn (quét bảng hai lượt) và không được bọc trong transaction, nên nếu migration fail giữa chừng sẽ để lại index ở trạng thái INVALID. Phải DROP INDEX rồi chạy lại; kiểm tra bằng pg_index.indisvalid.
- add_reference mặc định tạo index không concurrently — phải chỉ định rõ: add_reference :orders, :user, index: { algorithm: :concurrently }.
- Đặt lock_timeout và statement_timeout ngắn cho migration để nếu không lấy được lock thì fail nhanh, không xếp hàng chặn mọi query phía sau.
- Backfill dữ liệu là chuyện riêng: chạy theo lô, có throttle, ngoài transaction, và không dùng model của app trong migration.
Gem strong_migrations chặn sẵn các thao tác nguy hiểm này ở môi trường dev và gợi ý cách viết an toàn.