Fear-free PostgreSQL migrations for Rails
Bài tóm tắt GoCardless 2020 3. Quy mô lớn Độ khó: Trung bình
GoCardless · 2020 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc
Năm bài là ước lượng (xem
year_note). Nối tiếp GoCardless 2015.
Bối cảnh
- Postgres là database mặc định của GoCardless, đứng sau monolith API chính (Rails). Schema đổi qua migration của Rails.
- Phần lớn DDL lấy khoá
ACCESS EXCLUSIVE(chặn cả đọc lẫn ghi). Bẫy lớn hơn là hàng đợi khoá: nếu một batch job dài đang giữROW EXCLUSIVE, lệnh DDL phải chờ, và mọi query đến sau xếp sau nó. Khoá thật chỉ giữ một phần giây, nhưng bảng không dùng được suốt thời gian chờ. Mặc định Postgres không bao giờ timeout query, nên thêm một cột cũng có thể làm sập app. - Migration trông vô hại của Rails như
add_reference :payments, :customer, foreign_key: truesinh ra một transaction thêm cột và thêm foreign key: khoáACCESS EXCLUSIVEtrên cả hai bảng, giữ suốt lúc kiểm constraint (chậm trên bảng lớn). Database càng lớn thì càng bị cắn nhiều hơn. - Theo GoCardless, đó là rò rỉ trừu tượng: muốn tránh thì phải có hiểu biết sâu về concurrency của Postgres, thường chỉ có được sau một lần sự cố.
Thay đổi chính
- Bước đầu: gem
activerecord-safer_migrationsđặt lock timeout và statement timeout thấp mặc định, để không vô tình khoá bảng vô thời hạn. - Nandi: một DSL migration thay cho
ActiveRecord::Migration, chỉ cho viết migration an toàn về availability, rồi biên dịch ra migration ActiveRecord thường (kèm timeout).- Ví dụ thêm foreign key: Nandi sinh ba migration (thêm cột; thêm constraint với
NOT VALIDchỉ cập nhật catalog; validate constraint trong transaction riêng). - Vì là compiler, nó phân tích tĩnh cả migration trước khi chạy bất cứ phần nào, ví dụ chặn việc khoá exclusive hơn một bảng.
- Đã viết gần 600 migration bằng Nandi trong production.
- Ví dụ thêm foreign key: Nandi sinh ba migration (thêm cột; thêm constraint với
- So với gem khác:
strong_migrationschỉ quét lệnh và báo lỗi khi rõ ràng không an toàn;pg_ha_migrationsvàsafe-pg-migrationstắt transaction để giữ khoá ngắn, nhưng migration hỏng giữa chừng thì không chạy lại được và người phải vàopsqlsửa tay. GoCardless giữ transaction để migration chạy lại được.
Bài học cho SWE
- Rủi ro của DDL không chỉ là thời gian giữ khoá mà là thời gian chờ khoá, vì mọi query sau xếp hàng sau nó: luôn đặt lock timeout (Migration không downtime).
- Biến kiến thức "vết sẹo" thành công cụ: một compiler từ chối migration nguy hiểm tốt hơn hy vọng reviewer nhớ (Deploy và release an toàn).
- Tách thay đổi thành nhiều bước nhỏ, mỗi bước khoá ngắn (ví dụ
NOT VALIDrồi validate riêng). - Migration nên chạy lại được; sửa tay trong
psqllà nguồn lỗi mới.
Liên quan
Được nhắc tới trong
Công ty (1)
Bài tóm tắt (1)