API Tutorials

Pola Circuit Breaker untuk Panggilan API CAPTCHA

CaptchaAI menagih per thread aktif, bukan per solve — thread yang terus memanggil API bermasalah adalah thread yang Anda bayar tapi tak menghasilkan token. Circuit breaker menghentikan pemanggilan API yang gagal, menunggu masa pemulihan, lalu melanjutkan otomatis begitu API pulih — pipeline Anda tidak ikut jatuh saat satu dependency eksternal bermasalah.

Pola ini paling terasa manfaatnya untuk pipeline yang menjalankan banyak task paralel — scraper harga, job antrean pendaftaran, atau automasi form volume tinggi — di mana satu dependency eksternal yang lambat bisa menguras kapasitas thread dalam hitungan menit kalau tidak ada mekanisme berhenti otomatis.

Ringkas: circuit breaker menghemat thread CaptchaAI dengan menghentikan panggilan API yang hampir pasti gagal, lalu melanjutkan otomatis begitu API pulih — tanpa campur tangan manual.


Tiga State Circuit Breaker: Closed, Open, Half-Open

  1. Closed — Kondisi normal. Setiap request diteruskan ke API; setiap kegagalan dicatat.
  2. Open — Ambang kegagalan terlampaui. Semua request ditolak instan tanpa menyentuh API — tidak ada thread terbuang untuk request yang hampir pasti gagal.
  3. Half-Open — Setelah recovery_timeout berlalu, satu request percobaan dilepas. Berhasil → sirkuit closed lagi. Gagal → open lagi, cooldown dimulai ulang.
State Kondisi trigger Perilaku request
Closed Kegagalan di bawah failure_threshold Diteruskan normal ke API
Open Kegagalan mencapai failure_threshold Ditolak instan, tanpa memanggil API
Half-Open recovery_timeout sudah terlampaui Satu request percobaan dilepas untuk uji pemulihan

Transisi state ini seluruhnya berjalan otomatis di dalam class CircuitBreaker — pipeline Anda tidak perlu logika tambahan di luar pemanggilan breaker.call().


Implementasi Python: Class CircuitBreaker untuk reCAPTCHA

import time
import threading
import requests

SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
API_KEY = "YOUR_API_KEY"


class CircuitBreaker:
    def __init__(self, failure_threshold=5, recovery_timeout=60):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.failure_count = 0
        self.last_failure_time = 0
        self.state = "closed"  # closed, open, half-open
        self._lock = threading.Lock()

    def call(self, func, *args, **kwargs):
        with self._lock:
            if self.state == "open":
                if time.time() - self.last_failure_time > self.recovery_timeout:
                    self.state = "half-open"
                    print("[circuit] State: half-open — testing one request")
                else:
                    remaining = self.recovery_timeout - (
                        time.time() - self.last_failure_time
                    )
                    raise CircuitOpenError(
                        f"Circuit open — retry in {remaining:.0f}s"
                    )

        try:
            result = func(*args, **kwargs)
            with self._lock:
                self.failure_count = 0
                if self.state == "half-open":
                    print("[circuit] State: closed — API recovered")
                self.state = "closed"
            return result
        except Exception as e:
            with self._lock:
                self.failure_count += 1
                self.last_failure_time = time.time()
                if self.failure_count >= self.failure_threshold:
                    self.state = "open"
                    print(
                        f"[circuit] State: open — "
                        f"{self.failure_count} failures"
                    )
            raise


class CircuitOpenError(Exception):
    pass


def solve_captcha(sitekey, page_url):
    resp = requests.post(SUBMIT_URL, data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "json": "1",
    }, timeout=15)
    data = resp.json()
    if data["status"] != 1:
        raise Exception(f"Submit error: {data['request']}")

    task_id = data["request"]
    for _ in range(24):
        time.sleep(5)
        poll = requests.get(RESULT_URL, params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": "1",
        }, timeout=15).json()
        if poll["status"] == 1:
            return poll["request"]
        if poll["request"] != "CAPCHA_NOT_READY":
            raise Exception(f"Poll error: {poll['request']}")
    raise TimeoutError(f"Task {task_id} timed out")


# Usage
breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=30)

for i in range(10):
    try:
        token = breaker.call(
            solve_captcha, "6Le-SITEKEY", "https://example.com"
        )
        print(f"[task-{i}] Solved: {token[:40]}...")
    except CircuitOpenError as e:
        print(f"[task-{i}] Skipped: {e}")
    except Exception as e:
        print(f"[task-{i}] Failed: {e}")

Hasil yang diharapkan:

[task-0] Solved: 03AGdBq26ZfPxL...
[task-1] Solved: 03AGdBq27AbCdE...
[task-2] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[task-3] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[task-4] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[circuit] State: open — 3 failures
[task-5] Skipped: Circuit open — retry in 28s
[task-6] Skipped: Circuit open — retry in 25s
...
[circuit] State: half-open — testing one request
[task-8] Solved: 03AGdBq28FgHiJ...
[circuit] State: closed — API recovered

Implementasi Node.js dengan Axios

class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.recoveryTimeout = options.recoveryTimeout || 60000;
    this.failureCount = 0;
    this.lastFailureTime = 0;
    this.state = 'closed';
  }

  async call(fn, ...args) {
    if (this.state === 'open') {
      if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
        this.state = 'half-open';
        console.log('[circuit] State: half-open');
      } else {
        const remaining = this.recoveryTimeout - (Date.now() - this.lastFailureTime);
        throw new Error(`Circuit open — retry in ${Math.ceil(remaining / 1000)}s`);
      }
    }

    try {
      const result = await fn(...args);
      this.failureCount = 0;
      if (this.state === 'half-open') {
        console.log('[circuit] State: closed — recovered');
      }
      this.state = 'closed';
      return result;
    } catch (error) {
      this.failureCount++;
      this.lastFailureTime = Date.now();
      if (this.failureCount >= this.failureThreshold) {
        this.state = 'open';
        console.log(`[circuit] State: open — ${this.failureCount} failures`);
      }
      throw error;
    }
  }
}

// Usage
const axios = require('axios');

const API_KEY = 'YOUR_API_KEY';
const breaker = new CircuitBreaker({ failureThreshold: 3, recoveryTimeout: 30000 });

async function solveCaptcha(sitekey, pageurl) {
  const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
    params: { key: API_KEY, method: 'userrecaptcha', googlekey: sitekey, pageurl, json: 1 }
  });

  if (submit.data.status !== 1) throw new Error(submit.data.request);
  const taskId = submit.data.request;

  for (let i = 0; i < 24; i++) {
    await new Promise(r => setTimeout(r, 5000));
    const poll = await axios.get('https://ocr.captchaai.com/res.php', {
      params: { key: API_KEY, action: 'get', id: taskId, json: 1 }
    });
    if (poll.data.status === 1) return poll.data.request;
    if (poll.data.request !== 'CAPCHA_NOT_READY') throw new Error(poll.data.request);
  }
  throw new Error('Timeout');
}

(async () => {
  for (let i = 0; i < 10; i++) {
    try {
      const token = await breaker.call(solveCaptcha, '6Le-SITEKEY', 'https://example.com');
      console.log(`[task-${i}] Solved: ${token.substring(0, 40)}...`);
    } catch (err) {
      console.log(`[task-${i}] ${err.message}`);
    }
  }
})();

Menentukan failure_threshold dan recovery_timeout yang Tepat

Parameter Traffic rendah (< 10/mnt) Traffic tinggi (> 100/mnt)
failure_threshold 3 10
recovery_timeout 30s 60s

Angka failure_threshold harus cukup tinggi untuk menoleransi error intermiten, tapi cukup rendah agar pipeline berhenti menghajar API yang memang bermasalah.

Contoh kasus: tim automation yang menjalankan scraper harga e-commerce dari region AWS ap-southeast-3 (Jakarta) biasanya start dari paket STANDARD ($30/bulan, 15 thread). Saat API melambat, circuit breaker mencegah scraper memboroskan thread untuk request yang hampir pasti gagal — begitu API pulih, sirkuit menutup sendiri dan kapasitas thread kembali penuh.

Beberapa panduan praktis saat menyesuaikan kedua parameter ini:

  • Mulai dari nilai default (failure_threshold=5, recovery_timeout=60), lalu turunkan recovery_timeout jika API biasanya pulih cepat dari gangguan sementara.
  • Pantau log [circuit] State: open — jika sirkuit terbuka berkali-kali sehari padahal API sebenarnya sehat, failure_threshold Anda kemungkinan terlalu ketat.
  • Untuk endpoint submit dan poll dengan karakteristik kegagalan yang berbeda, pertimbangkan circuit breaker terpisah untuk masing-masing — lihat FAQ di bawah.

Menggabungkan Circuit Breaker dengan Retry Logic

Tempatkan logika retry di dalam circuit breaker. Dengan begitu, sirkuit hanya menghitung kegagalan final setelah semua retry habis, bukan tiap percobaan:

def solve_with_retry(sitekey, page_url, max_retries=2):
    for attempt in range(max_retries + 1):
        try:
            return solve_captcha(sitekey, page_url)
        except Exception:
            if attempt == max_retries:
                raise
            time.sleep(2 ** attempt)

# Circuit breaker wraps the retry function
token = breaker.call(solve_with_retry, "6Le-SITEKEY", "https://example.com")

Circuit Breaker vs Retry Sederhana: Kapan Pakai yang Mana

Retry biasa sudah cukup untuk error sporadis — satu atau dua percobaan ulang dengan backoff eksponensial menyelesaikan sebagian besar timeout jaringan sesaat. Circuit breaker baru dibutuhkan ketika:

  • API CAPTCHA mengalami gangguan berkepanjangan, bukan sekadar satu-dua request yang gagal.
  • Volume task cukup tinggi sehingga retry tanpa kendali bisa menghabiskan thread aktif dalam hitungan detik.
  • Pipeline punya banyak worker paralel yang perlu tahu status API secara bersama, bukan mencoba sendiri-sendiri tanpa koordinasi.

Untuk automasi skala kecil dengan traffic rendah, retry dengan backoff eksponensial saja sering sudah memadai. Tambahkan circuit breaker begitu volume task naik atau downtime API mulai berdampak ke SLA internal tim Anda.

Skenario Rekomendasi
Traffic rendah, error jarang Retry dengan backoff saja
Traffic tinggi, gangguan berkepanjangan Circuit breaker, dengan retry di dalamnya
Banyak worker paralel Circuit breaker dengan state bersama (mis. Redis)

Masalah Umum dan Solusinya

Empat masalah ini paling sering muncul saat tim mengoperasikan circuit breaker di production:

Masalah Penyebab Solusi
Sirkuit terbuka terlalu cepat Threshold terlalu rendah Naikkan failure_threshold
Sirkuit tidak pernah recover recovery_timeout terlalu lama Kurangi ke 30-60 detik
Race condition di multi-thread Tidak ada state lock Gunakan threading.Lock (Python) atau operasi atomik
Semua request diblokir saat partial outage Satu circuit breaker untuk semua endpoint Gunakan circuit breaker terpisah untuk endpoint submit dan poll

Catatan: kalau sirkuit sering terbuka padahal API CaptchaAI sedang sehat, cek dulu timeout HTTP client dan koneksi jaringan Anda sebelum menaikkan failure_threshold — akar masalahnya kadang bukan di sisi API.


Pertanyaan Umum seputar Circuit Breaker CAPTCHA API

Perlukah circuit breaker terpisah untuk endpoint submit dan polling?

Untuk sistem volume tinggi, ya. Endpoint submit (in.php) dan polling (res.php) bisa gagal independen — submit kena rate limit sementara polling task yang sudah antre tetap normal, atau sebaliknya. Sirkuit terpisah memberi kontrol lebih presisi.

Berapa failure_threshold dan recovery_timeout yang aman untuk trafik scraping tinggi?

Untuk trafik di atas 100 request/menit, mulai dari failure_threshold: 10 dan recovery_timeout: 60 detik seperti tabel di atas, lalu sesuaikan dari pola error nyata. Threshold yang terlalu ketat membuka sirkuit karena noise biasa, bukan outage sungguhan.

Apa yang terjadi pada task yang tertunda saat sirkuit berstatus open?

Task tidak hilang, tapi tidak diproses selama sirkuit terbuka. Antrekan untuk dicoba ulang begitu sirkuit menutup, tampilkan status fallback, atau lewati langkah CAPTCHA untuk alur yang tidak kritis. Lihat Graceful Degradation Saat Solve Gagal untuk pola lengkapnya.

Apakah circuit breaker membuat pemakaian thread CaptchaAI lebih hemat?

Secara tidak langsung, ya. CaptchaAI menagih per thread aktif, bukan per solve — thread yang terus dipakai untuk request yang hampir pasti gagal adalah kapasitas terbuang percuma. Circuit breaker membebaskannya lebih cepat begitu API bermasalah terdeteksi, sehingga kapasitas thread Anda kembali tersedia untuk task yang benar-benar bisa diselesaikan.

Perlukah circuit breaker dipasang di setiap worker, atau cukup satu instance global?

Untuk proses single-thread, satu instance CircuitBreaker seperti pada contoh di atas sudah cukup. Untuk worker pool paralel berbasis multiprocessing, setiap proses butuh instance sendiri — kecuali Anda menyimpan state sirkuit di penyimpanan bersama seperti Redis, sehingga semua worker melihat status yang sama dan tidak ada worker yang tetap memanggil API padahal worker lain sudah mendeteksi sirkuit open.


Panduan Terkait

Komentar dinonaktifkan untuk artikel ini.