Kaç worker çalıştırmanız gerektiğinin tek doğru cevabı yok — talep saatten saate değişir. Gece 03:00'te 2 worker yeterliyken, kampanya başladığı an kuyruk 500 göreve fırlayabilir. Statik bir havuz bu iki durumdan birinde her zaman yanlış boyuttadır: ya boşta bekleyen thread'lere para ödersiniz ya da kuyruk büyürken talebi karşılayamazsınız.
Bu rehberde CaptchaAI worker havuzunuzu kendiliğinden büyütüp küçültecek üç desen var:
- Thread tabanlı — tek süreçte, I/O-bound API çağrıları için.
- Süreç tabanlı — CPU'ya bağlı ön işleme eşlik ediyorsa.
- Bakiye kontrollü — hangi desende olursanız olun, üzerine eklenen bir güvenlik freni.
İpucu: Emin değilseniz thread tabanlı desenle başlayın — CaptchaAI çağrılarının büyük kısmı I/O-bound olduğu için çoğu iş yükünde en düşük karmaşıklığı sunar.
Ne Zaman Ölçeklendirmeli, Ne Zaman Küçültmelisiniz?
Otomatik ölçekleyicinizin izlemesi gereken beş sinyal var. Bunlardan biri eşiği aştığında worker ekleyin; tersi durumda güvenle azaltın:
| Sinyal | Ne Zaman Büyütülür | Ne Zaman Küçültülür |
|---|---|---|
| Kuyruk derinliği | > 20 bekleyen görev | < 5 bekleyen görev |
| Çalışan kullanımı | > %80 meşgul | < %20 meşgul |
| Çözüm gecikmesi (P95) | > 60 saniye | < 20 saniye |
| Hata oranı | > %5 (yeni worker gerekir) | Kararlı < %1 |
| Bakiye | — | Bakiye < $1 (ölçeklendirmeyi durdurun) |
Ölçeği hızlı büyütün (her 10–15 saniyede bir kontrol), yavaş küçültün (düşük yükte 30–60 saniye bekleyin) — bu, worker sayısının iki durum arasında salınmasını (thrashing) önler. Bakiye sinyali diğerlerinden farklıdır: büyütme eşiği yoktur, sadece bir fren görevi görür — kuyruk büyürken bile bakiye kritik seviyenin altına düşerse yeni worker eklemeyi durdurmalısınız.
Hangi Ölçeklendirme Stratejisi Size Uygun?
Kod örneklerine geçmeden önce doğru deseni seçin. Çoğu CaptchaAI çağrısı I/O-bound olduğu için tek başına bir thread havuzu genelde yeterlidir; görüntü ön işleme veya ağır hesaplama eklediğinizde süreç havuzuna, Kubernetes üzerinde çalışıyorsanız HPA veya KEDA'ya geçin:
| Strateji | En İyisi | Gecikme | Karmaşıklık |
|---|---|---|---|
| Thread havuzu | I/O-bound (API çağrıları) | Düşük | Düşük |
| Süreç havuzu | CPU'ya bağlı ön işleme | Orta | Orta |
| Kubernetes HPA | Bulutta yerel dağıtımlar | Daha yüksek | Yüksek |
| KEDA | Olay odaklı ölçeklendirme | Orta | Orta |
Örnek: İstanbul merkezli bir freelance otomasyon geliştiricisi, bir e-ticaret müşterisinin ödeme akışını her gece 02:00–06:00 (Europe/Istanbul) arasında QA amacıyla test ediyor. Kasım ayı indirim kampanyası öncesinde test hacmi beş kat artınca, min_workers=2 / max_workers=15 ayarlı thread havuzu STANDARD plandan ($30/ay, 15 thread) ADVANCE plana ($90/ay, 50 thread) geçmeye gerek kalmadan yükü karşıladı — çünkü ölçekleyici zaten mevcut thread limitine göre büyüyüp küçülüyordu.
Thread Tabanlı Worker Ölçekleme
Tek bir Python sürecinde thread havuzunu büyütüp küçültün — I/O-bound CaptchaAI çağrıları için en düşük karmaşıklığa sahip yöntem budur:
import os
import time
import threading
import requests
import json
import redis
class AutoScalingPool:
"""Dynamically scale CaptchaAI worker threads."""
def __init__(self, api_key, redis_url="redis://localhost:6379"):
self.api_key = api_key
self.redis = redis.from_url(redis_url)
self.base = "https://ocr.captchaai.com"
self.queue_key = "captcha:tasks"
self.results_key = "captcha:results"
self.min_workers = 2
self.max_workers = 20
self.workers = []
self.active_count = 0
self.lock = threading.Lock()
self.running = True
def start(self):
"""Start the pool with minimum workers."""
for _ in range(self.min_workers):
self._add_worker()
# Start scaler in background
scaler = threading.Thread(target=self._scaling_loop, daemon=True)
scaler.start()
print(f"Pool started with {self.min_workers} workers")
def _add_worker(self):
"""Add a worker thread."""
if len(self.workers) >= self.max_workers:
return
t = threading.Thread(target=self._worker_loop, daemon=True)
t.start()
self.workers.append(t)
def _remove_worker(self):
"""Signal one worker to stop (lazy removal)."""
if len(self.workers) <= self.min_workers:
return
self.workers.pop() # Thread will exit on next idle cycle
def _worker_loop(self):
"""Worker loop: fetch and process tasks."""
while self.running and threading.current_thread() in self.workers:
result = self.redis.blpop(self.queue_key, timeout=10)
if result is None:
continue
_, raw = result
task = json.loads(raw)
task_id = task["id"]
with self.lock:
self.active_count += 1
try:
token = self._solve(task["method"], task["params"])
self.redis.hset(self.results_key, task_id, json.dumps({
"status": "success", "token": token,
}))
except Exception as e:
self.redis.hset(self.results_key, task_id, json.dumps({
"status": "error", "error": str(e),
}))
finally:
with self.lock:
self.active_count -= 1
def _scaling_loop(self):
"""Periodically adjust worker count."""
while self.running:
time.sleep(10)
queue_depth = self.redis.llen(self.queue_key)
current = len(self.workers)
utilization = (
self.active_count / current * 100 if current > 0 else 0
)
# Scale up: queue growing and workers busy
if queue_depth > 20 and utilization > 70:
new_count = min(current + 2, self.max_workers)
while len(self.workers) < new_count:
self._add_worker()
print(f"Scaled up: {current} → {len(self.workers)} workers")
# Scale down: queue empty and workers idle
elif queue_depth < 5 and utilization < 20:
target = max(current - 1, self.min_workers)
while len(self.workers) > target:
self._remove_worker()
if len(self.workers) < current:
print(f"Scaled down: {current} → {len(self.workers)} workers")
def _solve(self, method, params, timeout=120):
data = {"key": self.api_key, "method": method, "json": 1}
data.update(params)
resp = requests.post(
f"{self.base}/in.php", data=data, timeout=30,
)
result = resp.json()
if result.get("status") != 1:
raise RuntimeError(result.get("request"))
captcha_id = result["request"]
start = time.time()
while time.time() - start < timeout:
time.sleep(5)
resp = requests.get(f"{self.base}/res.php", params={
"key": self.api_key,
"action": "get",
"id": captcha_id,
"json": 1,
}, timeout=15)
data = resp.json()
if data["request"] != "CAPCHA_NOT_READY":
if data.get("status") == 1:
return data["request"]
raise RuntimeError(data["request"])
raise TimeoutError("Solve timeout")
def stats(self):
return {
"workers": len(self.workers),
"active": self.active_count,
"queue": self.redis.llen(self.queue_key),
}
# Usage
pool = AutoScalingPool(os.environ["CAPTCHAAI_KEY"])
pool.start()
# Monitor
while True:
print(pool.stats())
time.sleep(30)
Süreç Tabanlı Worker Ölçekleme
Görüntü ön işleme veya ağır CPU işi çözüme eşlik ediyorsa, thread yerine ayrı süreçler kullanın — GIL sınırlamasını aşar ve çekirdekleri gerçek anlamda paralelleştirir. Tipik tetikleyiciler:
- Sitekey/pageurl çıkarımından önce ekran görüntüsü ön işleme
- OCR öncesi görüntü temizleme veya kırpma
- CAPTCHA çözümüyle birlikte çalışan CPU-ağır doğrulama adımları
import multiprocessing
import time
import redis
import os
class ProcessScaler:
"""Scale worker processes based on queue depth."""
def __init__(self, worker_fn, redis_url="redis://localhost:6379"):
self.worker_fn = worker_fn
self.redis = redis.from_url(redis_url)
self.processes = []
self.min_workers = 2
self.max_workers = 16
def run(self, check_interval=15):
"""Run the scaler loop."""
# Start minimum workers
for _ in range(self.min_workers):
self._spawn()
while True:
time.sleep(check_interval)
self._cleanup_dead()
queue_depth = self.redis.llen("captcha:tasks")
current = len(self.processes)
# Scale up
if queue_depth > current * 5 and current < self.max_workers:
to_add = min(
max(1, queue_depth // 10),
self.max_workers - current,
)
for _ in range(to_add):
self._spawn()
print(f"Scaled up to {len(self.processes)} workers")
# Scale down
elif queue_depth < 3 and current > self.min_workers:
to_remove = min(2, current - self.min_workers)
for _ in range(to_remove):
p = self.processes.pop()
p.terminate()
print(f"Scaled down to {len(self.processes)} workers")
def _spawn(self):
p = multiprocessing.Process(target=self.worker_fn)
p.start()
self.processes.append(p)
def _cleanup_dead(self):
self.processes = [p for p in self.processes if p.is_alive()]
# Ensure minimum
while len(self.processes) < self.min_workers:
self._spawn()
Bakiye Kontrollü Ölçekleme
Kuyruk büyürken bile bakiyeniz tükeniyorsa yeni worker eklemeyi durdurun — aksi halde boş thread'ler için ödeme yapıp hiç çözüm alamazsınız:
def check_balance(api_key, min_balance=2.0):
"""Check if balance is sufficient for scaling."""
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key,
"action": "getbalance",
"json": 1,
}, timeout=15)
balance = float(resp.json()["request"])
if balance < min_balance:
print(f"Balance ${balance:.2f} below ${min_balance} — halting scale-up")
return False
return True
Bunu ölçekleme döngünüze entegre edin — bakiye kontrolü, büyütme kararından önceki son adım olmalı:
# In _scaling_loop:
if queue_depth > 20 and utilization > 70:
if check_balance(self.api_key, min_balance=2.0):
# Scale up
...
else:
print("Scaling paused — low balance")
Sorun Giderme
Otomatik ölçekleyicide en sık karşılaşacağınız dört sorun ve düzeltmeleri — çoğu, tek bir metriğe (sadece kuyruk derinliği veya sadece kullanım oranı) körü körüne güvenmekten kaynaklanır:
- Worker'lar büyümeye devam ediyor (kuyruk asla tükenmiyor) → worker'ların gerçekten görev işleyip işlemediğini kontrol edin.
- Ölçek küçültme çok agresif (eşik çok düşük) → ölçek küçültme gecikmesini 30 saniyenin üzerine çıkarın.
- Zombi süreçler (temizlenmeyen süreçler) →
_cleanup_dead()'i düzenli olarak çağırın. - Bakiye hızla tükeniyor (çok fazla worker) → ölçekleme mantığına bakiye kontrolü ekleyin.
SSS
Otomatik ölçekleme kurulumlarında en çok tekrar eden beş soru:
Bir worker'a kaç bekleyen görev düşmelidir?
Kuyruktaki her 5–10 göreve bir worker hedefleyin. Türe bağlı olarak her worker dakikada yaklaşık 3–6 CAPTCHA işler.
Thread mi süreç mi kullanmalıyım?
Salt API çağrısı için thread yeterlidir (CaptchaAI çağrıları I/O-bound'dur). Görüntü ön işleme veya ağır hesaplama çözümle birlikte yapılıyorsa süreç kullanın — GIL'i aşarak gerçek paralellik sağlar.
Otomatik ölçekleme aylık CaptchaAI faturamı nasıl etkiler?
CaptchaAI çözüm başına değil thread bazında faturalandırır ve her thread için sınırsız çözüm hakkı tanır, bu yüzden otomatik ölçekleme tek başına maliyeti artırmaz — mevcut plan limitiniz içinde worker'ları talebe göre yeniden dağıtır. max_workers değerini planınızın thread sayısının (örn. BASIC $15/ay, 5 thread; STANDARD $30/ay, 15 thread) üzerine çıkarmayın; aksi halde fazladan worker'lar boşta bekler ve hiçbir işe yaramaz.
Redis olmadan otomatik ölçekleme kurabilir miyim?
Evet — örneklerdeki redis.llen() çağrısını RabbitMQ, SQS veya bir veritabanı tablosu gibi başka bir kuyruk arka ucuyla değiştirebilirsiniz; önemli olan _scaling_loop()'un kuyruk derinliğini periyodik olarak okuyabilmesidir.
KEDA mı, basit thread havuzu mu?
Tek bir sunucuda veya konteynerde çalışıyorsanız thread havuzu yeterlidir ve daha az operasyonel yük getirir. Kubernetes üzerinde birden fazla pod'u kuyruk derinliğine göre otomatik ölçeklendirmeniz gerekiyorsa KEDA'ya geçin.
İlgili Kılavuzlar
Akıllıca ölçeklendirin — CaptchaAI anahtarınızı alın ve worker havuzunuzu bugün otomatikleştirin.