DevOps & Scaling

Monitorando taxas de resolução de CAPTCHA com Prometheus e Grafana

Um solver de CAPTCHA que falha silenciosamente é pior do que um que falha alto: o time só percebe quando o suporte começa a receber tickets de checkout travado. A resposta é instrumentar o pipeline com Prometheus e visualizar tudo no Grafana antes que a taxa de sucesso caia sem avisar ninguém.

Este guia mostra como expor as métricas certas a partir da API da CaptchaAI, montar um exportador em Python, subir o stack com Docker Compose e configurar os três alertas que realmente importam em produção: saldo baixo, taxa de erro alta e tempo de resolução fora da curva. Se os seus workers rodam em uma região como AWS sa-east-1 (São Paulo), acompanhar o captcha_solve_duration também ajuda a separar latência de rede de lentidão real do desafio.

O que você vai montar:

  • Um exportador Python que instrumenta cada chamada de resolução
  • Um prometheus.yml apontando para esse exportador
  • Uma stack local com Docker Compose (solver + Prometheus + Grafana)
  • Cinco consultas PromQL prontas para os painéis
  • Três regras de alerta para saldo, erro e latência

Quais métricas acompanhar

As seis métricas abaixo cobrem os três tipos que o Prometheus espera: contadores para volume acumulado, medidores para o estado atual e um histograma para a distribuição do tempo de resolução. Nenhuma exige lógica customizada complexa — o exportador da próxima seção já expõe todas no formato certo.

Se você opera no plano BASIC (US$ 15/mês, 5 threads) ou ainda está validando a API antes de migrar para um plano maior, captcha_queue_length e captcha_balance_usd são as duas métricas que evitam a maior dor de cabeça: fila crescendo porque as threads disponíveis já estão todas ocupadas, ou saldo zerando no meio de um lote grande de resolução.

Métrica Tipo Objetivo
captcha_solves_total Contador Total de tentativas de resolução
captcha_solves_success Contador Soluções bem-sucedidas
captcha_solves_errors Contador Falha na resolução (por tipo de erro)
captcha_solve_duration Histograma Distribuição do tempo de resolução
captcha_balance Medidor Saldo atual da conta
captcha_queue_length Medidor Tarefas pendentes na fila

Exportador de métricas em Python

O exportador abaixo envolve a chamada de resolução com instrumentação automática: cada tentativa incrementa captcha_solves_total, cada sucesso registra a duração no histograma, e cada falha soma ao contador de erros usando o próprio código de erro como label. O método update_balance() consulta getbalance e atualiza o medidor de saldo — chame-o em um job periódico (a cada cinco minutos já é suficiente) para não sobrecarregar a API à toa.

# metrics.py
import time
import requests
from prometheus_client import (
    Counter, Histogram, Gauge, start_http_server,
)


# Define metrics
SOLVES_TOTAL = Counter(
    "captcha_solves_total",
    "Total CAPTCHA solve attempts",
    ["method"],
)

SOLVES_SUCCESS = Counter(
    "captcha_solves_success",
    "Successful CAPTCHA solves",
    ["method"],
)

SOLVES_ERRORS = Counter(
    "captcha_solves_errors",
    "Failed CAPTCHA solves",
    ["method", "error_code"],
)

SOLVE_DURATION = Histogram(
    "captcha_solve_duration_seconds",
    "CAPTCHA solve duration in seconds",
    ["method"],
    buckets=[5, 10, 15, 20, 30, 45, 60, 90, 120],
)

BALANCE = Gauge(
    "captcha_balance_usd",
    "Current CaptchaAI account balance in USD",
)

QUEUE_LENGTH = Gauge(
    "captcha_queue_length",
    "Number of pending CAPTCHA tasks",
)


class InstrumentedSolver:
    """Solver with Prometheus metric instrumentation."""

    def __init__(self, api_key):
        self.api_key = api_key
        self.base = "https://ocr.captchaai.com"

    def solve(self, method, **params):
        """Solve CAPTCHA with metric collection."""
        SOLVES_TOTAL.labels(method=method).inc()
        start = time.time()

        try:
            token = self._do_solve(method, params)
            duration = time.time() - start

            SOLVES_SUCCESS.labels(method=method).inc()
            SOLVE_DURATION.labels(method=method).observe(duration)

            return token

        except Exception as e:
            error_code = str(e)[:30]
            SOLVES_ERRORS.labels(
                method=method, error_code=error_code,
            ).inc()
            raise

    def update_balance(self):
        """Fetch and update balance metric."""
        resp = requests.get(f"{self.base}/res.php", params={
            "key": self.api_key,
            "action": "getbalance",
            "json": 1,
        }, timeout=15)
        balance = float(resp.json()["request"])
        BALANCE.set(balance)
        return balance

    def _do_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"))

        task_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": task_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")


# Start metrics server on port 8000
start_http_server(8000)
print("Metrics server running on :8000/metrics")

Rode esse script como um processo próprio, separado do worker que resolve os CAPTCHAs — assim, uma reinicialização do solver não derruba junto o endpoint /metrics.


Configuração do Prometheus

Aponte o Prometheus para o endpoint exposto na porta 8000. Um intervalo de coleta de 10 a 15 segundos já é suficiente — raspar mais rápido do que isso satura o /metrics sem ganho real de precisão.

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:

  - job_name: "captcha-solver"
    static_configs:

      - targets: ["solver-app:8000"]
    scrape_interval: 10s

Stack com Docker Compose

Para subir tudo junto em um ambiente de teste, três serviços — solver, Prometheus e Grafana — já cobrem o essencial. Troque a senha padrão do Grafana (GF_SECURITY_ADMIN_PASSWORD) antes de expor a porta 3000 fora da rede interna.

# docker-compose.yml
version: "3.8"

services:
  solver:
    build: .
    environment:

      - CAPTCHAAI_KEY=${CAPTCHAAI_KEY}
    ports:

      - "8000:8000"

  prometheus:
    image: prom/prometheus:latest
    volumes:

      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:

      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    ports:

      - "3000:3000"
    environment:

      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:

      - grafana-data:/var/lib/grafana

volumes:
  grafana-data:

Consultas para o painel do Grafana

As cinco consultas PromQL abaixo cobrem os painéis essenciais. Comece por elas e adicione métricas específicas do seu pipeline conforme a necessidade.

Taxa de sucesso

Retorna a porcentagem de resoluções bem-sucedidas na janela de 5 minutos — o painel que qualquer stakeholder olha primeiro.

rate(captcha_solves_success[5m])
/ rate(captcha_solves_total[5m]) * 100

Tempo médio de resolução

Útil para ver tendência geral, mas sozinho esconde os outliers — combine sempre com o P95 abaixo.

rate(captcha_solve_duration_seconds_sum[5m])
/ rate(captcha_solve_duration_seconds_count[5m])

Taxa de erro por tipo

Agrupar por error_code mostra se o problema é concentrado (um único tipo de erro disparando) ou distribuído (sintoma de instabilidade geral na rede ou no proxy).

sum by (error_code) (
  rate(captcha_solves_errors[5m])
)

Saldo ao longo do tempo

Um gráfico de linha simples já basta — o que importa é a inclinação da curva, não o valor absoluto em um único instante.

captcha_balance_usd

Duração de resolução (P95)

P95 importa mais do que a média porque captura os casos lentos que afetam a experiência do usuário final, mesmo quando a média parece saudável.

histogram_quantile(0.95,
  rate(captcha_solve_duration_seconds_bucket[5m])
)

Esse conjunto de cinco consultas já é suficiente para o primeiro dashboard — refine os thresholds depois de observar uma semana de tráfego real.


Regras de alerta

As três regras abaixo cobrem os sintomas mais comuns em produção:

  • saldo acabando antes do fim do ciclo de faturamento
  • taxa de erro subindo acima do normal
  • tempo de resolução degradando (P95 alto)

Ajuste os limites (< 5, > 0.1, > 60) para o perfil real do seu tráfego antes de ativar em produção.

# alert_rules.yml
groups:

  - name: captcha-alerts
    rules:

      - alert: LowBalance
        expr: captcha_balance_usd < 5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "CaptchaAI balance below $5"

      - alert: HighErrorRate
        expr: |
          rate(captcha_solves_errors[5m])
          / rate(captcha_solves_total[5m]) > 0.1
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "CAPTCHA error rate above 10%"

      - alert: SlowSolveTime
        expr: |
          histogram_quantile(0.95,
            rate(captcha_solve_duration_seconds_bucket[5m])
          ) > 60
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "P95 solve time exceeds 60s"

Conecte essas regras ao Slack, Telegram ou e-mail via Alertmanager — um aviso de saldo baixo às três da manhã vale muito mais do que descobrir a conta zerada só às nove.


Solução de problemas

Os quatro problemas abaixo cobrem praticamente todo chamado que chega sobre essa stack:

Problema Causa Correção
Nenhuma métrica em /metrics Servidor não iniciado Chame start_http_server(8000)
Prometheus mostra "down" Endereço de destino errado Verifique a rede e a porta do Docker
Grafana não mostra dados Prometheus não adicionado como fonte Adicione a fonte de dados Prometheus no Grafana
Métricas zeradas após reiniciar Reset de contador é esperado após restart Use rate(), nunca o contador bruto

Perguntas frequentes

Preciso de um plano maior para expor essas métricas?

Não. Qualquer plano, do BASIC ao VIP-3, usa os mesmos endpoints in.php/res.php e a mesma chamada getbalance — o exportador funciona igual independente do plano. O que muda é o número de threads simultâneas; em volumes maiores (ENTERPRISE ou VIP-1/2/3) vale reduzir o intervalo de scrape para acompanhar picos de fila mais de perto.

Como alertar por Slack ou e-mail em vez de só olhar o Grafana?

Adicione o Alertmanager ao stack e aponte as regras de alerta para ele. O Grafana também tem notificação nativa por canal, mas separar a avaliação (Prometheus/Alertmanager) da visualização (Grafana) evita perder um alerta só porque o painel estava fechado.

Consigo monitorar várias instâncias de worker ao mesmo tempo?

Sim. Cada worker expõe seu próprio endpoint /metrics na porta 8000; basta declarar um target por instância em prometheus.yml. As consultas PromQL já agregam automaticamente entre todas as instâncias — não é preciso somar nada manualmente.

Por quanto tempo devo guardar essas métricas?

Quinze dias de retenção local no Prometheus (o padrão de fábrica) já cobre a maioria dos casos de debugging. Para comparar tendência mês a mês, exporte para um Grafana Cloud ou um Thanos/Mimir de longo prazo — manter tudo local por muito mais tempo raramente compensa o espaço em disco.


Veja também


Observabilidade completa — monitore a CaptchaAI com Prometheus a partir de hoje.

Os comentários estão desativados para este artigo.