DevOps & Scaling

Многорегиональная архитектура решения CAPTCHA с помощью CaptchaAI

Если целевые сайты разбросаны по нескольким странам, а все воркеры решают CAPTCHA из одного региона, каждый запрос теряет 100–300 мс только на сетевую задержку — а при сбое единственного дата-центра встаёт весь пайплайн автоматизации. Многорегиональная архитектура снимает обе проблемы: воркеры разворачиваются рядом с целевыми сайтами, обращаются к общему API-ключу CaptchaAI и продолжают работу, даже если один регион полностью выходит из строя.

Когда нужна многорегиональная архитектура

Многорегиональная схема оправдана не всегда — для небольшого объёма задач в одной стране она добавляет сложность без ощутимой пользы. Сравните ситуации:

Ситуация Один регион Несколько регионов
Целевые сайты в одной стране Достаточно Избыточно
Целевые сайты по всему миру +100–300 мс задержки на запрос Локальная задержка в каждом регионе
Требование SLA на уровне 99,9% Сложно гарантировать одним регионом Естественная избыточность
Регуляторные требования к резидентности данных Не выполняется Обрабатывается локально
< 1000 задач в час Оптимально Лишняя сложность
> 10 000 задач в час Упирается в лимиты масштабирования Распределяет нагрузку

Резидентность данных особенно актуальна для команд, которые обрабатывают персональные данные пользователей из РФ: 152-ФЗ «О персональных данных» требует хранить и обрабатывать такие данные на серверах, физически расположенных в России, а трансграничные проекты обычно ориентируются на схожие принципы GDPR. Сама по себе многорегиональная архитектура вопрос комплаенса не решает — это ответственность вашей инфраструктуры, — но она даёт техническую возможность держать обработку локально там, где это требуется. Собирайте и обрабатывайте только те данные, которые вы вправе обрабатывать по применимому законодательству.

Для команд из России, Беларуси, Казахстана и Украины, у которых целевые сайты разбросаны между Европой и Азией, рабочей комбинацией часто становится связка европейского региона (eu-west-1 или eu-central-1) с площадкой ближе к Центральной Азии — это сокращает задержку сразу для западных и восточных целей и упрощает диагностику, если один из регионов начинает работать нестабильно из-за мобильных или нестабильных сетей у части воркеров.

Обзор архитектуры

Схема простая: маршрутизатор направляет каждую задачу в регион, ближайший к целевому сайту, воркеры этого региона решают CAPTCHA через API CaptchaAI и пишут результат в общее хранилище.

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

В каждом регионе работают независимые воркеры. Все они используют один и тот же API-ключ CaptchaAI и отправляют результаты в общее хранилище — отдельный ключ на регион не нужен (подробнее в разделе FAQ ниже).

Воркер с учётом региона (Python)

Каждый воркер узнаёт свой регион из переменной окружения WORKER_REGION и добавляет её в каждый результат — это единственное, что меняется от региона к региону; логика обращения к in.php/res.php остаётся одинаковой:

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}

Маршрутизация задач по регионам

Направляйте задачу в регион, ближайший к целевому сайту, по домену. Список сопоставлений расширяйте по мере роста набора целевых сайтов:

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

Настройка инфраструктуры

Каркас конфигурации Terraform

Модуль captcha-worker разворачивается по каждому региону из списка regions, получает доступ к общему секрету с API-ключом и своей паре очередей SQS:

# 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 для локального моделирования нескольких регионов

Перед выкладкой в облако схему удобно проверить локально: три воркера с разными значениями WORKER_REGION и своей Redis-очередью на регион воспроизводят поведение продакшена без реальной многорегиональной инфраструктуры:

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

Мониторинг работоспособности по регионам

Health-check каждого региона должен отвечать быстро и независимо от загрузки очереди — иначе аварийное переключение будет запаздывать:

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);

Стратегия аварийного переключения

Когда health-check помечает регион нездоровым, перенаправьте его задачи в работоспособный регион с наименьшей глубиной очереди:

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

Политика аварийного переключения

  • Прекращайте направлять новые задачи в регион, у которого задержка, доля ошибок или работоспособность прокси превысили пороги политики.
  • Сокращайте нагрузку на проблемный регион постепенно, а не переключайте все очереди и сессии разом.
  • Возвращайте трафик в регион только после того, как он снова проходит те же проверки работоспособности, что применялись до отработки отказа.

Экономика многорегионального развёртывания

Инфраструктурные расходы растут с числом регионов, но тариф CaptchaAI от региона не зависит:

Компонент Что влияет на стоимость Как оптимизировать
Воркеры Вычислительные ресурсы в каждом регионе Автомасштабирование до нуля в простое
Межрегиональная передача данных $0,02/GB между регионами Минимизируйте размер полезной нагрузки результата
Очереди SQS Оплата за запрос Группируйте сообщения в пакеты, где возможно
API CaptchaAI Одинаковая стоимость независимо от региона Надбавки за многорегиональность нет

CaptchaAI выставляет одинаковый счёт независимо от того, откуда физически приходит запрос — переплата за несколько регионов возникает только на стороне вашей инфраструктуры, а не на стороне API.

Типичные проблемы при многорегиональном развёртывании

Проблема Причина Решение
Один регион стабильно медленнее остальных Он физически дальше от серверов CaptchaAI Сравните базовую задержку — иногда это ожидаемо и правки не нужны
Маршрутизация отправляет почти все задачи в один регион Правило сопоставления домена и региона слишком грубое Добавьте более точные правила в REGION_MAP
Аварийное переключение не срабатывает Эндпоинт health-check не отвечает Разместите health-check на отдельном пути, не зависящем от логики воркера
Баланс API-ключа расходуется быстрее ожидаемого Все регионы используют один общий ключ Это ожидаемо — считайте расход по всем регионам агрегированно

FAQ

Нужен ли отдельный API-ключ CaptchaAI для каждого региона?

Нет. Один API-ключ работает глобально — используйте единый ключ и считайте расход по регионам на стороне своих метрик, как в примере воркера выше.

С какого объёма задач в час есть смысл переходить на несколько регионов?

Ориентир из таблицы выше: до 1000 задач в час одного региона обычно достаточно, а после 10 000 задач в час вы начинаете упираться в лимиты масштабирования одного региона, и распределение нагрузки становится оправданным.

Что делать, если прокси в одном регионе стали работать хуже?

Смените прокси-провайдера для этого региона либо временно перенаправьте его задачи по политике аварийного переключения, описанной выше — менять API-ключ CaptchaAI для этого не нужно.

Обязательно ли использовать Kubernetes для многорегионального деплоя воркеров?

Нет. Пула воркеров с очередью на регион и отдельным health-check-эндпоинтом обычно достаточно. Kubernetes оправдан, если у вас уже есть кластер и десятки сервисов, а не только очередь на решение CAPTCHA.

Как контролировать расходы на межрегиональную передачу данных?

Держите полезную нагрузку результата минимальной (ID задачи, решение, регион, длительность) и группируйте сообщения в очередях пакетами — при $0,02/GB между регионами именно объём передаваемых данных, а не число задач, определяет счёт за трафик.

Следующие шаги

Комментарии для этой статьи отключены.