Satu worker CaptchaAI mentok bukan karena limit API, tapi karena kapasitas satu proses — CPU, koneksi jaringan, dan slot polling yang bisa ditangani sebelum antrean membengkak. Begitu volume solve CAPTCHA dari scraping Anda melewati titik itu, jawabannya bukan mengganti worker jadi satu mesin raksasa, melainkan menjalankan beberapa worker paralel dan menaruh load balancer di depannya — mendistribusikan task, mengisolasi worker yang gagal, dan menambah kapasitas secara horizontal kapan pun dibutuhkan.
Panduan ini membahas pola arsitektur yang sama dipakai tim scraping untuk menskalakan worker CaptchaAI: konfigurasi NGINX, worker API di Python dan Node.js, load balancing sisi klien untuk setup kecil, dan masalah yang paling sering muncul di production.
Kenapa Satu Worker CaptchaAI Selalu Berujung Jadi Bottleneck
Task solve CAPTCHA bukan request singkat — reCAPTCHA v2 dan Turnstile bisa makan waktu 5 sampai 120 detik tergantung tipe dan load CaptchaAI saat itu. Worker yang menjalankan solve secara sinkron akan menumpuk antrean begitu request masuk lebih cepat daripada task selesai. Load balancer memecah masalah ini dengan menyebar task ke banyak worker yang berjalan paralel, masing-masing memanggil in.php dan res.php secara independen:
[Scraper 1] ──┐ ┌── [Worker 1] ──→ CaptchaAI API
[Scraper 2] ──┤── [Load Balancer] ──┤── [Worker 2] ──→ CaptchaAI API
[Scraper 3] ──┘ └── [Worker 3] ──→ CaptchaAI API
Konfigurasi NGINX di Depan Worker CaptchaAI
NGINX adalah pilihan paling umum untuk load balancer self-hosted worker CaptchaAI karena ringan dan sudah dikenal luas oleh tim DevOps.
Round-Robin: Bawaan NGINX, Bukan Default yang Tepat untuk CAPTCHA
Konfigurasi default NGINX merotasi request berurutan ke tiap worker di daftar upstream. Ini sederhana, tapi berasumsi semua request berdurasi mirip — asumsi yang gugur untuk solve CAPTCHA, karena reCAPTCHA v3 bisa selesai dalam hitungan detik sementara reCAPTCHA v2 Enterprise butuh waktu jauh lebih lama:
upstream captcha_workers {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080;
}
server {
listen 80;
server_name captcha.internal;
location /solve {
proxy_pass http://captcha_workers;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 10s;
proxy_read_timeout 300s; # CAPTCHA solving can take minutes
}
location /health {
proxy_pass http://captcha_workers;
proxy_connect_timeout 5s;
proxy_read_timeout 5s;
}
}
Least Connections: Pilihan yang Tepat untuk Solve CAPTCHA
Karena durasi task solve CAPTCHA bervariasi, least_conn lebih masuk akal — NGINX mengirim request baru ke worker dengan koneksi aktif paling sedikit, bukan sekadar giliran berikutnya. Konfigurasi ini juga menambahkan weight untuk worker berkapasitas besar dan max_fails/fail_timeout agar worker bermasalah otomatis keluar sementara dari rotasi:
upstream captcha_workers {
least_conn; # Route to worker with fewest active connections
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080 weight=2; # Higher capacity worker
# Health checks
server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.12:8080 max_fails=3 fail_timeout=30s;
}
Menambahkan Worker Backup untuk Isolasi Kegagalan
Tandai satu atau lebih worker sebagai backup kalau Anda ingin kapasitas cadangan yang baru aktif saat semua worker utama down — jalur darurat, bukan bagian dari rotasi normal:
upstream captcha_workers {
least_conn;
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080 backup; # Only used when others are down
}
Membangun Worker API Sendiri di Depan Load Balancer
Load balancer hanya meneruskan traffic — Anda tetap butuh worker API yang menerima request, memanggil CaptchaAI, dan melaporkan kapasitasnya sendiri lewat endpoint /health.
Worker Python dengan Flask
Worker ini membatasi task paralel lewat MAX_CONCURRENT, mengembalikan 503 saat penuh, dan mengekspos load_pct di /health agar Anda bisa memantau kapasitas riil — bukan cuma status hidup atau mati:
import os
import time
import threading
import requests
from flask import Flask, request, jsonify
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
app = Flask(__name__)
# Track active tasks for load reporting
active_tasks = 0
tasks_lock = threading.Lock()
max_concurrent = int(os.environ.get("MAX_CONCURRENT", "20"))
@app.route("/solve", methods=["POST"])
def solve():
global active_tasks
with tasks_lock:
if active_tasks >= max_concurrent:
return jsonify({"error": "WORKER_AT_CAPACITY"}), 503
active_tasks += 1
try:
data = request.json
result = solve_captcha(data)
return jsonify(result)
finally:
with tasks_lock:
active_tasks -= 1
@app.route("/health")
def health():
with tasks_lock:
load = active_tasks / max_concurrent
return jsonify({
"status": "healthy" if load < 0.9 else "overloaded",
"active_tasks": active_tasks,
"max_concurrent": max_concurrent,
"load_pct": round(load * 100, 1)
}), 200 if load < 0.9 else 503
def solve_captcha(data):
session = requests.Session()
payload = {
"key": API_KEY,
"method": data.get("method", "userrecaptcha"),
"googlekey": data.get("sitekey"),
"pageurl": data.get("pageurl"),
"json": 1
}
if data.get("proxy"):
payload["proxy"] = data["proxy"]
payload["proxytype"] = data.get("proxytype", "HTTP")
resp = session.post("https://ocr.captchaai.com/in.php", data=payload)
result = resp.json()
if result.get("status") != 1:
return {"error": result.get("request")}
captcha_id = result["request"]
for _ in range(60):
time.sleep(5)
poll = session.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": captcha_id, "json": 1
}).json()
if poll.get("status") == 1:
return {"solution": poll["request"], "captcha_id": captcha_id}
if poll.get("request") != "CAPCHA_NOT_READY":
return {"error": poll.get("request")}
return {"error": "TIMEOUT"}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, threaded=True)
Worker Node.js dengan Express
Versi Node.js memakai pola yang sama: counter activeTasks global, respons 503 saat mencapai MAX_CONCURRENT, dan /health yang mengembalikan loadPct:
const express = require("express");
const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const MAX_CONCURRENT = parseInt(process.env.MAX_CONCURRENT || "20", 10);
const PORT = parseInt(process.env.PORT || "8080", 10);
let activeTasks = 0;
const app = express();
app.use(express.json());
app.post("/solve", async (req, res) => {
if (activeTasks >= MAX_CONCURRENT) {
return res.status(503).json({ error: "WORKER_AT_CAPACITY" });
}
activeTasks++;
try {
const result = await solveCaptcha(req.body);
res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
} finally {
activeTasks--;
}
});
app.get("/health", (req, res) => {
const load = activeTasks / MAX_CONCURRENT;
const status = load < 0.9 ? "healthy" : "overloaded";
res
.status(load < 0.9 ? 200 : 503)
.json({ status, activeTasks, maxConcurrent: MAX_CONCURRENT, loadPct: Math.round(load * 100) });
});
async function solveCaptcha(data) {
const submitResp = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: API_KEY,
method: data.method || "userrecaptcha",
googlekey: data.sitekey,
pageurl: data.pageurl,
json: 1,
},
});
if (submitResp.data.status !== 1) {
return { error: submitResp.data.request };
}
const captchaId = submitResp.data.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const pollResp = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
});
if (pollResp.data.status === 1) {
return { solution: pollResp.data.request, captchaId };
}
if (pollResp.data.request !== "CAPCHA_NOT_READY") {
return { error: pollResp.data.request };
}
}
return { error: "TIMEOUT" };
}
app.listen(PORT, () => console.log(`Worker listening on port ${PORT}`));
Round-Robin vs Least Connections vs Weighted: Memilih Strategi Routing
Tabel berikut merangkum lima strategi routing yang didukung NGINX dan kapan masing-masing masuk akal untuk worker CaptchaAI:
| Strategi | Cara Kerja | Cocok Untuk |
|---|---|---|
| Round-Robin | Rotasi berurutan ke tiap worker | Worker dengan kapasitas dan durasi task yang seragam |
| Least Connections | Route ke worker dengan koneksi aktif paling sedikit | Solve CAPTCHA — durasi task bervariasi 5–120 detik |
| Weighted | Distribusi proporsional sesuai bobot | Worker dengan spesifikasi hardware campuran |
| IP Hash | Klien yang sama selalu ke worker yang sama | Kasus yang butuh session affinity |
| Random | Pemilihan worker acak | Distribusi load sederhana tanpa state |
Rekomendasi: pakai least connections untuk solve CAPTCHA — round-robin gampang membebani satu worker dengan beberapa task berat sekaligus sementara worker lain menganggur.
Kapasitas yang bisa Anda alirkan lewat load balancer ini akhirnya dibatasi jumlah thread di paket CaptchaAI, bukan jumlah worker. BASIC ($15/bulan, 5 thread) cukup untuk 2–3 worker kecil; tim dengan 5+ worker biasanya perlu ADVANCE ($90/bulan, 50 thread) atau lebih tinggi.
Load Balancing di Sisi Klien Tanpa NGINX atau HAProxy
Tim automation kecil — misalnya agensi price-monitoring atau freelancer yang menjalankan 2–3 worker dari satu VPS — sering belum butuh load balancer eksternal sama sekali. Class Python berikut mengimplementasikan logika least-connections yang sama langsung di kode scraper, lengkap dengan retry otomatis ke worker lain saat salah satu down:
import random
import requests
class ClientLoadBalancer:
def __init__(self, workers):
self.workers = [
{"url": url, "healthy": True, "active": 0}
for url in workers
]
def get_worker(self):
healthy = [w for w in self.workers if w["healthy"]]
if not healthy:
raise Exception("No healthy workers")
return min(healthy, key=lambda w: w["active"])
def solve(self, task):
worker = self.get_worker()
worker["active"] += 1
try:
resp = requests.post(
f"{worker['url']}/solve",
json=task,
timeout=300
)
if resp.status_code == 503:
worker["healthy"] = False
return self.solve(task) # Retry on another worker
return resp.json()
except requests.RequestException:
worker["healthy"] = False
return self.solve(task)
finally:
worker["active"] -= 1
lb = ClientLoadBalancer([
"http://10.0.1.10:8080",
"http://10.0.1.11:8080",
"http://10.0.1.12:8080"
])
result = lb.solve({"sitekey": "6Le-wvkS...", "pageurl": "https://example.com"})
Kapan Pakai Round-Robin, Least Connections, atau Backup Routing
- Pakai round-robin hanya kalau semua worker benar-benar homogen dan rentang waktu solve-nya sempit.
- Pakai least connections sebagai default begitu Anda mencampur tipe CAPTCHA dengan durasi solve berbeda-beda — ini mencegah task lama menumpuk di satu worker.
- Pakai backup routing dan IP hash hanya untuk isolasi kegagalan atau target yang butuh session affinity, jangan jadikan default.
- Kalau worker tersebar di beberapa region cloud — misalnya AWS
ap-southeast-1(Singapura) danap-southeast-3(Jakarta), atau GCPasia-southeast2— jalankan load balancer lokal di tiap region dan arahkan traffic ke region terdekat lewat global load balancer.
Masalah Umum di Load Balancer Worker CaptchaAI
Kesalahan yang paling sering ditemui saat menaruh worker CaptchaAI di belakang load balancer:
| Masalah | Penyebab | Solusi |
|---|---|---|
| 502 Bad Gateway | Worker crash atau belum berjalan | Periksa log worker; verifikasi port binding |
| Distribusi beban tidak merata | Round-robin dipakai untuk durasi solve yang bervariasi | Beralih ke least connections |
| Health check false positive | Check /health lulus, worker sudah di batas MAX_CONCURRENT |
Sertakan load_pct di respons health |
| Connection timeout | proxy_read_timeout terlalu pendek |
Set ke 300 detik atau lebih |
| Burst request gagal setelah restart | Semua worker submit task bersamaan saat load balancer pulih | Tambahkan jitter sebelum worker menerima traffic penuh |
Pertanyaan yang Sering Ditanyakan
Berapa banyak worker sebelum saya butuh load balancer khusus, bukan sekadar client-side routing?
Class load balancing sisi klien di atas biasanya cukup sampai 3–4 worker di satu VPS. Begitu Anda menambah worker kelima atau butuh SSL termination dan health check otomatis, load balancer khusus seperti NGINX atau HAProxy lebih mudah dirawat.
Apakah task solve CAPTCHA butuh sticky session?
Tidak. Request solve CaptchaAI bersifat stateless — worker mana pun bisa memproses task apa pun karena state task hidup di sisi CaptchaAI, bukan di worker. Sticky session justru membuat distribusi beban timpang.
Apakah menambah worker di belakang load balancer otomatis menaikkan biaya paket CaptchaAI saya?
Tidak langsung. CaptchaAI menagih per thread aktif, bukan per worker atau per solve — biaya naik hanya kalau total task paralel dari seluruh worker Anda melampaui alokasi thread paket saat ini. Cek dulu apakah paket Anda (misalnya ADVANCE $90/bulan, 50 thread) masih punya slot yang cukup sebelum menambah worker.
Apa bedanya scaling worker CaptchaAI secara horizontal dengan menambah thread di paket CaptchaAI?
Menambah thread (naik paket) menaikkan berapa banyak task paralel yang boleh berjalan di akun CaptchaAI Anda. Menambah worker menentukan berapa banyak proses yang bisa memanfaatkan thread itu sekaligus. Keduanya perlu naik bersamaan — worker banyak dengan paket thread kecil hanya akan antre di sisi CaptchaAI.
Artikel Terkait
- Pola penanganan error callback CaptchaAI
- Pola arsitektur solve CAPTCHA untuk volume tinggi
- Desain failover untuk high availability
Worker CaptchaAI yang solid di balik load balancer yang tepat adalah fondasi untuk scraping yang stabil di volume berapa pun — ambil API key CaptchaAI Anda dan mulai deploy worker pertama Anda di belakang least connections.