Vibe Code
Giáo trình/Phụ lục

Tám case thật, mổ xẻ

Tám dự án và sự cố có thật, mỗi cái kèm bối cảnh, cái sai, cái sửa, con số trước/sau, và bài học rút ra.

Tra cứu35 phút đọc

Mỗi case có cùng cấu trúc: bối cảnh · chuyện gì xảy ra · chữa thế nào · con số · bài học. Đọc để nhận ra mẫu, không phải để học thuộc.

Case 1 — App chấm công: 90 giây xuống 2 giây

Bối cảnh. App chấm công cho công ty ~300 người. Nhân viên chụp ảnh tại điểm làm việc, gửi kèm vị trí. Chạy 8 tháng.

Chuyện gì. Mở màn hình danh sách mất 26–90 giây. Xuất báo cáo thường hết giờ, lỗi. Nhân viên bỏ dùng, quay lại ghi giấy.

Nguyên nhân. Ảnh lưu dạng base64 trong cột TEXT của D1. Bảng 101 MB. Mỗi lần mở danh sách, truy vấn kéo về hàng chục MB — trong khi màn hình chỉ hiện 20 dòng.

Chữa (ba việc). 1. Chuyển ảnh sang R2, trong D1 chỉ giữ đường dẫn. 2. Danh sách chỉ trả các cột được vẽ, không SELECT *. 3. Phân trang 250 dòng, cộng một thẻ tóm tắt và cửa sổ mặc định 7 ngày.

Trước Sau
Cỡ D1 101 MB 18,9 MB
Mở danh sách 26–90 giây 1–3 giây
Dữ liệu mỗi lần mở ~4 MB ~80 KB

Bài học. D1 chậm theo byte trả về, không theo số dòng (~100 KB/giây). Cơ sở dữ liệu giữ sự thật về dữ liệu; kho tệp giữ tệp. → D1, KV & R2

Case 2 — Sáng không ai đăng nhập được

Bối cảnh. Cùng app trên, giờ cao điểm 7–8h sáng, hàng trăm nhân viên cùng vào.

Chuyện gì. Hàng loạt người nhận lỗi 429 Quá nhiều lượt. Kẹt màn hình tải. Xảy ra hai lần, cách nhau vài tuần.

Nguyên nhân. Trần rate limit đếm theo IP. Mạng 4G Việt Nam dùng NAT cấp nhà mạng — hàng trăm tới hàng nghìn người chung một IP công cộng. Cả trăm nhân viên chia nhau một trần.

Chữa. Người chưa có phiên: không đếm gì ngoài trần riêng cho cửa đăng nhập, khóa theo ip + số điện thoại. Có phiên: đếm theo phiên, và miễn đếm cho các đường rẻ (ảnh, sự kiện, giờ, phiên bản).

Bài học. Không bao giờ đếm theo IP trần cho luồng người thật ở Việt Nam. Và: an toàn được vì mọi đường tốn tài nguyên đều nằm sau tường xác thực. → Bảo mật & rate limit

Case 3 — App treo mà log không có lỗi

Bối cảnh. Một buổi chiều, cùng app.

Chuyện gì. Người dùng kẹt ở màn hình tải. Tỉ lệ lỗi trong bảng quan sát bình thường. Không đường API nào có vẻ chậm. Deploy gần nhất cách đó hai ngày — rollback không cứu được.

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ự trên cả cơ sở dữ liệu → mọi request khác xếp hàng chờ. Tệ hơn: biến điều tiết "chỉ chạy mỗi 10 phút" đặt ở tầng module, mà Cloudflare chạy nhiều bản mã song song — mỗi bản tưởng mình là lần đầu, nên nó chạy dày hơn nhiều lần dự tính.

Chữa. Bỏ việc nặng khỏi waitUntil, chuyển sang lệnh thủ công /admin. Bọc mọi batch đọc nóng bằng retry có lùi dần. Mọi fetch có hạn giờ + đường lui về trạng thái dùng được.

Bài học. Ba cái cùng lúc: việc nặng không nằm trong request nóng · biến tầng module không khóa được gì · app treo mà tỉ lệ lỗi bình thường là dấu hiệu nghẽn cơ sở dữ liệu, không phải lỗi mã. → Cron & việc nền, Quan sát sự cố

Case 4 — Lỗi đường ghi lọt lên bản thật

Bối cảnh. Quy trình deploy đã có bước test trên bản preview.

Chuyện gì. Bản mới lên, người dùng không lưu được dữ liệu. Lỗi "không ghi được vào cơ sở dữ liệu".

Nguyên nhân. Tài khoản dùng để test là tài khoản chỉ-xem. Mọi phép thử đều đi qua đường đọc, không đường ghi nào được chạm tới.

Chữa. Thêm tài khoản test ghi được vào quy trình. Bổ sung vào danh sách test: "test cả đường ĐỌC lẫn đường GHI".

Bài học. Test đi đúng đường mà bạn đã nghĩ tới thì không tìm ra gì. Danh sách test phải viết trước, và phải phủ cả đường ghi. → Test như kẻ phá hoại

Case 5 — Hai máy bán hàng làm mất món

Bối cảnh. App bán hàng cho quán ăn, hai máy cùng ghi đơn cho một bàn.

Chuyện gì. Món nhân viên A vừa thêm biến mất sau khi nhân viên B lưu. Không có cảnh báo nào. Bếp làm thiếu món, khách phàn nàn.

Nguyên nhân. Máy B tải đơn lúc chưa có món của A, sửa, rồi gửi lên toàn bộ đơn — ghi đè mất phần của A.

Chữa. Máy chủ từ chối bản ghi thiếu trường so với bản đang có. Mỗi lần lưu kèm mốc phiên bản; lệch thì báo "đơn vừa được người khác sửa, tải lại". Thêm cập nhật tức thì để hai máy thấy nhau.

Bài học. Chặn bản rút gọn ghi đè bản đầy đủ. Đây là kiểu mất dữ liệu không ai nghi ngờ, vì không có lỗi nào xuất hiện. → Sao lưu & chống ghi đè, Realtime

Case 6 — Đổi định tuyến làm mất sạch giao diện

Bối cảnh. Thử thêm một "cửa xoay" đổi tiền tố đường API để chống dò.

Chuyện gì. Deploy xong, app phục vụ file HTML thô, mất toàn bộ giao diện.

Nguyên nhân. Bước build thay chuỗi /api/ trong mã, và thao tác thay chuỗi đó phá luôn danh sách file mà hệ thống tách tài nguyên mong đợi. Cờ kiểm tra tài nguyên chuyển sang false, hệ thống lùi về phục vụ khung HTML trần.

Chữa. Revert. Và kết luận lại: /api/bootstrap khi chưa đăng nhập vốn đã vô hại (trả authenticated:false trong ~0,4 giây, không truy vấn D1 nặng) — cộng với trần rate limit là đủ, không cần cửa xoay.

Bài học. Thay đổi định tuyến hoặc cấu trúc file là RỦI RO CAO, dù mã đổi rất ít. Và: giải pháp an toàn nhất thường là không thêm lớp phức tạp mà chứng minh lớp hiện có đã đủ. → Quy trình deploy

Case 7 — Trợ lý AI bịa ra chính sách công ty

Bối cảnh. App tra cứu thông báo nội bộ, có hỏi đáp bằng AI trên kho văn bản của công ty.

Chuyện gì. Hôm nghiệm thu, hỏi một việc công ty chưa bao giờ ra thông báo: "Công ty có hỗ trợ tiền xăng không?" Nó trả lời rất trôi chảy, kèm cả mức tiền. Hoàn toàn bịa.

Chữa. Một câu trong hướng dẫn hệ thống: "chỉ dựa trên đoạn văn bản được cung cấp; nếu không có thì trả lời 'Tài liệu hiện không đề cập'; không đoán." Cộng với việc mọi câu trả lời bắt buộc kèm nguồn bấm được.

Bài học. Phép thử bắt buộc cho mọi tính năng AI: hỏi một câu mà dữ liệu không có đáp án. Tỉ lệ bịa phải bằng 0 trước khi giao. → Nhúng AI vào app

Case 8 — Bản sao lưu chạy 4 tháng và vô dụng

Bối cảnh. App có sao lưu tự động hằng đêm, chạy đều, không lỗi.

Chuyện gì. Chạy nhầm một lệnh xóa. Mở file sao lưu ra: chỉ có 3 bảng trong số 7.

Nguyên nhân. Script sao lưu viết lúc app mới có 3 bảng, và không bao giờ được cập nhật khi thêm bảng mới.

Chữa. Sao lưu đọc danh sách bảng động thay vì liệt kê cứng. Ghi nhật ký số dòng từng bảng mỗi lượt. Kiểm chứng ngay sau khi ghi: đọc lại file vừa ghi, đếm, so. Và định kỳ nạp thử vào cơ sở dữ liệu trống.

Bài học. Bản sao lưu chưa từng được phục hồi thử thì chưa phải bản sao lưu. → Sao lưu & khôi phục

Mẫu chung của tám case

Năm điểm lặp lạichuyên gia insight & case thực tế

Đọc tám case cạnh nhau thì thấy chúng không ngẫu nhiên:

  1. Sáu trên tám sự cố không hiện ra dưới dạng "lỗi". App vẫn trả 200, log vẫn sạch, tỉ lệ lỗi vẫn bình thường. Nên: đừng dựa vào bảng lỗi để biết app khỏe.

  2. Bốn case bắt nguồn từ một quyết định "cho tiện". Ảnh để luôn trong cơ sở dữ liệu cho gọn; dọn dẹp nhét vào waitUntil cho khỏi làm thêm cron; liệt kê bảng cứng cho nhanh; test bằng tài khoản sẵn có. Cái tiện hôm nay là cái đắt sáu tháng sau.

  3. Hai case là do "cải tiến" chứ không phải do tính năng mới. Thêm cửa xoay, dọn dẹp định kỳ. Thay đổi không ai yêu cầu là thay đổi rủi ro nhất — vì chẳng ai test nó kỹ.

  4. Ba case chỉ lộ ra khi có NHIỀU người dùng cùng lúc — 429 dây chuyền, nghẽn D1, hai máy ghi đè. Test một mình không bao giờ tìm ra. → phải test với tải thật hoặc ít nhất hai thiết bị.

  5. Không case nào do lỗ hổng bảo mật từ bên ngoài. Tất cả đều do quyết định thiết kế của chính người làm. Đó là tin tốt: chúng phòng được hết bằng danh sách kiểm.

Đọc case của người khác dễ hơn nhận ra của mìnhngười dùng bình thường góp ý

Tôi đọc tám case này và gật gù "ừ, rõ ràng quá". Rồi tôi mở app của mình ra và phát hiện tôi đang mắc case 1 — tôi lưu ảnh chữ ký dạng base64 trong cơ sở dữ liệu, vì lúc viết thấy tiện.

Nên tôi khuyên: đừng đọc bài này một lượt rồi thôi. Đọc từng case, xong mỗi case thì mở app của mình ra kiểm. Mất một buổi, và gần như chắc chắn bạn tìm ra ít nhất một cái đang có.

Chốt review bài nàychuyên gia review
  • Nguồn: tám case đều từ app thật đang chạy, con số là số đo thật.
  • Cố ý chọn case thất bại nhiều hơn case thành công — case thành công dạy được ít hơn, và dễ gây ảo tưởng rằng có một công thức đúng.
  • Rủi ro còn lại: tám case này đến từ một bối cảnh: app nội bộ cho doanh nghiệp vừa và nhỏ ở Việt Nam, trên Cloudflare. Mẫu lặp lại thì chung, nhưng con số cụ thể thì không nên mang sang bối cảnh khác.
Bài tập chốt

Với mỗi case, mở app của bạn ra và trả lời đúng một câu:

  1. Có dữ liệu lớn nào nằm trong cơ sở dữ liệu mà đáng lẽ phải ở kho tệp không?
  2. Trần rate limit của tôi đếm theo gì?
  3. Có việc nặng nào trong ctx.waitUntil không?
  4. Tài khoản tôi dùng để test có ghi được không?
  5. Hai người cùng sửa một bản ghi thì ai thắng, và người thua có biết không?
  6. Thay đổi gần nhất của tôi có đụng tới định tuyến hoặc cấu trúc file không?
  7. (Nếu có AI) Hỏi một câu không có đáp án — nó có bịa không?
  8. Bản sao lưu mới nhất có đủ bảng không, và đã nạp thử chưa?

Câu nào trả lời không được là một việc cần làm ngay tuần này.