Como Processar Consultas de CPF em Paralelo Usando Goroutines

Aprenda a processar consultas de CPF em paralelo usando goroutines em Go, com channels, semáforos, errgroup e controle de concorrência.

Redação CPFHub.io
Redação CPFHub.io
··7 min de leitura
Como Processar Consultas de CPF em Paralelo Usando Goroutines

Para processar consultas de CPF em paralelo com goroutines em Go, utilize sync.WaitGroup para lotes simples, um channel como semáforo para controlar a concorrência máxima e errgroup quando precisar de propagação de erros e cancelamento coordenado. Cada goroutine consome apenas alguns kilobytes de memória, permitindo processar centenas de CPFs simultaneamente com latência próxima à de uma única consulta sequencial.

Introdução

Go foi projetada desde o início para concorrência, e as goroutines são a primitiva mais poderosa da linguagem para esse propósito. Processar centenas ou milhares de consultas de CPF em paralelo usando goroutines é extremamente eficiente: cada goroutine consome apenas alguns kilobytes de memória, e o scheduler do Go distribui o trabalho automaticamente entre os cores disponíveis.

Goroutines vs. threads tradicionais

Goroutines oferecem vantagens significativas sobre threads do sistema operacional.

CaracterísticaGoroutineThread do SO
Memória inicial~2KB~1MB
CriaçãoMicrossegundosMilissegundos
Troca de contexto~200ns~1000ns
Limite práticoMilhõesMilhares
GerenciamentoRuntime do GoSistema operacional
ComunicaçãoChannels (seguro)Mutexes (propenso a erros)

Goroutines são leves -- é possível criar milhares sem impacto significativo na memória.

Channels são seguros -- permitem comunicação entre goroutines sem necessidade de locks manuais.

Scheduler eficiente -- o runtime do Go multiplexa goroutines em threads do SO automaticamente.


Padrão básico com WaitGroup

A abordagem mais simples usa sync.WaitGroup para aguardar todas as goroutines completarem.

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "sync"
    "time"
)

type ResultadoCPF struct {
    CPF string `json:"cpf"`
    Sucesso bool `json:"sucesso"`
    Nome string `json:"nome,omitempty"`
    Erro string `json:"erro,omitempty"`
}

type APIResponse struct {
    Success bool `json:"success"`
    Data struct {
    Name string `json:"name"`
    } `json:"data"`
}

func consultarCPF(cpf string, apiKey string) ResultadoCPF {
    url := fmt.Sprintf("https://api.cpfhub.io/cpf/%s", cpf)
    req, _ := http.NewRequest("GET", url, nil)
    req.Header.Set("x-api-key", apiKey)

    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
    return ResultadoCPF{CPF: cpf, Sucesso: false, Erro: err.Error()}
    }
    defer resp.Body.Close()

    var resultado APIResponse
    json.NewDecoder(resp.Body).Decode(&resultado)

    if resultado.Success {
    return ResultadoCPF{CPF: cpf, Sucesso: true, Nome: resultado.Data.Name}
    }
    return ResultadoCPF{CPF: cpf, Sucesso: false, Erro: "nao encontrado"}
}

func consultarLoteSimples(cpfs []string, apiKey string) []ResultadoCPF {
    var wg sync.WaitGroup
    resultados := make([]ResultadoCPF, len(cpfs))

    for i, cpf := range cpfs {
    wg.Add(1)
    go func(idx int, c string) {
    defer wg.Done()
    resultados[idx] = consultarCPF(c, apiKey)
    }(i, cpf)
    }

    wg.Wait()
    return resultados
}

func main() {
    apiKey := os.Getenv("CPFHUB_API_KEY")
    cpfs := []string{"12345678901", "98765432100", "11122233344"}

    inicio := time.Now()
    resultados := consultarLoteSimples(cpfs, apiKey)
    duracao := time.Since(inicio)

    for _, r := range resultados {
    fmt.Printf("CPF: %s | Sucesso: %v | Nome: %s\n", r.CPF, r.Sucesso, r.Nome)
    }
    fmt.Printf("Duracao: %v\n", duracao)
}

Controle de concorrência com semáforo

Para evitar sobrecarregar a API, use um channel como semáforo para limitar goroutines simultâneas.

func consultarLoteControlado(cpfs []string, apiKey string, maxConcorrencia int) []ResultadoCPF {
    var wg sync.WaitGroup
    resultados := make([]ResultadoCPF, len(cpfs))
    semaforo := make(chan struct{}, maxConcorrencia)

    for i, cpf := range cpfs {
    wg.Add(1)
    go func(idx int, c string) {
    defer wg.Done()
    semaforo <- struct{}{} // Adquirir slot
    defer func() { <-semaforo }() // Liberar slot

    resultados[idx] = consultarCPF(c, apiKey)
    }(i, cpf)
    }

    wg.Wait()
    return resultados
}
Concorrência100 CPFs1.000 CPFs10.000 CPFs
1 (sequencial)~20s~200s~2000s
10~2s~20s~200s
50~0.4s~4s~40s
100~0.2s~2s~20s

Padrão worker pool com channels

O padrão worker pool distribui trabalho entre um número fixo de goroutines trabalhadoras.

func workerPool(cpfs []string, apiKey string, numWorkers int) []ResultadoCPF {
    type tarefa struct {
    indice int
    cpf string
    }

    tarefas := make(chan tarefa, len(cpfs))
    resultadosChan := make(chan ResultadoCPF, len(cpfs))

    // Iniciar workers
    var wg sync.WaitGroup
    for w := 0; w < numWorkers; w++ {
    wg.Add(1)
    go func() {
    defer wg.Done()
    for t := range tarefas {
    resultado := consultarCPF(t.cpf, apiKey)
    resultadosChan <- resultado
    }
    }()
    }

    // Enviar tarefas
    for i, cpf := range cpfs {
    tarefas <- tarefa{indice: i, cpf: cpf}
    }
    close(tarefas)

    // Aguardar workers e fechar canal de resultados
    go func() {
    wg.Wait()
    close(resultadosChan)
    }()

    // Coletar resultados
    resultados := make([]ResultadoCPF, 0, len(cpfs))
    for r := range resultadosChan {
    resultados = append(resultados, r)
    }

    return resultados
}

Errgroup para tratamento de erros

O pacote errgroup oferece propagação de erros e cancelamento coordenado entre goroutines. A recomendação da OWASP para processamento seguro de dados pessoais inclui garantir que falhas parciais não deixem dados inconsistentes — o errgroup facilita esse controle ao cancelar o contexto assim que um erro crítico ocorre.

import (
    "context"
    "golang.org/x/sync/errgroup"
)

func consultarLoteComErrgroup(ctx context.Context, cpfs []string, apiKey string) ([]ResultadoCPF, error) {
    resultados := make([]ResultadoCPF, len(cpfs))
    g, ctx := errgroup.WithContext(ctx)
    g.SetLimit(20) // Máximo 20 goroutines simultâneas

    for i, cpf := range cpfs {
    idx := i
    c := cpf
    g.Go(func() error {
    select {
    case <-ctx.Done():
    return ctx.Err()
    default:
    resultados[idx] = consultarCPF(c, apiKey)
    if resultados[idx].Erro == "rate_limit" {
    return fmt.Errorf("rate limit excedido para CPF %s", c)
    }
    return nil
    }
    })
    }

    if err := g.Wait(); err != nil {
    return resultados, fmt.Errorf("erro no processamento: %w", err)
    }

    return resultados, nil
}

SetLimit -- define o número máximo de goroutines concorrentes.

Context cancelável -- se uma goroutine falhar com erro crítico, as demais podem ser canceladas.

Propagação de erros -- o primeiro erro é retornado ao chamador após todas as goroutines completarem.


Perguntas frequentes

Quantas goroutines simultâneas devo usar para consultar a API de CPF?

O número ideal depende do seu volume e do plano contratado. Um bom ponto de partida é 10 a 20 goroutines simultâneas, o que já reduz o tempo de processamento de lotes em até 95% em relação à abordagem sequencial. Aumente gradualmente monitorando a latência de resposta da API e os erros de timeout.

Como evitar erros de timeout ao processar lotes grandes de CPF?

Configure um timeout explícito no http.Client (recomendado: 10 segundos para absorver os ~900ms de latência com margem de segurança) e use o padrão de semáforo ou errgroup.SetLimit para não disparar todas as goroutines ao mesmo tempo. Para lotes acima de 1.000 CPFs, processe em chunks com pausas entre eles.

O plano gratuito da CPFHub.io suporta processamento em lote?

Sim. O plano gratuito oferece 50 consultas por mês, sem bloqueio ao atingir o limite — consultas adicionais são cobradas a R$0,15 cada. Para testes de carga e desenvolvimento, o plano gratuito é suficiente para validar a implementação das goroutines antes de escalar para o plano Pro (1.000 consultas/mês por R$149).

Qual padrão de concorrência é mais adequado para processar CPFs em produção?

O worker pool com channels é o padrão mais robusto para produção: controla o número exato de goroutines ativas, reutiliza workers em vez de criar novos a cada tarefa e facilita o monitoramento. O errgroup é preferível quando você precisa cancelar todo o lote ao detectar um erro crítico.


Conclusão

As goroutines de Go oferecem uma forma elegante e eficiente de processar consultas de CPF em paralelo. Desde o padrão simples com WaitGroup até o worker pool com channels e o errgroup para tratamento coordenado de erros, Go fornece primitivas poderosas para cada cenário de concorrência. A chave é escolher o nível de concorrência adequado ao rate limit da API e monitorar métricas de performance para ajustar os parâmetros.

Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a processar lotes de CPF com alta performance usando goroutines hoje mesmo.

CPFHub.io

Pronto para integrar a API?

50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

Redação CPFHub.io

Sobre a redação

Redação CPFHub.io

Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.

WhatsAppFale conosco via WhatsApp