¿Cuándo deja de bastar una sola región para resolver CAPTCHA a escala? En cuanto tus sitios objetivo se reparten por varios continentes o necesitas sobrevivir a la caída de una zona. Una arquitectura multirregional coloca los workers cerca de cada sitio objetivo, recorta el tiempo de ida y vuelta hacia la API y reparte la carga para que ninguna región sea un único punto de falla.
¿Cuándo conviene la arquitectura multirregional?
Una sola región bien dimensionada resuelve la mayoría de los flujos:
| Situación | Una sola región | Multirregión |
|---|---|---|
| Sitios objetivo en un solo país | Suficiente | Excesivo |
| Sitios objetivo globales | 100–300 ms de latencia extra | Latencia local por región |
| Alta disponibilidad exigida | Difícil de sostener | Redundancia natural |
| Residencia de datos por normativa | No la cumples | Procesas localmente |
| < 1.000 tareas/hora | Va bien | Complejidad innecesaria |
| > 10.000 tareas/hora | Topes de escala | Reparte la carga |
Un ejemplo: una agencia en Madrid que hace QA de portales públicos con CAPTCHA para clientes en España (cita previa), México (SAT) y Argentina (AFIP). Sus workers en eu-west-1 responden rápido a los .es, pero los .mx y .ar arrastran latencia extra; una región en América la recorta. Trabaja siempre sobre flujos autorizados.
Visión general de la arquitectura
[Task Router]
(Route53 / Load Balancer)
↙ ↓ ↘
[US-East] [EU-West] [AP-Southeast]
Workers Workers Workers
↓ ↓ ↓
[CaptchaAI API] ← shared API key
↓ ↓ ↓
[Central DB / Queue]
(Results aggregation)
Cada región ejecuta workers independientes que comparten la clave API de CaptchaAI y empujan resultados a un almacén central.
Despliegue de workers por región
Worker de Python con conciencia de región
Cada worker resuelve el CAPTCHA y etiqueta el resultado con su región de origen, para medir la latencia por zona:
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}
Enrutador de tareas
Envía cada tarea a la cola de la región más cercana al sitio objetivo:
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
Infraestructura como código
Esqueleto de 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"
}
Docker Compose (simulación multirregional local)
Para reproducirlo en local, levanta un worker por zona con bases de Redis distintas:
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
Monitoreo de salud por región
Chequeo de salud en JavaScript
Un chequeo periódico revela qué regiones responden y a qué velocidad antes del failover:
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);
Estrategia de failover
El failover no debería ser automático siempre. Decide con señales operativas claras qué redirigir:
| Señal operativa | Acción recomendada | Motivo |
|---|---|---|
El p95 de una región duplica su línea base durante 5 minutos |
Deja de enviar tareas nuevas a esa región | Evita amplificar la latencia y los reintentos cruzados |
| Errores del proveedor concentrados en una región | Redirige solo los envíos nuevos, no el polling ya iniciado | El token y la sesión suelen seguir atados al flujo original |
| Requisito de residencia de datos | No hagas failover automático | Necesita aprobación explícita para no romper el cumplimiento |
Cuando una región cae, redistribuye sus tareas hacia la zona sana con menor profundidad de cola:
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
Resolución de problemas
| Problema | Causa | Solución |
|---|---|---|
| Una región siempre más lenta | Distancia a los servidores de CaptchaAI | Compara la latencia base; puede ser lo esperado |
| El enrutador manda todo a una sola región | Las reglas por dominio son demasiado amplias | Añade reglas de enrutamiento más granulares |
| El failover no se dispara | El endpoint de salud no responde | Ponlo en una ruta separada de la lógica del worker |
| El saldo de la clave API baja más rápido | Todas las regiones comparten una clave | Es lo esperado: vigila el uso agregado |
Costos de operar en varias regiones
| Componente | Factor de costo | Optimización |
|---|---|---|
| Instancias de workers | Cómputo por región | Escala a 0 cuando están inactivas |
| Transferencia de datos entre regiones | $0.02/GB entre regiones | Minimiza el tamaño del payload de resultados |
| Colas SQS | Precio por solicitud | Agrupa mensajes por lotes cuando puedas |
| API de CaptchaAI | Mismo costo sea cual sea la región | Sin recargo por multirregión |
CaptchaAI cobra por thread concurrente, no por región ni por resolución: el modelo arranca en el plan BASIC ($15/mes, 5 threads) y sube en threads según tu volumen. El costo extra de la multirregión es solo de infraestructura.
Preguntas frecuentes
¿Necesito una clave API distinta por región?
No. Una sola clave API funciona en todo el mundo; mide el uso por zona con tus propias métricas.
¿Cómo decido a qué región enrutar cada tarea?
Enruta por la ubicación del sitio objetivo. El TLD del dominio (.es, .mx, .de) suele bastar; refina con reglas más granulares cuando un .com genérico concentre demasiado tráfico en una cola.
¿La arquitectura multirregional encarece el uso de CaptchaAI?
No. CaptchaAI cobra por thread concurrente con el mismo precio sea cual sea la región del worker (desde el plan BASIC a $15/mes con 5 threads). El sobrecosto es solo de infraestructura.
Próximos pasos
Despliega workers de CaptchaAI en varias regiones, obtén tu clave API y coloca la resolución de CAPTCHA cerca de cada sitio objetivo.