Trocar a versão do seu pipeline de resolução de CAPTCHA sem perder nenhuma tarefa em produção é uma questão de manter dois ambientes idênticos no ar ao mesmo tempo — um recebendo tráfego real (azul), outro pronto para assumir (verde) — e só migrar depois que o novo ambiente já provou, com CAPTCHAs reais, que está funcionando.
Diferente de um deploy tradicional, não existe reinicialização "no escuro": o ambiente novo passa por um teste canário antes de receber tráfego real, e a reversão leva o mesmo tempo que a troca — segundos, não minutos.
Neste guia você vai:
- Entender como o roteador decide qual ambiente recebe o tráfego de produção.
- Rodar um teste canário automatizado no standby antes de qualquer troca real.
- Implementar rollback instantâneo em Python e Node.js.
- Seguir um checklist de 5 passos para a virada de tráfego.
Como funciona a arquitetura azul-verde
┌─────────────────────┐
[Scraper Clients] → │ Traffic Router │
└──────┬──────┬───────┘
│ │
Active│ │Standby
▼ ▼
┌───────┐ ┌───────┐
│ BLUE │ │ GREEN │
│Workers│ │Workers│
└───┬───┘ └───┬───┘
│ │
└────┬─────┘
▼
[CaptchaAI API]
O roteador de tráfego fica entre os clientes (scrapers, workers de automação) e os dois pools de workers. Só o ambiente ativo recebe tráfego real; o standby fica isolado, disponível apenas para os testes canário — é essa separação que permite reverter em segundos: a troca é só uma mudança de referência no roteador, não um novo deploy.
Se os seus workers rodam perto dos usuários finais — por exemplo, na região sa-east-1 (São Paulo) da AWS, para manter baixa latência com tráfego da América Latina — mantenha blue e green na mesma região. Trocar de região junto com a versão mistura duas variáveis de risco na mesma janela de deploy e dificulta isolar a causa se algo sair errado.
Checklist do fluxo de implantação
| Passo | Ação | Gatilho de rollback |
|---|---|---|
| 1 | Implantar a nova versão só no ambiente standby | Falha no build |
| 2 | Rodar o teste canário no standby com tarefas reais | Taxa de sucesso abaixo de 80% |
| 3 | Migrar o tráfego para o ambiente novo | — |
| 4 | Monitorar a taxa de erro por 5 minutos | Taxa de erro acima de 20% |
| 5 | Desativar o ambiente antigo | — |
Implementação do roteador azul-verde
A implementação abaixo cobre as duas pontas do problema: o pool de workers que efetivamente resolve o CAPTCHA (Python) e o orquestrador que decide quando trocar, testar e reverter (Node.js). Os dois falam com os mesmos endpoints in.php e res.php da CaptchaAI — a diferença está em qual camada guarda o estado de "quem está ativo agora".
Router em Python: pool de workers com métricas de erro
O CaptchaWorkerPool abaixo encapsula um ambiente (blue ou green): ele envia a tarefa, faz o polling do resultado e guarda tasks_solved e errors para alimentar a decisão de troca. O BlueGreenRouter decide qual pool está ativo agora e expõe canary_test para validar o standby antes de qualquer troca real:
import os
import time
import threading
import requests
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
class CaptchaWorkerPool:
"""Represents one environment (blue or green)."""
def __init__(self, name, config):
self.name = name
self.config = config
self.session = requests.Session()
self.tasks_solved = 0
self.errors = 0
self.healthy = True
def solve(self, task):
resp = self.session.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": task.get("method", "userrecaptcha"),
"googlekey": task["sitekey"],
"pageurl": task["pageurl"],
"json": 1
})
data = resp.json()
if data.get("status") != 1:
self.errors += 1
return {"error": data.get("request")}
captcha_id = data["request"]
for _ in range(60):
time.sleep(5)
result = self.session.get(
"https://ocr.captchaai.com/res.php",
params={
"key": API_KEY,
"action": "get",
"id": captcha_id,
"json": 1
}
).json()
if result.get("status") == 1:
self.tasks_solved += 1
return {"solution": result["request"]}
if result.get("request") != "CAPCHA_NOT_READY":
self.errors += 1
return {"error": result.get("request")}
self.errors += 1
return {"error": "TIMEOUT"}
@property
def error_rate(self):
total = self.tasks_solved + self.errors
return self.errors / total if total > 0 else 0.0
@property
def stats(self):
return {
"name": self.name,
"solved": self.tasks_solved,
"errors": self.errors,
"error_rate": round(self.error_rate, 4),
"healthy": self.healthy
}
class BlueGreenRouter:
def __init__(self, blue_config, green_config):
self.blue = CaptchaWorkerPool("blue", blue_config)
self.green = CaptchaWorkerPool("green", green_config)
self.active = self.blue
self.standby = self.green
self.lock = threading.Lock()
def solve(self, task):
"""Route task to the active environment."""
with self.lock:
pool = self.active
return pool.solve(task)
def switch(self):
"""Swap active and standby environments."""
with self.lock:
self.active, self.standby = self.standby, self.active
print(f"Switched: {self.active.name} is now ACTIVE")
return self.active.name
def rollback(self):
"""Switch back to the previous environment."""
return self.switch()
def canary_test(self, test_tasks, threshold=0.9):
"""Run test tasks on standby before switching."""
successes = 0
for task in test_tasks:
result = self.standby.solve(task)
if "solution" in result:
successes += 1
success_rate = successes / len(test_tasks) if test_tasks else 0
passed = success_rate >= threshold
print(
f"Canary test: {successes}/{len(test_tasks)} "
f"({success_rate:.0%}) — {'PASS' if passed else 'FAIL'}"
)
return passed
@property
def status(self):
return {
"active": self.active.stats,
"standby": self.standby.stats
}
# Usage
router = BlueGreenRouter(
blue_config={"version": "1.2.0", "workers": 4},
green_config={"version": "1.3.0", "workers": 4}
)
# Canary test before switching
test_tasks = [
{"sitekey": "6Le-wvkS...", "pageurl": "https://example.com/test"}
]
if router.canary_test(test_tasks, threshold=0.8):
router.switch()
print(f"Now active: {router.status['active']['name']}")
else:
print("Canary failed — staying on current environment")
O bloco acima já cobre produção em escala pequena com controle manual. Para automatizar deploy, canário e rollback num único fluxo — sem alguém rodando comandos manualmente durante a madrugada —, o exemplo em Node.js a seguir encapsula as três etapas numa única chamada.
Switch automatizado em Node.js: deploy, canário e rollback numa chamada
A classe BlueGreenDeployment faz o deploy no ambiente standby, roda o canário via canaryCheck, só troca o tráfego se a taxa de sucesso passar de 80%, e monitora o ambiente ativo por um período configurável — revertendo sozinha se a taxa de erro passar de 20%:
const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
class BlueGreenDeployment {
constructor() {
this.environments = {
blue: { name: "blue", version: null, solved: 0, errors: 0 },
green: { name: "green", version: null, solved: 0, errors: 0 },
};
this.activeEnv = "blue";
}
get active() {
return this.environments[this.activeEnv];
}
get standby() {
return this.environments[this.activeEnv === "blue" ? "green" : "blue"];
}
async deploy(version, config = {}) {
const target = this.standby;
target.version = version;
target.solved = 0;
target.errors = 0;
console.log(`Deployed v${version} to ${target.name} (standby)`);
// Run canary checks
const canaryPassed = await this.canaryCheck(config.canaryTasks || []);
if (!canaryPassed && config.canaryTasks?.length > 0) {
console.log("Canary check failed — aborting deployment");
return { success: false, reason: "canary_failed" };
}
// Switch traffic
this.activeEnv = target.name;
console.log(`Switched traffic to ${target.name} (v${version})`);
// Monitor for rollback
if (config.monitorDuration) {
const stable = await this.monitorAfterSwitch(config.monitorDuration);
if (!stable) {
this.rollback();
return { success: false, reason: "post_deploy_errors" };
}
}
return { success: true, active: this.activeEnv };
}
async canaryCheck(tasks) {
if (tasks.length === 0) return true;
let successes = 0;
for (const task of tasks) {
try {
await this.solveCaptcha(task);
successes++;
} catch (err) {
console.log(`Canary task failed: ${err.message}`);
}
}
const rate = successes / tasks.length;
console.log(`Canary: ${successes}/${tasks.length} (${(rate * 100).toFixed(0)}%)`);
return rate >= 0.8;
}
async monitorAfterSwitch(durationMs) {
const start = Date.now();
const checkInterval = 10000;
while (Date.now() - start < durationMs) {
await new Promise((r) => setTimeout(r, checkInterval));
const errorRate = this.active.errors /
Math.max(1, this.active.solved + this.active.errors);
if (errorRate > 0.2) {
console.log(`Error rate ${(errorRate * 100).toFixed(1)}% — triggering rollback`);
return false;
}
}
return true;
}
rollback() {
const previous = this.activeEnv === "blue" ? "green" : "blue";
console.log(`Rolling back: ${this.activeEnv} → ${previous}`);
this.activeEnv = previous === "blue" ? "blue" : "green";
}
async solveCaptcha(task) {
const submitResp = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: task.sitekey,
pageurl: task.pageurl,
json: 1,
},
});
if (submitResp.data.status !== 1) {
this.active.errors++;
throw new Error(submitResp.data.request);
}
const captchaId = submitResp.data.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const pollResp = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
});
if (pollResp.data.status === 1) {
this.active.solved++;
return pollResp.data.request;
}
if (pollResp.data.request !== "CAPCHA_NOT_READY") {
this.active.errors++;
throw new Error(pollResp.data.request);
}
}
this.active.errors++;
throw new Error("TIMEOUT");
}
}
// Deploy new version with canary and monitoring
const deployer = new BlueGreenDeployment();
deployer
.deploy("1.3.0", {
canaryTasks: [
{ sitekey: "6Le-wvkS...", pageurl: "https://example.com/test" },
],
monitorDuration: 60000, // Monitor for 1 minute after switch
})
.then((result) => console.log("Deploy result:", result));
Runbook de redução gradual de tráfego
- Mantenha os pools azul e verde em checagens de saúde espelhadas antes de rotear qualquer tráfego real para a pilha nova.
- Desloque o tráfego em etapas explícitas e exija latência estável e taxa de erro controlada antes de avançar para a próxima etapa.
- Reverta de imediato se a latência de resolução, a taxa de erro alvo ou a profundidade da fila ultrapassarem o limite combinado com o time — não espere o próximo checkpoint programado.
Perguntas frequentes
Quantos workers preciso para manter os dois ambientes ativos sem estourar meu plano de threads?
Blue e green consomem threads do mesmo plano — os dois ambientes não somam limites separados. No BASIC (US$ 15/mês, 5 threads), 2–3 workers por ambiente já ocupam boa parte da cota durante o teste canário; em ADVANCE (US$ 90/mês, 50 threads) ou PREMIUM (US$ 170/mês, 100 threads) sobra folga para manter os dois lados com workers de reserva mesmo durante a troca.
Por quanto tempo faz sentido rodar o teste canário antes de virar todo o tráfego?
Rode pelo menos 10 resoluções reais de CAPTCHA no ambiente standby antes de considerar a taxa confiável. Para sistemas críticos, direcione 5–10% do tráfego de produção para o standby por cerca de 10 minutos antes da virada completa — um lote pequeno demais mascara problemas que só aparecem sob volume real.
O que acontece com as tarefas que já estavam em andamento no ambiente antigo quando o tráfego migra?
Elas continuam rodando até o res.php devolver a resposta ou o timeout estourar — a troca de tráfego afeta só as tarefas novas. Por isso vale esperar as tarefas em voo esgotarem antes de desativar o ambiente antigo: desligar cedo demais derruba resoluções que já estavam quase prontas.
Dá para montar isso com um proxy reverso simples, sem load balancer dedicado?
Sim. NGINX (ou HAProxy) apontando para a porta do ambiente ativo já resolve num único host, com blue e green como processos ou contêineres separados. O roteador em Python deste guia funciona do mesmo jeito nesse cenário — trocar de ambiente é só redirecionar o proxy para a outra porta.
Erros comuns e como corrigir
| Problema | Causa provável | Correção |
|---|---|---|
| Canário passa, mas a produção falha | Tarefas de teste simples demais, sem cobrir todos os sitekey em uso |
Use tarefas reais retiradas da fila de produção, não exemplos fixos |
| Reversões frequentes | Limite de monitoramento agressivo demais para o volume real | Aumente o limite de erro e o período de observação antes de decidir |
| A divisão de tráfego não fica limpa durante a troca | Requisições em andamento continuam presas no ambiente antigo | Aguarde as tarefas em voo esgotarem antes de desativar o ambiente antigo |
| Os dois ambientes ficam instáveis ao mesmo tempo | Falha de uma dependência compartilhada (rede, API da CaptchaAI) | Ative um circuit breaker; não reverta por um problema de infraestrutura que afeta os dois lados |
| Latência sobe logo após a troca | O ambiente novo ainda não aqueceu as conexões (pool "frio") | Aqueça o standby com requisições sintéticas antes da troca real |
Próximos passos
Configure a chave de API da CaptchaAI e coloque o roteador azul-verde deste guia em produção — comece pela CaptchaAI e faça sua primeira troca de tráfego sem downtime.
Guias relacionados: