Berapa banyak kegagalan solve CAPTCHA yang masih boleh terjadi sebelum sistem Anda dianggap bermasalah? Error budget menjawab pertanyaan itu dengan angka, bukan perasaan. Alih-alih menebak-nebak begitu dashboard menunjukkan warna merah, Anda menetapkan SLO, menghitung sisa jatah kegagalan, dan tahu persis kapan harus menahan deployment — bukan setelah tim support kebanjiran tiket.
Bayangkan tim automation di Jakarta yang menjalankan seribuan task solve Turnstile per jam dari worker di AWS ap-southeast-1: target SLO 95%, tapi realisasi minggu ini 94,2%. Tanpa error budget, angka itu cuma terasa "agak turun". Dengan error budget, Anda tahu persis apakah ini masih dalam toleransi atau sudah waktunya menahan perubahan berisiko.
Apa Itu Error Budget dalam Solve CAPTCHA?
- SLO — target tingkat keberhasilan solve yang disepakati. Contoh: 95% solve harus berhasil.
- Error budget — jatah kegagalan yang masih ditoleransi sebelum SLO dianggap dilanggar. Contoh: 5% dari total solve boleh gagal.
- Burn rate — kecepatan jatah kegagalan itu terpakai. Contoh: 2× artinya budget habis di separuh window.
- Window — rentang waktu pengukuran. Contoh: rolling 24 jam atau 7 hari.
Contoh konkret: SLO 95% pada window 24 jam dengan 10.000 solve berarti jatah kegagalan Anda 500 kali. Begitu counter kegagalan menyentuh angka 500, deployment baru dan perubahan konfigurasi berisiko harus dihentikan dulu.
Aturan praktis: begitu counter kegagalan menyentuh batas error budget, deployment baru dan perubahan berisiko berhenti dulu — bukan keputusan yang diambil panik di tengah insiden, tapi aturan main yang sudah disepakati sejak awal.
Ambang Alert Berdasarkan Burn Rate
Burn rate memberi tahu Anda seberapa cepat jatah kegagalan itu terpakai dibanding laju normal. Gunakan ambang berikut untuk menentukan respons yang tepat sebelum budget benar-benar habis:
| Burn rate | Maknanya | Langkah yang Diambil |
|---|---|---|
| < 1.0 | Konsumsi budget lebih lambat dari perkiraan | Tidak perlu tindakan khusus |
| 1.0 | Budget diperkirakan pas habis di akhir window | Pantau lebih ketat |
| 2.0 | Budget habis di separuh window | Selidiki penyebabnya, perlambat traffic |
| ≥ 5.0 | Konsumsi budget sangat cepat, tanda bahaya | Pause solve non-kritis segera |
Membangun Error Budget Tracker CAPTCHA dengan Python
Tracker berikut menghitung jatah kegagalan secara real-time dari deque event bertanda waktu, lalu memicu callback begitu status berubah — dari healthy ke warning, critical, sampai exhausted. Cocok dijalankan berdampingan dengan fungsi solve_with_budget yang memanggil in.php dan res.php CaptchaAI:
import time
import threading
from dataclasses import dataclass, field
from collections import deque
from enum import Enum
API_KEY = "YOUR_API_KEY"
class BudgetStatus(Enum):
HEALTHY = "healthy" # Budget > 50% remaining
WARNING = "warning" # Budget 10-50% remaining
CRITICAL = "critical" # Budget < 10% remaining
EXHAUSTED = "exhausted" # Budget depleted
@dataclass
class SLOConfig:
"""Service Level Objective configuration."""
target_success_rate: float = 0.95 # 95%
window_seconds: int = 86400 # 24 hours
warning_threshold: float = 0.50 # Alert at 50% budget
critical_threshold: float = 0.10 # Alert at 10% budget
@dataclass
class ErrorBudgetEvent:
timestamp: float
success: bool
class ErrorBudgetTracker:
"""Tracks error budget consumption for CAPTCHA solving."""
def __init__(self, config: SLOConfig = SLOConfig()):
self.config = config
self._events: deque[ErrorBudgetEvent] = deque()
self._lock = threading.Lock()
self._callbacks: dict[BudgetStatus, list[callable]] = {
status: [] for status in BudgetStatus
}
self._last_status = BudgetStatus.HEALTHY
def on_status_change(self, status: BudgetStatus, callback: callable):
"""Register a callback for status transitions."""
self._callbacks[status].append(callback)
def record(self, success: bool):
"""Record a solve attempt."""
now = time.monotonic()
event = ErrorBudgetEvent(timestamp=now, success=success)
with self._lock:
self._events.append(event)
self._prune(now)
new_status = self._compute_status()
if new_status != self._last_status:
self._last_status = new_status
for cb in self._callbacks.get(new_status, []):
try:
cb(self.get_report())
except Exception as e:
print(f"[BUDGET] Callback error: {e}")
def _prune(self, now: float):
"""Remove events outside the window."""
cutoff = now - self.config.window_seconds
while self._events and self._events[0].timestamp < cutoff:
self._events.popleft()
def _compute_status(self) -> BudgetStatus:
remaining = self.remaining_fraction
if remaining <= 0:
return BudgetStatus.EXHAUSTED
if remaining < self.config.critical_threshold:
return BudgetStatus.CRITICAL
if remaining < self.config.warning_threshold:
return BudgetStatus.WARNING
return BudgetStatus.HEALTHY
@property
def total_events(self) -> int:
with self._lock:
return len(self._events)
@property
def success_count(self) -> int:
with self._lock:
return sum(1 for e in self._events if e.success)
@property
def failure_count(self) -> int:
with self._lock:
return sum(1 for e in self._events if not e.success)
@property
def current_success_rate(self) -> float:
total = self.total_events
return self.success_count / total if total > 0 else 1.0
@property
def error_budget_total(self) -> float:
"""Total allowed failures in the window."""
total = self.total_events
if total == 0:
return 0
return total * (1 - self.config.target_success_rate)
@property
def error_budget_remaining(self) -> float:
"""Remaining failure allowance."""
return max(0, self.error_budget_total - self.failure_count)
@property
def remaining_fraction(self) -> float:
"""Fraction of error budget remaining (0.0 to 1.0)."""
budget = self.error_budget_total
if budget <= 0:
return 1.0 if self.failure_count == 0 else 0.0
return max(0, self.error_budget_remaining / budget)
@property
def burn_rate(self) -> float:
"""How fast the budget is being consumed (1.0 = normal, 2.0 = 2× faster)."""
total = self.total_events
if total == 0:
return 0.0
expected_failures = total * (1 - self.config.target_success_rate)
if expected_failures == 0:
return 0.0
return self.failure_count / expected_failures
def get_report(self) -> dict:
return {
"status": self._last_status.value,
"slo_target": self.config.target_success_rate,
"current_rate": round(self.current_success_rate, 4),
"total_events": self.total_events,
"successes": self.success_count,
"failures": self.failure_count,
"budget_total": round(self.error_budget_total, 1),
"budget_remaining": round(self.error_budget_remaining, 1),
"budget_remaining_pct": round(self.remaining_fraction * 100, 1),
"burn_rate": round(self.burn_rate, 2),
}
# --- Integration with solver ---
budget = ErrorBudgetTracker(SLOConfig(
target_success_rate=0.95,
window_seconds=3600, # 1-hour window for demo
))
# Register alerts
budget.on_status_change(BudgetStatus.WARNING, lambda r:
print(f"[ALERT] Budget warning: {r['budget_remaining_pct']}% remaining"))
budget.on_status_change(BudgetStatus.CRITICAL, lambda r:
print(f"[ALERT] Budget critical: {r['budget_remaining_pct']}% remaining"))
budget.on_status_change(BudgetStatus.EXHAUSTED, lambda r:
print(f"[ALERT] Budget EXHAUSTED — throttle new requests"))
def solve_with_budget(params: dict) -> str:
"""Solve CAPTCHA while tracking error budget."""
import requests
if budget._last_status == BudgetStatus.EXHAUSTED:
raise RuntimeError("Error budget exhausted — solving paused")
try:
submit_params = {**params, "key": API_KEY, "json": 1}
resp = requests.post(
"https://ocr.captchaai.com/in.php", data=submit_params, timeout=30
).json()
if resp.get("status") != 1:
budget.record(False)
raise RuntimeError(f"Submit: {resp.get('request')}")
task_id = resp["request"]
start = time.monotonic()
while time.monotonic() - start < 180:
time.sleep(5)
poll = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
budget.record(True)
return poll["request"]
budget.record(False)
raise RuntimeError(f"Solve: {poll.get('request')}")
budget.record(False)
raise RuntimeError("Timeout")
except Exception:
budget.record(False)
raise
# Usage
for i in range(100):
try:
token = solve_with_budget({
"method": "turnstile",
"sitekey": "0x4XXXXXXXXXXXXXXXXX",
"pageurl": "https://example.com",
})
except RuntimeError as e:
if "exhausted" in str(e):
print(f"Stopped at iteration {i}")
break
print(budget.get_report())
Versi JavaScript: Error Budget Tracker CAPTCHA untuk Node.js
Tim yang menjalankan worker solve di Node.js bisa pakai versi berikut — logikanya identik dengan versi Python, hanya memakai private class field dan callback per status:
class ErrorBudgetTracker {
#events = [];
#config;
#callbacks = {};
constructor(config = {}) {
this.#config = {
targetRate: config.targetRate || 0.95,
windowMs: config.windowMs || 3600_000,
warningThreshold: config.warningThreshold || 0.5,
criticalThreshold: config.criticalThreshold || 0.1,
};
this.lastStatus = "healthy";
}
on(status, callback) {
this.#callbacks[status] = this.#callbacks[status] || [];
this.#callbacks[status].push(callback);
}
record(success) {
const now = Date.now();
this.#events.push({ time: now, success });
this.#prune(now);
const newStatus = this.#computeStatus();
if (newStatus !== this.lastStatus) {
this.lastStatus = newStatus;
for (const cb of this.#callbacks[newStatus] || []) {
cb(this.report());
}
}
}
#prune(now) {
const cutoff = now - this.#config.windowMs;
while (this.#events.length && this.#events[0].time < cutoff) {
this.#events.shift();
}
}
#computeStatus() {
const frac = this.remainingFraction;
if (frac <= 0) return "exhausted";
if (frac < this.#config.criticalThreshold) return "critical";
if (frac < this.#config.warningThreshold) return "warning";
return "healthy";
}
get total() { return this.#events.length; }
get successes() { return this.#events.filter((e) => e.success).length; }
get failures() { return this.#events.filter((e) => !e.success).length; }
get currentRate() { return this.total ? this.successes / this.total : 1; }
get budgetTotal() {
return this.total * (1 - this.#config.targetRate);
}
get budgetRemaining() {
return Math.max(0, this.budgetTotal - this.failures);
}
get remainingFraction() {
const bt = this.budgetTotal;
if (bt <= 0) return this.failures === 0 ? 1 : 0;
return Math.max(0, this.budgetRemaining / bt);
}
get burnRate() {
const expected = this.total * (1 - this.#config.targetRate);
return expected > 0 ? this.failures / expected : 0;
}
report() {
return {
status: this.lastStatus,
currentRate: Math.round(this.currentRate * 10000) / 10000,
total: this.total,
failures: this.failures,
budgetRemainingPct: Math.round(this.remainingFraction * 1000) / 10,
burnRate: Math.round(this.burnRate * 100) / 100,
};
}
}
// Usage
const budget = new ErrorBudgetTracker({ targetRate: 0.95, windowMs: 3600_000 });
budget.on("warning", (r) => console.log(`[WARN] ${r.budgetRemainingPct}% budget left`));
budget.on("exhausted", (r) => console.log("[ALERT] Budget exhausted!"));
// Record results from your solver
budget.record(true); // success
budget.record(false); // failure
console.log(budget.report());
Troubleshooting Error Budget yang Meleset
Masalah yang paling sering muncul di lapangan — termasuk saat traffic melonjak mendadak, misalnya saat kampanye flash sale seperti Harbolnas atau 11.11 ketika volume solve naik drastis dalam hitungan menit:
| Masalah | Penyebab | Perbaikan |
|---|---|---|
| Budget habis terlalu cepat | SLO terlalu ketat untuk kondisi aktual | Tetapkan SLO realistis berdasarkan data historis |
| Budget tidak pernah habis | SLO terlalu longgar | Perketat SLO untuk mendorong peningkatan keandalan |
| Status berfluktuasi terus | Window terlalu pendek | Gunakan window pengukuran lebih panjang (24 jam vs 1 jam) |
| Burn rate menyesatkan pada volume rendah | Sedikit event menyimpangkan perhitungan | Wajibkan jumlah event minimum sebelum menghitung burn rate |
| Memory tracker terus bertambah | Event tidak di-prune | Pastikan _prune berjalan setiap kali record() dipanggil |
Tanya Jawab Error Budget CAPTCHA
Berapa lama window pengukuran yang tepat untuk error budget CAPTCHA?
Untuk kebanyakan tim automation, window rolling 24 jam sudah cukup stabil — cukup panjang untuk meredam fluktuasi jam sibuk vs jam sepi, tapi tetap responsif untuk menangkap masalah dalam satu hari kerja. Tim dengan volume solve sangat tinggi (puluhan ribu per hari) bisa mempersempit ke window 6–12 jam; tim dengan volume rendah sebaiknya memakai window 7 hari supaya jumlah event yang terhitung cukup banyak untuk bermakna secara statistik.
Berapa target SLO yang realistis untuk solve CAPTCHA?
Tergantung tipe CAPTCHA yang Anda proses. reCAPTCHA v2 biasanya mencapai tingkat keberhasilan 90–95%, Turnstile cenderung lebih tinggi, sementara image CAPTCHA bervariasi tergantung kualitas gambar. Ukur dulu baseline tingkat keberhasilan Anda saat ini selama beberapa minggu, lalu tetapkan SLO 2–3% di bawah baseline itu — supaya error budget-nya benar-benar bermakna, bukan angka yang selalu longgar atau selalu habis.
Apa yang harus dilakukan saat error budget CAPTCHA habis?
Ada beberapa opsi, dari paling ringan sampai paling agresif: kirim alert ke tim, throttle request baru, pause solve yang tidak kritis, atau sementara alihkan ke penanganan CAPTCHA manual. Karena CaptchaAI menagih per thread dan bukan per solve, menjeda task yang tidak kritis saat budget kritis tidak membuang kuota yang sudah dibayar — thread yang menganggur tinggal menunggu task berikutnya. Yang tidak boleh dilakukan: mendiamkan budget yang sudah habis dan membiarkan pipeline berjalan seperti biasa.
Apakah error budget tracking butuh tool monitoring terpisah seperti Prometheus atau Grafana?
Tidak wajib untuk mulai. Tracker Python atau JavaScript pada panduan ini sudah cukup untuk pipeline skala kecil-menengah — cukup simpan hasil get_report() atau report() ke log atau database Anda. Begitu volume solve naik dan Anda butuh dashboard historis serta alerting multi-channel, barulah menyambungkan metrik ini ke Prometheus dan Grafana jadi masuk akal.
Bagaimana menyesuaikan error budget saat volume solve masih kecil?
Pada volume rendah (di bawah beberapa ratus solve per window), burn rate gampang menyesatkan karena satu-dua kegagalan bisa melonjakkan angkanya drastis. Solusinya: perpanjang window pengukuran, dan tetapkan jumlah event minimum (misalnya 100 solve) sebelum sistem mulai menghitung dan melaporkan burn rate.
Artikel Terkait
Langkah Selanjutnya
Lacak keandalan solve CAPTCHA Anda dengan angka, bukan tebakan — dapatkan API key CaptchaAI Anda dan pasang error budget tracker ini di pipeline Anda.
Panduan terkait: