Vibe Code
Giáo trình/Phần 5 — Quy trình & vận hành

Quan sát và xử lý sự cố

Đọc log đúng cách, phân biệt lỗi thật với lỗi nhiễu, quy trình 5 bước khi app đang chết, và cách chuẩn bị để lần sau nhanh hơn.

Cấp 535 phút đọc

App chạy thật thì sớm muộn cũng có ngày hỏng. Khác biệt giữa người làm được và người không nằm ở chỗ: hỏng rồi mất bao lâu để biết, và bao lâu để chữa.

Ba tầng quan sát

Tầng Công cụ Trả lời câu hỏi
Tức thời wrangler tail "Ngay bây giờ đang xảy ra gì?"
Tổng quan Dashboard → Observability "Hôm nay có bất thường không?"
Lịch sử Bảng nhật ký của chính bạn trong D1 "Hôm 15 có chuyện gì?"

Ba tầng này bù cho nhau. wrangler tail chỉ thấy lúc bạn đang mở; dashboard giữ vài ngày; bảng nhật ký của bạn giữ bao lâu tùy bạn.

Tầng 1 — wrangler tail

npx wrangler tail --format=pretty
npx wrangler tail --status=error              # chỉ lỗi
npx wrangler tail --search="don-hang"         # lọc theo chuỗi

Mở một cửa sổ riêng để nó chạy trong lúc bạn thao tác. Đây là công cụ mạnh nhất lúc đang gỡ lỗi.

`wallTime` tính cả việc chạy nền — đừng kết luận nhầm

Thấy một request wallTime: 23000ms thì rất dễ tưởng app chậm thảm họa. Nhưng wallTime cộng cả thời gian ctx.waitUntil — người dùng đã nhận kết quả từ giây đầu.

Muốn biết người dùng chờ bao lâu, đo từ phía người dùng:

curl -w "ket noi %{time_connect}s | byte dau %{time_starttransfer}s | tong %{time_total}s\n" -o NUL -s https://app.com/api/bootstrap

Tầng 2 — Đọc bảng Observability cho đúng

Dashboard → Worker của bạn → Observability. Ba con số cần biết cách đọc:

Tỉ lệ lỗi = Errors / Total. Dưới ~1% thường là khỏe. Nhưng con số này chỉ có nghĩa nếu bạn ghi log đúng loại:

`console.log` hay `console.error` — chọn đúng, nếu không bảng lỗi vô dụng

Lỗi của dịch vụ bên ngoài đã có đường lui thì ghi bằng console.log. Ví dụ: dịch vụ tra địa chỉ hỏng, app lùi về dùng tọa độ thô — người dùng không mất gì. Đó không phải lỗi app.

Dùng console.error cho trường hợp này là bảng lỗi phồng lên vì những thứ vô hại, và nó che mất lỗi thật khi bạn cần nhìn.

Để dành console.error cho thứ thật sự hỏng và cần bạn ra tay.

Thời gian CPU. Nhớ: thời gian chờ không tính là CPU. Chờ cơ sở dữ liệu 2 giây có thể chỉ tốn 3 ms CPU. Nếu CPU cao thật thì nguyên nhân là tính toán: vòng lặp lớn, xử lý ảnh, mã hóa.

Số lượt gọi theo đường. Đường nào gọi nhiều bất thường? Đây là cách phát hiện việc hỏi lặp thừa — một app từng gọi /api/trang-thai 72.000 lượt mỗi ngày cho một thứ không ai nhìn.

Tầng 3 — Nhật ký của chính bạn

Dashboard không biết nghiệp vụ của bạn. Hãy tự ghi:

CREATE TABLE nhat_ky (
  id       INTEGER PRIMARY KEY AUTOINCREMENT,
  loai     TEXT NOT NULL,          -- 'dang-nhap' | 'xoa-don' | 'cron' | 'loi'
  nguoi    TEXT,
  chi_tiet TEXT,
  luc      TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_nk ON nhat_ky(loai, luc DESC);

Ghi những việc có hậu quả: đăng nhập, xóa, sửa dữ liệu quan trọng, duyệt, mỗi lượt chạy việc định kỳ. Đừng ghi mọi lượt đọc — bảng phình nhanh mà không dùng tới.

Và hiện lên trang quản trị: "Đồng bộ lần cuối: 08:15 hôm nay · 42 dòng · 0 lỗi". Một dòng này giúp bạn biết hệ thống còn sống mà không cần mở dashboard.

Việc nền chết âm thầm nguy hiểm hơn app chết ồn àochuyên gia insight & case thực tế

App chết thì trong 5 phút có người gọi. Cron ngừng chạy thì hai tuần sau mới có người hỏi "sao tháng này không thấy báo cáo?".

Ba mức, nên làm đủ: 1. Ghi mỗi lượt chạy vào nhat_ky — lúc nào, bao nhiêu dòng, mấy lỗi. 2. Hiện lần chạy cuối trên trang quản trị, kèm màu: xanh nếu trong chu kỳ, đỏ nếu quá hạn. 3. Báo động khi im lặng. Thêm một cron khác kiểm: "nếu đồng bộ chưa chạy quá 2 chu kỳ thì gửi cảnh báo."

Mức 3 nghe thừa với app nhỏ, nhưng nó bắt đúng loại sự cố mà bạn không bao giờ tự phát hiện.

Khi app đang chết — 5 bước

Khôi phục trước, điều tra sau.

Bước 1 — Xác nhận phạm vi (1 phút).

curl -I https://app.com                    # toàn bộ hay một phần?
curl -I https://app.com/api/bootstrap

Hỏi: mọi người hay một người? mọi màn hình hay một màn hình? từ lúc nào?

Bước 2 — Có vừa deploy gì không? Nếu có: ROLLBACK NGAY.

npx wrangler deployments list
npx wrangler versions deploy <id-cu>@100% --yes

Đừng ngồi tìm nguyên nhân trong lúc cả công ty ngồi chơi. Lùi mất 30 giây.

Bước 3 — Kiểm chứng bằng người-mở-mới thật. Xóa cookie, cửa sổ ẩn danh, tải lại. Đã có lần rollback xong mà app vẫn treo — vì nguyên nhân nằm ở một tác vụ nền, không phải ở bản vừa deploy. Không kiểm chứng thì bạn tưởng đã xong.

Bước 4 — Nếu rollback không cứu được: tìm theo thứ tự này.

[ ] Cơ sở dữ liệu có đang quá tải / có việc nặng nào đang chạy không?
[ ] Có dịch vụ bên ngoài nào đang chết mà mình gọi KHÔNG có hạn giờ?
[ ] Có bị chặn bởi trần rate limit không? (xem lỗi 429 trong log)
[ ] Có binding nào biến mất sau lần deploy gần nhất không?
[ ] Cloudflare có sự cố không? → cloudflarestatus.com

Bước 5 — Báo cho người dùng. Một tin nhắn "App đang có sự cố, bên em đang xử lý, dự kiến 30 phút" có giá trị hơn mọi thứ bạn làm trong giờ đó. Im lặng là thứ khách nhớ lâu nhất.

Hai sự cố mẫu, mổ kỹ

Sự cố 1 — App treo, không đường nào có vẻ chậm.

Triệu chứng: người dùng kẹt màn hình tải, log không có lỗi nào nổi bật. Nguyên nhân: một tác vụ dọn dẹp nhét trong ctx.waitUntil chạy 40 lượt xóa trên D1. D1 xử lý tuần tự cả cơ sở dữ liệu, nên mọi request khác xếp hàng chờ. Chữa: bỏ việc nặng ra khỏi waitUntil, chuyển sang cron riêng. Phòng: không bao giờ để việc nhiều lượt gọi cơ sở dữ liệu trong waitUntil.

Sự cố 2 — 429 dây chuyền, không ai đăng nhập được.

Triệu chứng: giờ cao điểm, hàng loạt nhân viên bị từ chối. Nguyên nhân: trần đếm theo IP, mà mạng 4G Việt Nam gộp cả nghìn người vào một IP. Chữa: đổi sang đếm theo ip + tên đăng nhập cho cửa đăng nhập, theo phiên cho phần còn lại, và miễn đếm cho các đường rẻ. (Bảo mật) Phòng: không bao giờ đếm theo IP trần cho luồng người thật.

Giao việc điều tra cho Claude Codechuyên gia thực thi
App báo lỗi "không tải được danh sách đơn" từ khoảng 14:00 hôm nay.
Hãy TỰ ĐIỀU TRA, chỉ đọc và chạy lệnh đọc, CHƯA được sửa gì:

1. wrangler deployments list — có bản nào deploy gần 14:00 không?
2. wrangler tail --status=error trong 2 phút, thu thập mẫu lỗi
3. Truy vấn nhat_ky: lượt chạy việc nền gần nhất lúc nào, có lỗi không
4. Đếm số dòng của các bảng chính — có bảng nào phình bất thường?
5. curl -w đo thời gian thật của /api/bootstrap

Rồi nói cho tôi:
- Nguyên nhân khả dĩ nhất, kèm bằng chứng cụ thể từ bước nào
- Cách khôi phục nhanh nhất
- Cách sửa gốc

Đừng sửa gì cho tới khi tôi đồng ý.

Câu "chưa được sửa gì" rất quan trọng lúc đang có sự cố: bạn cần hiểu trước, vì một lần sửa mò giữa sự cố có thể biến một sự cố thành hai.

Chuẩn bị trước để lần sau nhanh hơn

Viết sẵn một file SU-CO.md trong repo, có đủ:

## Lệnh khôi phục nhanh
wrangler deployments list
wrangler versions deploy <id>@100% --yes

## Bản đang chạy ổn định gần nhất
dafb9038  (ngày 29/09, đã chạy ổn 2 tuần)

## Chỗ kiểm theo thứ tự
1. cloudflarestatus.com
2. wrangler tail --status=error
3. Dashboard → Observability → tỉ lệ lỗi
4. Trang quản trị → lần chạy cuối của việc nền

## Số cần gọi
- Người phụ trách bên khách: ...
- Nhóm Zalo thông báo sự cố: ...

## Mẫu tin nhắn báo sự cố
"App đang có sự cố từ lúc HH:MM, bên em đang xử lý.
 Em sẽ báo lại trong 30 phút nữa."

File này viết lúc bình yên mất 15 phút. Lúc có sự cố, nó tiết kiệm cho bạn đúng 15 phút đầu — 15 phút đắt nhất.

Lần đầu app của tôi chết, tôi làm sai mọi thứngười dùng bình thường góp ý

Tôi hoảng. Tôi sửa mò, deploy ba lần trong 20 phút, mỗi lần một hướng khác nhau. Cuối cùng tôi không còn biết bản đang chạy là bản nào, và app hỏng theo kiểu mới.

Điều tôi học, nghe rất tầm thường nhưng thật: việc đầu tiên không phải mở mã nguồn. Việc đầu tiên là lùi về bản hôm qua. App sống lại, mọi người làm việc tiếp, và tôi có cả buổi chiều yên tĩnh để tìm hiểu.

Thứ hai: báo cho người dùng ngay. Lần đó tôi im lặng 40 phút vì xấu hổ. Sự im lặng đó làm khách khó chịu hơn cả sự cố.

Chốt review bài nàychuyên gia review
  • Đủ: ba tầng quan sát, đọc wallTime cho đúng, phân biệt console.log/console.error, nhật ký nghiệp vụ, quy trình 5 bước, hai sự cố mẫu, file SU-CO.md.
  • Đã trả giá thật: cả hai sự cố mẫu, và chuyện rollback xong vẫn treo.
  • Chưa nói: nối log ra hệ thống ngoài, cảnh báo tự động qua nhiều kênh. Với app một người vận hành thì ba tầng ở trên là đủ.
  • Rủi ro còn lại: mọi thứ ở đây giúp bạn phản ứng. Chúng không giúp bạn phát hiện sớm nếu không có mức 3 (báo động khi im lặng). Nếu chỉ làm được một việc trong bài này, hãy làm cái đó.
Bài tập chốt
  1. Viết SU-CO.md cho project của bạn, đủ 5 mục ở trên.
  2. Thêm bảng nhat_ky và ghi 3 loại sự kiện có hậu quả.
  3. Hiện "lần chạy cuối của việc nền" lên trang quản trị.
  4. Diễn tập: cố ý deploy một bản hỏng (đổi một dòng làm vỡ màn hình chính), rồi chạy đủ 5 bước xử lý sự cố, bấm giờ. Mục tiêu từ lúc phát hiện tới lúc app sống lại: dưới 3 phút.

Việc 4 làm vào một buổi tối rảnh, khi không ai dùng app. Lần đầu bạn làm quy trình này không nên là lúc thật sự có sự cố.