DevOps & Scaling

Architecture de résolution de CAPTCHA multirégion avec CaptchaAI

Déployez vos workers là où sont vos sites cibles, pas là où est votre équipe : c'est la règle qui décide d'une architecture multirégion. Un pipeline hébergé à Paris qui interroge des cibles nord-américaines paie 100 à 300 ms de latence par aller-retour, et une panne de zone arrête tout.

L'API CaptchaAI, elle, reste hors de l'équation : elle répond aux requêtes mondiales et se facture au thread concurrent, où que tournent vos workers.

Faut-il vraiment une deuxième région ?

Signal Le multi-région vaut l'effort Une seule région suffit
Répartition des cibles Plusieurs zones géographiques distinctes Cibles concentrées dans une zone
Tolérance à la panne Un incident régional doit rester invisible Une courte interruption est acceptable
Conformité Certaines sessions doivent être traitées localement Aucune contrainte de résidence
Volumétrie Les pics justifient routage et observabilité Volume faible et stable

Trois lignes sur quatre à droite : gardez une région unique.

Le schéma cible

                    [Task Router]
                    (Route53 / Load Balancer)
                    ↙        ↓        ↘
        [US-East]      [EU-West]     [AP-Southeast]
        Workers        Workers        Workers
            ↓              ↓              ↓
        [CaptchaAI API] ← shared API key
            ↓              ↓              ↓
        [Central DB / Queue]
        (Results aggregation)

Chaque région exécute ses propres workers, partage la même clé API CaptchaAI et pousse ses résultats vers un stockage central, le seul endroit d'où vous voyez le débit par région.

Région unique ou distribuée : ce qui change

Situation Région unique Multi-région
Cibles dans un seul pays Suffisant Surdimensionné
Cibles mondiales 100 à 300 ms ajoutées Latence locale
Disponibilité élevée Difficile sans redondance Redondance naturelle
Résidence des données imposée Contrainte non tenable Traitement local
Moins de 1 000 tâches/heure Largement suffisant Complexité inutile
Plus de 10 000 tâches/heure Plafond de mise à l'échelle Charge répartie

Déployer des workers conscients de leur région

Un worker multirégion étiquette chaque résultat avec sa région d'exécution.

Worker Python sensible à la région

import os
import time
import requests

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
REGION = os.environ.get("WORKER_REGION", "us-east-1")
RESULT_QUEUE_URL = os.environ["RESULT_QUEUE_URL"]


def solve_captcha(task):
    """Solve CAPTCHA and tag with region metadata."""
    start = time.time()

    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": task["method"],
        "googlekey": task["sitekey"],
        "pageurl": task["pageurl"],
        "json": 1
    })
    data = resp.json()

    if data.get("status") != 1:
        return {
            "task_id": task["task_id"],
            "error": data.get("request"),
            "region": REGION
        }

    captcha_id = data["request"]

    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": captcha_id, "json": 1
        }).json()

        if result.get("status") == 1:
            return {
                "task_id": task["task_id"],
                "solution": result["request"],
                "region": REGION,
                "duration": time.time() - start,
                "api_latency_ms": round((time.time() - start) * 1000)
            }
        if result.get("request") != "CAPCHA_NOT_READY":
            return {
                "task_id": task["task_id"],
                "error": result.get("request"),
                "region": REGION
            }

    return {"task_id": task["task_id"], "error": "TIMEOUT", "region": REGION}

Le champ region et la durée voyagent avec chaque résultat : vous obtenez une médiane de temps de résolution par zone sans instrumentation.

Router les tâches vers la bonne file

Le routage se fait sur le domaine de la cible : un site en .fr part vers eu-west-3 (Paris), un site en .jp vers Tokyo.

from urllib.parse import urlparse

# Region mapping by target site TLD/domain
REGION_MAP = {
    ".co.uk": "eu-west-1",
    ".de": "eu-central-1",
    ".fr": "eu-west-3",
    ".jp": "ap-northeast-1",
    ".com.au": "ap-southeast-2",
    ".com": "us-east-1",  # Default
}

REGION_QUEUES = {
    "us-east-1": "sqs://captcha-tasks-us-east",
    "eu-west-1": "sqs://captcha-tasks-eu-west",
    "ap-southeast-1": "sqs://captcha-tasks-ap-southeast",
}


def route_task(task):
    """Route task to the closest regional queue."""
    domain = urlparse(task["pageurl"]).netloc

    target_region = "us-east-1"  # Default
    for suffix, region in REGION_MAP.items():
        if domain.endswith(suffix):
            target_region = region
            break

    queue = REGION_QUEUES.get(target_region, REGION_QUEUES["us-east-1"])
    send_to_queue(queue, task)
    return target_region

Pour les .com européens, ajoutez une table d'exceptions explicite.

Provisionner l'infrastructure

Squelette Terraform

# Define regions
variable "regions" {
  default = ["us-east-1", "eu-west-1", "ap-southeast-1"]
}

# Deploy worker fleet per region
module "captcha_workers" {
  for_each = toset(var.regions)
  source   = "./modules/captcha-worker"

  region              = each.key
  worker_count        = var.workers_per_region
  api_key_secret_arn  = aws_secretsmanager_secret.captchaai_key.arn
  task_queue_arn      = aws_sqs_queue.tasks[each.key].arn
  result_queue_arn    = aws_sqs_queue.results.arn
}

# SQS queue per region for task intake
resource "aws_sqs_queue" "tasks" {
  for_each = toset(var.regions)
  name     = "captcha-tasks-${each.key}"
}

# Central result queue
resource "aws_sqs_queue" "results" {
  name = "captcha-results-central"
}

Une file d'entrée par région, une file de résultats centrale : l'agrégation reste simple.

Simuler trois régions en local avec Docker Compose

Validez le routage en local avant de payer trois flottes cloud.

version: "3.8"
services:
  worker-us:
    build: ./worker
    environment:

      - CAPTCHAAI_API_KEY=${CAPTCHAAI_API_KEY}
      - WORKER_REGION=us-east-1
      - TASK_QUEUE=redis://redis:6379/0
    depends_on:

      - redis

  worker-eu:
    build: ./worker
    environment:

      - CAPTCHAAI_API_KEY=${CAPTCHAAI_API_KEY}
      - WORKER_REGION=eu-west-1
      - TASK_QUEUE=redis://redis:6379/1

  worker-ap:
    build: ./worker
    environment:

      - CAPTCHAAI_API_KEY=${CAPTCHAAI_API_KEY}
      - WORKER_REGION=ap-southeast-1
      - TASK_QUEUE=redis://redis:6379/2

  redis:
    image: redis:7-alpine

Surveiller la santé de chaque région

const axios = require("axios");

const REGIONS = ["us-east-1", "eu-west-1", "ap-southeast-1"];

async function checkRegionHealth() {
  const health = {};

  for (const region of REGIONS) {
    const endpoint = `https://${region}.workers.example.com/health`;
    try {
      const start = Date.now();
      const resp = await axios.get(endpoint, { timeout: 5000 });
      health[region] = {
        status: "healthy",
        latencyMs: Date.now() - start,
        activeWorkers: resp.data.activeWorkers,
        queueDepth: resp.data.queueDepth,
      };
    } catch (err) {
      health[region] = { status: "unhealthy", error: err.message };
    }
  }

  return health;
}

// Periodic health check
setInterval(async () => {
  const health = await checkRegionHealth();
  console.table(health);
}, 60000);

Exposez l'endpoint de santé sur un chemin distinct de la résolution : sinon un worker saturé se déclare sain jusqu'au bout.

Basculer quand une région tombe

def failover_check(region_health):
    """Redirect tasks from unhealthy regions."""
    healthy_regions = [
        r for r, h in region_health.items()
        if h["status"] == "healthy"
    ]

    if not healthy_regions:
        raise RuntimeError("All regions unhealthy")

    redirects = {}
    for region, health in region_health.items():
        if health["status"] == "unhealthy":
            # Pick the healthy region with lowest queue depth
            target = min(
                healthy_regions,
                key=lambda r: region_health[r].get("queue_depth", 0)
            )
            redirects[region] = target
            print(f"Failover: {region} → {target}")

    return redirects

La cible du basculement est la région saine dont la file est la plus courte, pas la plus proche. Gardez 30 % de marge ailleurs, sinon la panne devient saturation globale.

Ce que coûte réellement le multi-région

Composant Facteur de coût Optimisation
Instances de worker Calcul par région Mise à l'échelle automatique à zéro
Transfert inter-régions 0,02 $/Go Réduire la taille des payloads
Files SQS Tarification à la requête Regrouper les messages par lots
API CaptchaAI Coût identique partout Aucun supplément multirégion

La facturation CaptchaAI porte sur les threads concurrents, avec des résolutions illimitées par thread. Un plan ADVANCE ($90/mois, 50 threads) offre la même capacité à Paris, en Virginie ou à Singapour : répartir ces threads sur trois régions ne change pas la facture. Le surcoût reste infrastructurel.

Résidence des données : le réflexe RGPD

Dès que vos tâches transportent des données personnelles, la région n'est plus un arbitrage de latence. Traitez les tâches européennes dans une région européenne — eu-west-3 (Paris), eu-central-1 (Francfort), OVHcloud ou Scaleway. N'envoyez à la file centrale que l'identifiant de tâche, le token et l'horodatage, jamais le contenu de la page, et validez vos obligations RGPD avec votre DPO.

Dépannage

Problème Cause probable Correctif
Une région toujours plus lente Distance réseau vers les cibles Comparez d'abord la latence de référence
Tout part vers une seule file Mappage TLD trop grossier Ajoutez une table d'exceptions par domaine
Le basculement ne se déclenche pas Endpoint de santé répondant à tort Séparez-le du worker, surveillez la file
Le solde descend vite Régions partageant les mêmes threads Attendu : suivez la consommation agrégée

FAQ

Combien de régions faut-il pour commencer ?

Deux, dans deux zones distinctes. Une troisième en Asie ne se justifie que si vos cibles y sont nombreuses.

Comment répartir les threads de mon plan entre les régions ?

Les threads sont un plafond de concurrence global sur votre clé API : c'est au routeur de limiter les tâches en vol par région.

Le multi-région améliore-t-il le taux de réussite ?

Pas directement : il réduit la latence et absorbe les pannes de zone. Les gains viennent de la cohérence entre proxy et session.

Peut-on traiter les tâches européennes uniquement en Europe ?

Oui : routez par TLD vers une file européenne et ne déployez que des workers européens derrière elle.

Articles connexes

Prochaines étapes

Commencez par deux régions et mesurez la latence réelle une semaine. Récupérez votre clé API et déployez votre premier worker régional.

Guides associés :

Les commentaires sont désactivés pour cet article.