Vận hành
Bug severity, on-call và root cause analysis
Severity quyết định ai bị gọi lúc mấy giờ. Và mỗi incident phải kết thúc bằng một thay đổi cơ chế, không phải bằng câu "lần sau cẩn thận hơn".
Nội dung bài
Thuật ngữ trong bài (21)
- backlog · hàng đợi việc
- definition of done · định nghĩa hoàn thành
- sprint · chu kỳ làm việc
- waiver · miễn trừ có kiểm soát
- merge · gộp nhánh
- bug · lỗi
- integration test · kiểm thử tích hợp
- unit test · kiểm thử đơn vị
- feature flag · công tắc tính năng
- release · bản phát hành
- rollback · quay về bản cũ
- rollout · đưa bản mới ra người dùng
- staged rollout · phát hành theo bậc
- incident · sự cố
- on-call · ca trực
- root cause · nguyên nhân gốc
- severity · mức nghiêm trọng
- SLA · cam kết mức dịch vụ
- API · giao diện lập trình
- idempotency · gọi lại không nhân đôi
- migration · chuyển đổi dữ liệu
Bốn mức severity
Severity không phải để xếp hạng mức độ khó chịu. Nó là hàm quyết định: mức nào thì được đánh thức người, mức nào thì chờ tới giờ hành chính, mức nào thì vào backlog.
| Mức | Nghĩa | Ví dụ trong Alliance App | Phản hồi | Ai vào việc |
|---|---|---|---|---|
| P0 | Mất chức năng cốt lõi diện rộng, hoặc sai về tiền | App crash ngay khi mở; trừ tiền mà không tạo đơn; tin nhắn gửi sai người nhận | Ngay lập tức, kể cả ngoài giờ | Người trực, gọi thêm người nếu cần |
| P1 | Một chức năng chính không dùng được, không có đường vòng | Không đăng nhập được trên một OS version; không tạo được Task | Trong ngày làm việc | Người trực chuyển cho chủ module |
| P2 | Lỗi có đường vòng, hoặc ảnh hưởng nhóm nhỏ | Bộ lọc sai với một trạng thái; sai định dạng ngày | Đưa vào sprint hiện tại hoặc kế tiếp | Chủ module |
| P3 | Lỗi hiển thị, không cản trở công việc | Lệch canh lề, sai chính tả, icon mờ | Backlog | Gộp vào đợt dọn |
Luật chống lạm dụng: người báo lỗi không tự đặt P0. Người trực xác nhận severity. Ngược lại, hạ severity của một bug để nó không chặn release là hành vi bị cấm — cách xử lý đúng là dùng đường waiver trong Mẫu checklist trước release, có người ký tên.
Hai trường hợp tự động là P0, không cần tranh luận:
- Bất kỳ sai lệch nào về tiền, kể cả một đồng, kể cả chỉ một user.
- Bất kỳ nghi ngờ nào về rò rỉ dữ liệu cá nhân — xem bảo mật và quyền riêng tư.
Và luôn quy severity ra số user thật, không để ở dạng phần trăm. Ở quy mô 500.000 user, "chỉ 0,5% bị ảnh hưởng" là 2.500 người — người trực đêm cần con số đó để quyết định gọi ai, chứ không cần tự nhân nhẩm.
On-call
SLA "P0 phản hồi ngay lập tức" mà không có lịch trực là một SLA giả. Ngay lập tức nghĩa là có một người cụ thể, biết trước là hôm nay tới lượt mình, và có sẵn quyền để hành động.
| Hạng mục | Quy định |
|---|---|
| Chu kỳ | Luân phiên theo tuần, lịch công bố trước ít nhất một tháng |
| Ai tham gia | Mọi dev đã qua ba tháng trong dự án, cộng chủ kỹ thuật |
| Phạm vi | Nhận cảnh báo, phân loại, chặn máu. Không bắt buộc phải tự sửa xong |
| Bù | Trực ngoài giờ có bù nghỉ; trực mà không bù thì sau ba tháng sẽ không còn ai nhận |
| Bàn giao | Cuối tuần bàn giao bằng văn bản: đang có gì treo, cảnh báo nào đang ồn |
Quyền của người trực — phần này phải rõ, nếu không họ sẽ ngồi chờ xin phép trong lúc sự cố lan rộng. Người trực được toàn quyền, không cần hỏi ai:
- Tắt feature flag
- Dừng staged rollout
- Bật chế độ bảo trì cho một module
- Đánh thức bất kỳ ai trong đội khi gặp P0
Dùng sai một trong bốn quyền trên không bị trách. Không dùng vì ngại mới bị trách.
Vai trò khi có sự cố lớn
Với P0, chia vai ngay từ phút đầu. Không chia vai thì ba người cùng sửa một chỗ, không ai trả lời hỗ trợ, và không ai nhớ đã thử gì.
| Vai | Làm gì | Không làm gì |
|---|---|---|
| Người chỉ huy | Quyết định, phân việc, giữ timeline | Không tự tay sửa |
| Người xử lý | Điều tra và sửa | Không trả lời người ngoài |
| Người liên lạc | Cập nhật cho hỗ trợ, chủ sản phẩm, lãnh đạo | Không tham gia sửa |
Luật quan trọng nhất trong bảng: người chỉ huy không bao giờ kiêm người xử lý. Người đang cắm đầu đọc log không còn khả năng nhìn toàn cảnh, không nhớ gọi ai, và không nhận ra lúc nào nên rollback thay vì sửa tiếp.
Với sự cố nhỏ, một người kiêm cả ba vai là bình thường. Nhưng khi sự cố kéo dài quá 30 phút hoặc lan rộng, phải tách vai.
Ai cũng được quyền tuyên bố một sự cố. Tuyên bố nhầm rồi đóng lại sau 10 phút không tốn gì. Ngại tuyên bố thì tốn hàng giờ.
Trình tự khi có incident
Chặn máu trước, điều tra sau. Thứ tự này không được đảo, vì điều tra tốn hàng giờ còn thiệt hại thì tính theo phút.
- Xác nhận phạm vi: bao nhiêu user, version nào, từ lúc nào.
- Chặn: tắt flag, dừng rollout, hoặc chuyển sang chế độ bảo trì.
- Thông báo: bộ phận hỗ trợ biết để trả lời user; chủ sản phẩm biết nếu ảnh hưởng nghiệp vụ.
- Ghi timeline ngay trong lúc xử lý, không để cuối buổi nhớ lại. Mỗi dòng một mốc thời gian kèm việc đã làm.
- Sửa, kiểm chứng, mở lại dần.
- Viết root cause analysis trong vòng một tuần.
Timeline ghi trong lúc xử lý là dữ liệu quý nhất cho bước 6. Viết lại từ trí nhớ sau ba ngày thì phần "phát hiện lúc nào, bằng cách nào" luôn bị làm đẹp đi.
Thứ tự chặn máu
Ở bước 2, thử theo đúng thứ tự này — nhanh nhất và ít rủi ro nhất đứng trước:
| Thứ tự | Cách chặn | Thời gian | Rủi ro kèm theo |
|---|---|---|---|
| 1 | Tắt feature flag liên quan | 30 giây | Gần như không |
| 2 | Rollback bản triển khai gần nhất | Vài phút | Mất tính năng mới |
| 3 | Giảm tỷ lệ rollout về 0 | Vài phút | Như trên |
| 4 | Bật chế độ bảo trì cho một module | Vài phút | User thấy rõ |
| 5 | Vá nóng | Hàng giờ | Cao nhất — vá dưới áp lực hay sinh lỗi mới |
Cách số 5 là lựa chọn cuối cùng, không phải phản xạ đầu tiên. Dev có xu hướng lao vào sửa vì đó là việc quen tay, trong khi tắt một flag đã dừng được thiệt hại từ phút thứ nhất.
Câu hỏi dẫn đường trong lúc điều tra luôn là: "thay đổi gì gần đây nhất?" Phần lớn sự cố bắt đầu từ một thay đổi trong 24 giờ trước đó — một lần merge, một lần bật flag, một thay đổi cấu hình, một migration. Nhìn ở đó trước khi nghĩ tới nguyên nhân phức tạp.
Nhịp cập nhật
Người liên lạc cập nhật đều đặn, kể cả khi chưa có gì mới:
| Mức | Nhịp cập nhật |
|---|---|
| P0 | Mỗi 30 phút |
| P1 | Mỗi 2 giờ |
| P2 trở xuống | Cuối ngày làm việc |
"Chưa có gì mới, vẫn đang tìm" là một cập nhật hợp lệ. Im lặng thì người ngoài sẽ tự đoán, và họ luôn đoán tệ hơn thực tế — rồi bắt đầu nhắn hỏi từng người, làm chậm chính việc xử lý.
Khi nào đóng sự cố
Đóng khi thiệt hại với user đã dừng, không phải khi đã tìm ra nguyên nhân. Hai việc này tách rời: tắt flag là đóng sự cố; tìm hiểu vì sao flag đó gây lỗi là công việc của RCA, làm trong giờ hành chính, với đầu óc tỉnh táo.
Giữ sự cố mở tới khi hiểu hết nguyên nhân là cách làm kiệt sức người trực mà không cứu thêm được user nào.
Root cause analysis
Với mọi P0 và P1, và với bất kỳ bug nào lặp lại lần thứ ba, phải trả lời năm câu — không phải một câu:
- Vì sao lỗi xảy ra? (nguyên nhân kỹ thuật)
- Vì sao unit test không bắt được?
- Vì sao QA không bắt được?
- Vì sao automation không bắt được?
- Vì sao monitoring không phát hiện sớm hơn?
Câu 2 tới 5 mới là phần có giá trị. Câu 1 chỉ cho biết sửa gì lần này; bốn câu sau cho biết vì sao hệ thống kiểm soát đã để nó đi qua — và đó là thứ sẽ để lọt bug tiếp theo nếu không đụng tới.
Đầu ra bắt buộc: preventive action
Một buổi phân tích kết thúc bằng "lần sau cẩn thận hơn" là một buổi thất bại. Cẩn thận hơn không phải là cơ chế, và nó không sống sót qua ngày bận rộn.
Mỗi RCA phải đẻ ra ít nhất một preventive action có chủ và có hạn. Ví dụ thật:
Incident: Mạng chập chờn lúc thanh toán → user bấm lại → tạo hai đơn, trừ tiền hai lần.
Fix: Thêm idempotency key phía client, server chống trùng theo key.
Prevent: Thêm integration test chủ động gửi trùng request cho mọi API tạo giao dịch.
Process: Thêm "API tạo giao dịch phải idempotent" vào Definition of Done.
Detect: Thêm cảnh báo khi tỷ lệ đơn trùng trong 10 phút vượt ngưỡng.
Bốn dòng cuối mới là giá trị của buổi phân tích. Chỉ có dòng Fix thì cùng lớp lỗi đó sẽ quay lại
ở một API khác.
Preventive action được đưa vào backlog như một hạng mục bình thường, có người nhận, có hạn — không phải một dòng ghi chú trong biên bản.
Không truy trách nhiệm cá nhân
RCA tập trung vào hệ thống, không vào người. Lý do thực dụng, không phải vì tử tế: người ta chỉ báo lỗi sớm khi biết báo lỗi không dẫn tới bị quy trách nhiệm. Một đội sợ nhận lỗi là một đội sẽ biết tin xấu muộn hơn vài ngày — và vài ngày đó là khoảng cách giữa một sự cố nhỏ và một sự cố lớn.
Câu hỏi đúng không phải "ai đẩy code này lên", mà là "vì sao hệ thống cho phép thay đổi này đi tới production mà không bị chặn".
Giới hạn
- Đừng RCA mọi bug. Chỉ P0, P1, và bug lặp lại. RCA cho một lỗi lệch canh lề là cách chắc chắn để cả đội chán quy trình này trong hai tháng.
- Preventive action cũng tốn chi phí. Nếu một RCA đẻ ra năm hạng mục và không cái nào được làm thì quy trình đã hỏng — thà chọn một cái và làm thật.
- Danh sách severity ở trên cần ví dụ thật của dự án để dùng được. Mỗi quý soát lại và bổ sung ví dụ từ incident đã xảy ra.

