Python at Scale: Strict Modules
Bài tóm tắt Instagram 2019 3. Quy mô lớn Độ khó: Khó
Instagram · 2019 · 3. Quy mô lớn · Độ khó: Khó — bài gốc
Bài thứ ba trong series "Python at scale" (sheet chỉ trỏ bài này). Medium chặn crawler; đọc qua trình duyệt, ghi chú bằng lời của wiki.
Bối cảnh
- Instagram Server là monolith Python vài triệu dòng, hàng trăm commit mỗi ngày, deploy production vài phút một lần (2019). Theo Instagram, các tính năng động của Python ổn với codebase nhỏ và đội có kỷ luật, nhưng thành vấn đề khi hàng trăm developer (nhiều người mới với Python) cùng làm.
- Ba nỗi đau:
- Khởi động chậm: import một module chạy rất nhiều code (biên dịch regex, decorator đăng ký route, thân class, metaclass). Server khởi động hơn 20 giây, có lúc tụt về khoảng một phút; gần hết thời gian là import. Vì import có thể có side effect tuỳ ý, không reload tăng dần một cách an toàn được, phải khởi động lại từ đầu dù 99% code không đổi. Việc này còn lãng phí compute ở production vì deploy liên tục.
- Side effect khi import không an toàn: ví dụ lấy cấu hình qua mạng ở mức module: service mạng chết thì server không khởi động được; thứ tự import là thuộc tính nổi lên của toàn hệ, một import vô hại ở chỗ khác có thể làm module dùng service trước khi nó được khởi tạo, có lúc làm sập production.
- Trạng thái toàn cục khả biến: gán thuộc tính request vào class (singleton toàn cục) làm bẩn mọi request sau trong tiến trình; monkeypatch trong test gây test flaky nhiều tới mức họ chuyển sang mỗi test một tiến trình.
Thay đổi chính
- Theo họ, viết lại sang ngôn ngữ khác là không khả thi ở quy mô đó, và developer thích Python; nên phải làm Python chạy được ở quy mô này.
- Strict modules (thử nghiệm, đang rollout): module đánh dấu
__strict__ = Trueđược một module loader riêng phân tích AST bằng abstract interpretation lúc biên dịch. Code ở mức module phải thuần (không side effect, không I/O), nhưng vẫn cho phép comprehension, nhiều loại decorator. Gọi hàm từ module không strict bị coi là không an toàn (trừ một số hàm thư viện chuẩn). - Module và class trong strict module bất biến sau khi tạo (thân module được biến thành một hàm, biến toàn cục thành closure); class tự có
__slots__từ các thuộc tính gán trong__init__, nên không gắn thêm thuộc tính tuỳ tiện, đồng thời tiết kiệm bộ nhớ và truy cập nhanh hơn. - Hướng tiếp theo họ nêu: reload tăng dần an toàn, thực thi lười từng câu lệnh mức module, lưu sẵn metadata module cho module không đổi.
Vì sao
- Ở monolith lớn, chi phí của tính động không nằm ở tốc độ chạy mà ở việc không ai biết import làm gì; giới hạn có kiểm chứng tại một ranh giới (module) cho phép tối ưu khởi động và loại cả một lớp lỗi (Đổi ngôn ngữ và runtime, Monolith và microservices — đi và quay lại).
Bài học cho SWE
- Ở mức module chỉ nên khai báo; mọi I/O và đăng ký toàn cục để vào lúc chạy có kiểm soát.
- Thời gian khởi động là chi phí trả cho mỗi lần deploy và mỗi lần developer thử thay đổi.
- Khi không đổi được ngôn ngữ, thêm một tập con có kiểm chứng của ngôn ngữ (opt-in theo module).
Liên quan
- Instagram 2017 (cùng monolith Python, tối ưu runtime) · Instagram 2011 · Deno 2024 (tiền xử lý lúc deploy để giảm cold start)
- Đổi ngôn ngữ và runtime · Monolith và microservices — đi và quay lại
#Python monolith#Import side effect#Startup time#Incremental reload#Mutable global state#Flaky test#Strict modules#Abstract interpretation#__slots__
Được nhắc tới trong
Công ty (1)
Bài tóm tắt (2)