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: true sinh ra một transaction thêm cột và thêm foreign key: khoá ACCESS EXCLUSIVE trê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 VALID chỉ 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.
  • So với gem khác: strong_migrations chỉ quét lệnh và báo lỗi khi rõ ràng không an toàn; pg_ha_migrations và safe-pg-migrations tắ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ào psql sử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 VALID rồi validate riêng).
  • Migration nên chạy lại được; sửa tay trong psql là nguồn lỗi mới.

Liên quan