🔔 Đặt ngân sách và cảnh báo khi vượt

By Better Wise

Bài trước bạn đã có hai con số: chi phí cho mỗi request và chi phí dự phóng cho cả tháng. Giờ là lúc biến chúng thành hàng rào. Bài học này dạy bạn cách chia ngân sách thành nhiều tầng, gắn cảnh báo ở đúng chỗ, và chuẩn bị sẵn phương án hạ cấp hoặc chặn trước khi hóa đơn kịp gửi tới.

Sau bài này, bạn sẽ:

  • Chia ngân sách thành bốn tầng: theo user, theo feature, toàn hệ thống, và hard limit ở phía provider.
  • Đặt ngưỡng cảnh báo theo phần trăm và theo tốc độ chi tiêu.
  • Viết được thang hạ cấp trước khi phải chặn người dùng.
  • Biết chỗ đặt kill switch và cách tránh alert fatigue.
  • Có sẵn các con số để đưa vào project ở bài sau.

Ngân sách nhiều tầng: đặt giới hạn trước khi mở cửa

Nhắc lại nhanh phép tính của bài trước: một request khoảng 2.000 token input và 500 token output, với giá $0,15/1M input và $0,60/1M output, ra khoảng $0,0006 mỗi request. Nghĩa là 10.000 request ≈ $6, và 300.000 request ≈ $180. Con số này là nền cho mọi hạn mức bên dưới.

Một ngân sách tử tế không phải một con số duy nhất. Nó là bốn tầng lồng nhau:

Tầng Ví dụ hạn mức Chặn được điều gì
Theo user $0,50/ngày (~830 request) một user không kéo sập cả hệ thống
Theo feature $50/tháng biết tính năng nào đang đốt tiền
Toàn hệ thống $200/tháng trần ngân sách thật của bạn
Hard limit ở provider $250/tháng chốt chặn cuối, đặt trong dashboard provider

Tầng cuối là tầng quan trọng nhất và cũng hay bị bỏ qua nhất. Code của bạn có thể có bug, có thể bị bypass, có thể bị một vòng lặp retry đốt sạch trong đêm. Dashboard của provider thì không quan tâm code của bạn viết gì.

Important: Đặt hard limit ở provider trước, viết code sau. Code có bug, còn con số bạn nhập vào dashboard thì không.

Một mẹo nhỏ: đặt hard limit ở provider thấp hơn 20 đến 30% so với ngân sách bạn thật sự chịu được, để phần dư bù cho độ trễ và các khoản phát sinh.

Bài tập nhanh: với $0,0006/request, hạn mức $1/ngày cho mỗi user tương ứng bao nhiêu request? Nếu bạn có 200 user active, kịch bản xấu nhất trong một ngày là bao nhiêu tiền, và nó có phá ngân sách tháng $200 không?

Xem gợi ý

$1 / $0,0006 ≈ 1.660 request/ngày cho mỗi user. Kịch bản xấu nhất: 200 × $1 = $200/ngày, tức là đúng bằng ngân sách cả tháng. Kết luận: hạn mức theo user phải đi kèm hạn mức toàn hệ thống, nếu không bạn chỉ đang chia nhỏ một thảm họa.

Cảnh báo: phần trăm chưa đủ, phải nhìn tốc độ chi tiêu

Bộ ngưỡng cơ bản là 50%, 80% và 100% ngân sách. Nhưng cảnh báo theo phần trăm có một điểm mù: nó báo muộn. Ngày 5 của tháng mà bạn đã tiêu 60% ngân sách thì hệ thống vẫn "chưa vượt ngưỡng 80%", trong khi thực tế bạn đang chi gấp 3,6 lần kế hoạch.

Vì vậy cần thêm cảnh báo theo burn rate:

$$ \text{Chi tiêu dự phóng} = \frac{\text{Chi tiêu đến hiện tại}}{\text{Số ngày đã qua}} \times \text{Số ngày trong kỳ} $$

Nếu chi tiêu dự phóng vượt 100% ngân sách, bắn cảnh báo ngay, bất kể đang ở ngưỡng phần trăm nào. Một cảnh báo dùng được phải có đủ: số tiền, đối tượng gây ra (user hay feature nào), và việc cần làm. Cảnh báo chỉ nói "chi phí tăng" là cảnh báo sẽ bị bỏ qua sau ba lần.

Để tránh alert fatigue: gộp các cảnh báo trùng trong một cửa sổ thời gian, chỉ bắn lại khi vượt ngưỡng tiếp theo, và gửi vào nơi bạn thật sự nhìn (Slack, email, hay một tin nhắn điện thoại).

// Kiểm tra trước khi gọi provider: rẻ hơn nhiều so với gọi rồi mới biết
function canSpend(userId, estimatedCost) {
  const day = usage.today(userId);      // USD user này đã tiêu hôm nay
  const month = usage.monthGlobal();    // USD toàn hệ thống trong tháng
  if (day + estimatedCost > LIMITS.userDaily) {
    return { ok: false, reason: "user_daily" };
  }
  if (month + estimatedCost > LIMITS.globalMonthly) {
    return { ok: false, reason: "global_monthly" };
  }
  return { ok: true };
}

Kiểm tra này nên chạy ở tầng API gateway, trước khi request chạm tới provider. Bạn cũng nên log chi phí ước tính tại chỗ, vì dashboard của provider thường có độ trễ (có nơi tới 24 giờ).

Cảnh báo nên đặt ở ba ngưỡng _____, 80 và _____ phần trăm ngân sách. Vì cảnh báo theo phần trăm báo muộn, cần thêm cảnh báo theo _____, tức tốc độ chi tiêu dự phóng. Trước khi chặn người dùng, hãy _____ xuống model rẻ hơn, cắt ngắn prompt hoặc dùng cache. Khi trả lỗi 429, nên kèm header _____ để client biết khi nào được thử lại. Hạn mức theo user giúp một người không thể _____ ngân sách của toàn hệ thống.

Bài tập nhanh: hôm nay là ngày 10/30, hệ thống đã tiêu $70 trên ngân sách $200. Chi tiêu dự phóng cuối tháng là bao nhiêu, và bạn nên làm gì?

Xem gợi ý

$70 / 10 × 30 = $210, tức vượt 5% ngân sách, dù mới ở ngưỡng 35%. Đây là lúc cảnh báo burn rate phát huy tác dụng. Hành động hợp lý: kiểm tra feature nào tăng, hạ model cho các tác vụ không cần thiết, và đặt cảnh báo chặt hơn cho 10 ngày tới.

Chạm trần rồi thì sao: hạ cấp trước, chặn sau

Chặn là phương án cuối, không phải phương án đầu. Thang hạ cấp nên đi theo thứ tự từ rẻ nhất về mặt trải nghiệm:

flowchart TD
  A["💸 Request tới"] --> B{"Còn ngân sách?"}
  B -- Có --> C["🤖 Gọi model bình thường"]
  B -- Không --> D["⚡ Cache, model rẻ hơn, cắt prompt"]
  D --> E{"Vẫn vượt?"}
  E -- Không --> F["✅ Trả kết quả"]
  E -- Có --> G["⏳ Queue hoặc 429 kèm Retry-After"]

Ba điểm cần nhớ:

  1. Chỉ chặn đúng đối tượng vượt hạn mức. Một user chạm trần thì trả 429 cho user đó, không tắt cả hệ thống.
  2. Fail closed với các request đắt. Nếu không đọc được số liệu usage (DB lỗi, cache mất), hãy chặn thay vì cho qua. Hóa đơn không chờ bạn sửa database.
  3. Luôn có kill switch. Một biến môi trường hoặc flag trong config cho phép tắt toàn bộ lời gọi provider trong vòng 30 giây, không cần deploy. Hãy tập bật thử một lần khi hệ thống đang bình thường, để lúc có sự cố bạn không phải vừa học vừa làm.

Bài tập nhanh: ngày 20, ngân sách tháng đã chạm 100% và bạn còn 10 ngày. Hãy xếp thứ tự bốn hành động: chặn toàn bộ, chuyển sang model rẻ hơn, bật cache cho các câu hỏi lặp, thông báo cho người dùng về hạn mức tạm thời.

Xem gợi ý

Thứ tự hợp lý: bật cache (rẻ nhất, không ai bị ảnh hưởng), chuyển model rẻ hơn cho tác vụ không cần chất lượng cao, thông báo hạn mức tạm thời cho người dùng, và chỉ chặn ở cuối với những request vẫn vượt. Chặn toàn bộ ngay từ đầu là cách nhanh nhất để mất người dùng mà không cần thiết.

Vài thói quen để không bao giờ bị hóa đơn bất ngờ

  • Đặt hard limit ở provider thấp hơn ngân sách thật 20 đến 30%. Đây là chốt chặn cuối không phụ thuộc vào code của bạn.
  • Cảnh báo phải có số tiền, đối tượng gây ra và việc cần làm. Không có ba thứ đó thì đừng bắn.
  • Đừng chỉ đặt cap toàn hệ thống. Đó là cách để một user đốt hết ngân sách của mọi người.
  • Đừng chỉ tin dashboard của provider, vì độ trễ có thể lên tới 24 giờ. Log chi phí ước tính ngay trong hệ thống của bạn.
  • Rule of thumb: hạn mức ngày cho mỗi user = ngân sách tháng / 30 × 2, đủ chỗ cho ngày cao điểm nhưng không vượt xa.
  • Viết kill switch từ ngày đầu tiên, và thử bật nó một lần khi chưa có sự cố.

Ba hàng rào bạn vừa dựng xong

  • Ngân sách bốn tầng: user, feature, toàn hệ thống, và hard limit ở provider.
  • Cảnh báo ở 50/80/100% cộng thêm cảnh báo burn rate để không bị báo muộn.
  • Thang hạ cấp: cache, model rẻ hơn, cắt prompt, queue, rồi mới tới 429. Luôn kèm kill switch.

Ở bài tiếp theo, bạn sẽ tự tay lắp ba hàng rào này vào project: dựng lớp bảo vệ trước provider và một bảng theo dõi chi phí hiển thị đúng những con số bạn vừa học cách đặt.

Front: Vì sao phải đặt hard limit ở phía provider? Back: Vì code có thể có bug hoặc bị bypass; hard limit trong dashboard provider là chốt chặn cuối không phụ thuộc code của bạn.

Front: Bốn tầng ngân sách nên có là gì? Back: Theo user, theo feature, toàn hệ thống, và hard limit ở provider.

Front: Ba ngưỡng cảnh báo phần trăm phổ biến? Back: 50%, 80% và 100% ngân sách.

Front: Vì sao cảnh báo theo phần trăm là chưa đủ? Back: Vì nó báo muộn; cần thêm cảnh báo theo burn rate, tức tốc độ chi tiêu dự phóng.

Front: Công thức chi tiêu dự phóng cuối kỳ? Back: Chi tiêu đến hiện tại chia số ngày đã qua, nhân số ngày trong kỳ.

Front: Một cảnh báo dùng được phải có gì? Back: Số tiền, đối tượng gây ra (user hay feature), và việc cần làm.

Front: Thang hạ cấp trước khi chặn gồm những bước nào? Back: Cache, model rẻ hơn, cắt ngắn prompt và max_tokens, queue, cuối cùng mới là 429.

Front: Khi trả lỗi 429 nên kèm gì? Back: Header Retry-After và lý do rõ ràng (hết hạn mức).

Front: Một user vượt hạn mức thì chặn ai? Back: Chỉ user hoặc feature đó, không chặn toàn hệ thống.

Front: Kill switch dùng để làm gì? Back: Tắt ngay toàn bộ lời gọi provider đắt tiền khi chi tiêu bất thường, không cần deploy.

Front: Vì sao phải log chi phí tại chỗ thay vì chỉ xem dashboard provider? Back: Dashboard provider thường có độ trễ, có khi tới 24 giờ, không đủ nhanh để chặn kịp.

Front: Nếu không đọc được số liệu usage, nên cho qua hay chặn request đắt? Back: Fail closed, tức chặn, vì hóa đơn không chờ bạn sửa database.

Front: Rule of thumb cho hạn mức ngày của mỗi user? Back: Ngân sách tháng chia 30, nhân 2.