# Como Construir um Sistema de Retry Inteligente com Dead Letter Queue para Consultas de CPF

> Aprenda a construir um sistema de retry com backoff exponencial e dead letter queue para consultas de CPF que falharam. Garanta que nenhuma consulta se perca.

**Publicado:** 02/03/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/retry-inteligente-dead-letter-queue-cpf

---


## Introdução

Nem toda falha na consulta de CPF é permanente. Timeouts, erros de rede e indisponibilidades temporárias são comuns em integrações com APIs externas. Um sistema de **retry inteligente** sabe diferenciar falhas temporárias de permanentes, aplica backoff exponencial para não sobrecarregar o provedor e envia consultas que esgotaram as tentativas para uma **dead letter queue (DLQ)** para reprocessamento posterior.

---

## Anatomia de um retry inteligente

Nem toda falha merece retry. Erros 4xx geralmente indicam problemas no input e não vale a pena repetir. Já erros 5xx e timeouts são candidatos perfeitos para nova tentativa.

| Código HTTP | Tipo de erro | Deve fazer retry? | Justificativa |
|------------|-------------|-------------------|---------------|
| 400 | Bad Request | Não | Input inválido, retry não resolve |
| 401 | Unauthorized | Não | Credencial errada, precisa correção |
| 403 | Forbidden | Não | Sem permissão, precisa ajuste |
| 408 | Timeout | Sim | Temporário, servidor sobrecarregado |
| 429 | Rate Limit | Sim (com delay) | Esperar e tentar novamente |
| 500 | Internal Error | Sim | Erro no servidor, pode se resolver |
| 502/503 | Indisponível | Sim | Servidor temporariamente fora |

- **Backoff exponencial** -- cada retry espera progressivamente mais tempo (1s, 2s, 4s, 8s) para dar tempo ao servidor de se recuperar
- **Jitter** -- adiciona aleatoriedade ao tempo de espera para evitar que múltiplos clientes façam retry no mesmo instante
- **Max retries** -- limita o número de tentativas para não ficar em loop infinito

---

## Implementando o retry com backoff exponencial

```javascript
const axios = require("axios");

class RetryConfig {
 constructor({
 maxRetries = 3,
 baseDelay = 1000,
 maxDelay = 30000,
 jitterFactor = 0.5,
 retryableStatuses = [408, 429, 500, 502, 503, 504],
 } = {}) {
 this.maxRetries = maxRetries;
 this.baseDelay = baseDelay;
 this.maxDelay = maxDelay;
 this.jitterFactor = jitterFactor;
 this.retryableStatuses = retryableStatuses;
 }

 calculateDelay(attempt) {
 const exponential = this.baseDelay * Math.pow(2, attempt);
 const capped = Math.min(exponential, this.maxDelay);
 const jitter = capped * this.jitterFactor * Math.random();
 return capped + jitter;
 }

 isRetryable(error) {
 if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
 return true;
 }
 if (error.response) {
 return this.retryableStatuses.includes(error.response.status);
 }
 return true; // Erros de rede são retryable
 }
}

async function consultarCpfComRetry(cpf, config = new RetryConfig()) {
 let lastError;

 for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
 try {
 const response = await axios.get(
 `https://api.cpfhub.io/cpf/${cpf}`,
 {
 headers: { "x-api-key": process.env.CPFHUB_API_KEY },
 timeout: 10000,
 }
 );
 return response.data;
 } catch (error) {
 lastError = error;

 if (!config.isRetryable(error) || attempt === config.maxRetries) {
 break;
 }

 const delay = config.calculateDelay(attempt);
 console.log(`Tentativa ${attempt + 1} falhou. Retry em ${delay}ms...`);
 await new Promise((resolve) => setTimeout(resolve, delay));
 }
 }

 throw lastError;
}
```

- **calculateDelay** -- implementa backoff exponencial com jitter para distribuir as retentativas no tempo
- **isRetryable** -- avalia se o erro é temporário e merece nova tentativa
- **Loop controlado** -- cada iteração incrementa o attempt e calcula um delay maior

---

## Implementando a dead letter queue

Quando todas as tentativas de retry se esgotam, a consulta não deve ser simplesmente descartada. Uma DLQ armazena essas consultas falhadas para reprocessamento posterior.

```javascript
const { Queue, Worker } = require("bullmq");
const Redis = require("ioredis");

const connection = new Redis({ host: "localhost", port: 6379 });

// Fila principal de consultas
const cpfQueue = new Queue("cpf-consultas", { connection });

// Dead Letter Queue
const cpfDLQ = new Queue("cpf-dlq", { connection });

// Worker que processa consultas
const worker = new Worker(
 "cpf-consultas",
 async (job) => {
 const { cpf, requestId } = job.data;
 try {
 const resultado = await consultarCpfComRetry(cpf);
 return resultado;
 } catch (error) {
 // Após esgotar retries, envia para DLQ
 await cpfDLQ.add("consulta-falhada", {
 cpf,
 requestId,
 error: error.message,
 originalJobId: job.id,
 failedAt: new Date().toISOString(),
 attempts: job.attemptsMade,
 });
 throw error;
 }
 },
 {
 connection,
 concurrency: 10,
 limiter: { max: 100, duration: 60000 },
 }
);

// Enfileirar uma consulta
async function enfileirarConsulta(cpf, requestId) {
 await cpfQueue.add("consultar", { cpf, requestId }, {
 attempts: 3,
 backoff: { type: "exponential", delay: 2000 },
 removeOnComplete: 1000,
 removeOnFail: false,
 });
}
```

- **BullMQ** -- biblioteca robusta para filas de trabalho com Redis, suporta retry nativo e prioridades
- **DLQ dedicada** -- fila separada que armazena jobs falhados com contexto completo do erro
- **Limiter** -- controla a taxa de requisições para respeitar rate limits da API

---

## Reprocessando a dead letter queue

A DLQ não é um cemitério de mensagens. Ela precisa de um mecanismo de reprocessamento para tentar novamente as consultas quando o problema for resolvido.

```javascript
// Worker da DLQ - reprocessa periodicamente
const dlqWorker = new Worker(
 "cpf-dlq",
 async (job) => {
 const { cpf, requestId, failedAt } = job.data;

 // Verifica se a consulta não é muito antiga
 const horasDesdeAFalha =
 (Date.now() - new Date(failedAt).getTime()) / 3600000;

 if (horasDesdeAFalha > 24) {
 console.log(`Job ${requestId} expirado (${horasDesdeAFalha}h)`);
 return { status: "expired" };
 }

 // Tenta reprocessar com configuração mais tolerante
 const resultado = await consultarCpfComRetry(
 cpf,
 new RetryConfig({ maxRetries: 5, baseDelay: 5000 })
 );

 return resultado;
 },
 {
 connection,
 concurrency: 2,
 limiter: { max: 10, duration: 60000 },
 }
);

// Métricas da DLQ
async function obterMetricasDLQ() {
 const waiting = await cpfDLQ.getWaitingCount();
 const failed = await cpfDLQ.getFailedCount();
 return { pendentes: waiting, falhados: failed };
}
```

| Parâmetro DLQ | Valor recomendado | Motivo |
|--------------|-------------------|--------|
| Concurrency | 2-5 | Não sobrecarregar a API durante reprocessamento |
| TTL (expiração) | 24h | Consultas muito antigas podem ser irrelevantes |
| Rate limit | 10/min | Baixa prioridade, não competir com tráfego normal |
| Alerta | > 100 pendentes | Indica problema sistêmico que precisa de atenção |

---

## Perguntas frequentes

### O que é necessário para implementar validação de CPF neste contexto?
A validação de CPF exige uma chamada à API com o número do documento e a chave de autenticação. A CPFHub.io retorna o status do CPF, nome do titular e data de nascimento em menos de 200ms, permitindo a verificação em tempo real durante o cadastro ou transação.

### A API CPFHub.io funciona para todos os volumes de consulta?
Sim. O plano gratuito oferece 50 consultas por mês sem cartão de crédito — ideal para testes e projetos pequenos. Para volumes maiores, o plano Pro inclui 1.000 consultas mensais por R$149. Se o limite for ultrapassado, a API não bloqueia: cobra R$0,15 por consulta adicional.

### Como garantir conformidade com a LGPD ao usar uma API de CPF?
Use o CPF apenas para a finalidade declarada ao titular, armazene apenas o necessário (não guarde o CPF cru se um token bastar), implemente controle de acesso aos logs de consulta e documente a base legal para o tratamento. A [ANPD](https://www.gov.br/anpd) orienta que dados de identificação devem ser tratados com o princípio da necessidade.

### Quanto tempo leva para integrar a API CPFHub.io?
A integração básica leva menos de 30 minutos: crie uma conta em cpfhub.io, gere a API key no painel e faça uma chamada GET para `https://api.cpfhub.io/cpf/{CPF}` com o header `x-api-key`. A documentação inclui exemplos em Python, Node.js, PHP, Java e outras linguagens.

### Leia também

- [SLA de API de CPF: níveis de disponibilidade e o que exigir do seu provedor](https://cpfhub.io/blog/sla-api-cpf-niveis-disponibilidade)
- [Como validar CPF no frontend com React e API REST](https://cpfhub.io/blog/como-validar-cpf-no-frontend-com-react-e-api-rest)
- [10 erros mais comuns ao integrar uma API de CPF e como evitá-los](https://cpfhub.io/blog/10-erros-mais-comuns-ao-integrar-uma-api-de-cpf)
- [API de CPF grátis para desenvolvedores: como começar em 5 minutos](https://cpfhub.io/blog/api-cpf-gratis-desenvolvedores-comecar-5-minutos)

---

## Conclusão

Um sistema de retry inteligente com dead letter queue garante que nenhuma consulta de CPF se perca, mesmo em cenários de falha. O backoff exponencial com jitter protege o provedor de sobrecarga, enquanto a DLQ preserva as consultas para reprocessamento posterior. Teste essa abordagem com a API do [cpfhub.io](https://www.cpfhub.io/).

