Stack Overflow: How We Do Deployment - 2016 Edition
Bài tóm tắt Stack Overflow 2016 3. Quy mô lớn Độ khó: Trung bình
Stack Overflow · 2016 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc
Bài 3 trong series của Nick Craver. Bài trước: Stack Overflow 2016; bài sau: Stack Overflow 2018.
Bối cảnh
Số liệu 2016:
- khoảng 25 lần deploy/ngày lên môi trường dev (CI build), chỉ tính repo Q&A; 5–10 lần/ngày lên production;
- từ lúc push tới deploy xong toàn bộ dưới 9 phút (dev 2:15, meta 2:40, mọi site 3:20);
- khoảng 15 người push vào repo này.
Repo chứa nhiều ứng dụng: app Q&A (mọi site), stackexchange.com, Stack Auth (OAuth), sstatic.net (domain CDN không cookie), API v2, API mobile, Stack Server (tag engine và indexing Elasticsearch) và Socket Server (websocket).
Thay đổi chính
- Gần như không dùng branch: đa số commit vào thẳng
masterrồi deploy ngay. Chỉ tạo branch khi dev mới cần review, khi tính năng lớn hoặc rủi ro, hoặc khi nhiều người cùng làm. Merge thì squash để dễ revert. Theo tác giả, đây là cách hợp với đội của họ, không phải lời khuyên chung. - Git on-prem (GitLab), không dùng GitHub cho code chính: build kéo code qua 2×10 Gb nội bộ nhanh hơn, và chuỗi phụ thuộc ngắn hơn (không phụ thuộc mạng Internet, DDoS hay bên thứ ba để build được). TeamCity chạy chung VM với GitLab.
- Build agent chạy ngay trên web tier (11 agent mỗi data center), vì web tier dư CPU. TeamCity poll repo mỗi 15 giây; agent giữ git mirror để mỗi lần chỉ
git pullphần thay đổi. - 9 bước build ở môi trường dev: migrate DB
Sites→ migrate các DB Q&A → dump chuỗi cần dịch (JS) → MSBuild (biên dịch view bằng Roslyn, nhúng sẵn bản dịch; bundle và minify JS/LESS bằng Node chạy song song) → dump chuỗi C# → import chuỗi tiếng Anh → deploy → webhook báo chuỗi mới. Tier meta và production chỉ chạy một phần các bước. - Deploy một web server: báo HAProxy ngừng gửi request mới (DRAIN) → chờ request đang chạy xong → dừng site IIS → báo HAProxy server đã down →
robocopycode mới → khởi động site → báo HAProxy sẵn sàng. HAProxy chỉ trả traffic lại sau 3 lần health check thành công. Làm lần lượt từng máy; 9 web server mất khoảng 2 phút.- Theo tác giả, đi từ
MAINTsangREADYthì HAProxy coi server sống ngay và dồn traffic vào khi app chưa ấm, gây vấn đề nặng với việc tăng ThreadPool;DRAINthì phải qua health check trước.
- Theo tác giả, đi từ
- Tránh race của cache tĩnh khi deploy cuốn chiếu: static asset cache 7 ngày, cache breaker là hash nội dung file (không phải số build). Asset được deploy trước sang một site IIS riêng phục vụ CDN, rồi mới tới code phát hash mới. Lỗi nếu có thì nghiêng về phía tự hết khi tải lại trang.
- Migration DB:
- luôn migrate DB trước khi deploy code; migration chạy song song trên mọi DB Q&A (hàng trăm DB);
- mỗi migration phải idempotent; mỗi DB có bảng
Migrationslưu tên file và hash, hash lệch thì dừng; - đặt số thứ tự migration bằng cách "giành số" trong chatroom, có bot ghim lại;
- thêm cột: thêm dạng nullable hoặc chưa dùng, ràng buộc
not nullhay khoá ngoại để deploy sau; xoá cột: xoá khỏi code, deploy, rồi mới drop. Không đổi tên object SQL trên production; - gần như không bao giờ rollback: viết migration mới để sửa (roll forward).
- Đổi mà không làm vỡ: object cache thay đổi hẳn thì dùng key mới; object trong Redis thì phải giữ tương thích protobuf hoặc đổi key. Đổi API thì qua 3 lần deploy: thêm endpoint → chuyển consumer → dọn endpoint cũ.
- Localization xử lý lúc biên dịch: chuỗi
_s()/_m()được biên dịch thành các mảnh đã intern để giảm allocation lúc chạy. Theo tác giả, đáng đổi chút phức tạp lúc build để đỡ GC lúc chạy.
Vì sao
Theo tác giả: build queue dài làm người ta gom commit lớn; commit nhỏ, deploy liên tục thì sửa nhanh hơn rollback, nhất là khi có hàng trăm DB. Deploy được giữ "càng đơn giản càng tốt": về cơ bản chỉ là dừng site, chép file, chạy lại.
Bài học cho SWE
- Phối hợp với load balancer khi deploy (drain → health check rồi mới nhận traffic) quyết định có lỗi lúc deploy hay không.
- Mô hình expand-contract (thêm trước, dọn sau) cho cả schema lẫn API là điều kiện để deploy nhiều lần mỗi ngày mà không rollback.
- Cache breaker theo hash nội dung vừa tiết kiệm băng thông vừa bắt được lỗi đường dẫn ngay lúc build.
- Năm 2019, chính hệ build này (TeamCity, chuỗi dev → build) là đường kẻ tấn công đi vào (Stack Overflow 2021); sau đó build được tách khỏi deploy.
Liên quan
#Rolling deploy#HAProxy drain#Trunk-based#TeamCity#GitLab on-prem#SQL migration#Expand-contract#Cache breaker#Localization compile-time
Được nhắc tới trong
Công ty (1)
Bài tóm tắt (3)