---
title: "SQS em Go: Filas, Workers e Retry na AWS"
url: "https://golang.com.br/blog/sqs-go-filas-workers-aws/"
markdown_url: "https://golang.com.br/blog/sqs-go-filas-workers-aws.MD"
description: "Aprenda SQS em Go com AWS SDK v2: producer, consumer, long polling, visibility timeout, DLQ, idempotência, testes locais e padrões de produção."
date: "2026-06-04"
author: "Golang Brasil"
---

# SQS em Go: Filas, Workers e Retry na AWS

Aprenda SQS em Go com AWS SDK v2: producer, consumer, long polling, visibility timeout, DLQ, idempotência, testes locais e padrões de produção.


Amazon SQS é uma das formas mais pragmáticas de colocar trabalho assíncrono em uma aplicação Go que já roda na AWS. Em vez de operar RabbitMQ, Kafka ou NATS logo no primeiro mês, o time publica mensagens em uma fila gerenciada e deixa workers Go processarem no ritmo certo. Para produtos brasileiros com picos de tráfego, integrações financeiras, webhooks, envio de e-mail, relatórios e sincronização de dados, essa simplicidade costuma valer muito.

O ponto importante é não confundir simplicidade operacional com simplicidade de arquitetura. SQS entrega mensagens pelo menos uma vez, pode redeliver duplicatas, depende de `visibility timeout` bem configurado e exige idempotência no consumer. Se você ignorar esses detalhes, a fila vai funcionar em desenvolvimento e falhar justamente quando a produção tiver retry, deploy, timeout ou instabilidade em um fornecedor externo.

Este guia mostra como usar SQS em Go com AWS SDK v2: producer, consumer, long polling, delete depois do sucesso, retry, dead-letter queue, idempotência, observabilidade e testes locais. Se você ainda está comparando opções, leia também [mensageria em Go: RabbitMQ, Kafka, NATS ou SQS](/blog/mensageria-go-rabbitmq-kafka-nats-sqs/) e [idempotência, retry e DLQ em Go](/blog/idempotencia-retry-dlq-go/).

## Quando SQS faz sentido

SQS funciona melhor quando você precisa de uma fila de trabalho, não de um log de eventos com replay longo. Bons casos de uso:

- Processar webhooks fora da requisição HTTP.
- Enviar e-mails, notificações ou mensagens transacionais.
- Gerar relatórios e exports sob demanda.
- Sincronizar dados com CRM, antifraude, billing ou ERPs.
- Suavizar picos entre uma API Go e workers independentes.
- Rodar tarefas de baixa ou média latência sem operar broker.

Se vários times precisam consumir o mesmo evento com retenção longa, Kafka ou Kinesis podem ser melhores. Se você precisa de roteamento complexo, RabbitMQ pode ser mais expressivo. Se o objetivo é uma fila gerenciada simples para workers na AWS, SQS costuma ser a primeira escolha.

## Standard ou FIFO?

SQS tem dois tipos principais de fila. A fila Standard escala muito, tem melhor throughput e entrega mensagens pelo menos uma vez, mas não garante ordem estrita. A fila FIFO preserva ordem dentro de um `MessageGroupId` e oferece deduplicação por janela, mas tem limitações de throughput e exige mais cuidado na modelagem.

Para a maioria dos workers de e-mail, relatório, importação e integração, comece com Standard e escreva o handler como idempotente. Escolha FIFO quando a ordem por entidade é parte do requisito de negócio: por exemplo, processar eventos de status de um mesmo pedido na sequência correta.

## Producer com AWS SDK v2

O producer deve publicar uma mensagem pequena, versionada e rastreável. Evite payload gigante. Se o dado é grande, salve em S3 ou no banco e publique apenas a referência.

```go
package fila

import (
    "context"
    "encoding/json"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/service/sqs"
    "github.com/aws/aws-sdk-go-v2/service/sqs/types"
)

type PedidoCriado struct {
    EventID  string `json:"event_id"`
    PedidoID string `json:"pedido_id"`
    Versao   int    `json:"versao"`
}

type Producer struct {
    client   *sqs.Client
    queueURL string
}

func NewProducer(client *sqs.Client, queueURL string) *Producer {
    return &Producer{client: client, queueURL: queueURL}
}

func (p *Producer) PublicarPedidoCriado(ctx context.Context, evento PedidoCriado) error {
    body, err := json.Marshal(evento)
    if err != nil {
        return err
    }

    _, err = p.client.SendMessage(ctx, &sqs.SendMessageInput{
        QueueUrl:    aws.String(p.queueURL),
        MessageBody: aws.String(string(body)),
        MessageAttributes: map[string]types.MessageAttributeValue{
            "event_type": {
                DataType:    aws.String("String"),
                StringValue: aws.String("pedido.criado"),
            },
        },
    })
    return err
}
```

O exemplo usa `EventID` porque o consumer precisa de uma chave estável para idempotência. Não use `time.Now()` como única identidade do evento: em retry, você quer reconhecer que a mesma intenção já foi processada.

## Consumer com long polling

Um worker Go típico fica em loop lendo mensagens com long polling. Long polling reduz chamadas vazias e custo, porque o SQS segura a requisição por alguns segundos até uma mensagem chegar.

```go
func RunConsumer(ctx context.Context, client *sqs.Client, queueURL string) error {
    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
        }

        resp, err := client.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{
            QueueUrl:            aws.String(queueURL),
            MaxNumberOfMessages: 5,
            WaitTimeSeconds:     20,
            VisibilityTimeout:   60,
        })
        if err != nil {
            return err
        }

        for _, msg := range resp.Messages {
            if err := processarMensagem(ctx, msg); err != nil {
                // Sem DeleteMessage: o SQS libera a mensagem de novo depois do visibility timeout.
                continue
            }

            _, err := client.DeleteMessage(ctx, &sqs.DeleteMessageInput{
                QueueUrl:      aws.String(queueURL),
                ReceiptHandle: msg.ReceiptHandle,
            })
            if err != nil {
                return err
            }
        }
    }
}
```

A regra é simples: só chame `DeleteMessage` depois que o trabalho terminou com sucesso. Se o worker cair, tomar deploy ou perder rede antes do delete, a mensagem volta. Isso é uma vantagem quando o handler é idempotente; é um desastre quando o handler duplica cobrança, e-mail ou alteração de estado.

## Visibility timeout e retry

`VisibilityTimeout` é o tempo em que uma mensagem fica invisível para outros consumers depois de ser recebida. Se o processamento demora 40 segundos e o timeout é 30, outro worker pode pegar a mesma mensagem antes do primeiro terminar. Configure o timeout acima do tempo normal de processamento e monitore casos lentos.

Para tarefas longas, existem duas opções. A primeira é quebrar o trabalho em etapas menores. A segunda é renovar a visibilidade com `ChangeMessageVisibility` enquanto o job avança. Prefira quebrar o trabalho quando possível, porque timeouts longos demais atrasam retry quando o worker morre.

SQS já faz retry por redelivery. Quando a mensagem falha muitas vezes, a configuração correta é mandar para uma dead-letter queue. A DLQ evita loop infinito e cria uma superfície clara para investigação: por que esse payload não processa? Falta dado? O fornecedor externo mudou contrato? O handler tem bug?

## Idempotência no consumer

Como SQS pode entregar duplicatas, o handler deve aceitar a mesma mensagem mais de uma vez. Um padrão comum é gravar o `EventID` em uma tabela de eventos processados dentro da mesma transação do efeito de negócio.

```go
func processarPedidoCriado(ctx context.Context, db *sql.DB, evento PedidoCriado) error {
    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    defer tx.Rollback()

    _, err = tx.ExecContext(ctx, `
        INSERT INTO eventos_processados (id, tipo, processado_em)
        VALUES ($1, $2, now())
        ON CONFLICT (id) DO NOTHING
    `, evento.EventID, "pedido.criado")
    if err != nil {
        return err
    }

    // Aplique o efeito de negócio aqui: atualizar status, criar tarefa, enviar comando.

    return tx.Commit()
}
```

Em sistemas financeiros ou de assinatura, a idempotência precisa ficar ainda mais explícita. A chave pode ser `payment_id`, `invoice_id`, `external_event_id` ou outra identidade de negócio que sobreviva a retry, deploy e reprocessamento.

## Observabilidade mínima

Um worker SQS sem métricas vira caixa-preta. Monitore pelo menos:

- Quantidade de mensagens visíveis na fila.
- Idade da mensagem mais antiga.
- Taxa de sucesso e falha por tipo de evento.
- Tempo de processamento por handler.
- Quantidade de mensagens na DLQ.
- Número de redeliveries ou tentativas quando disponível.

Nos logs, registre `event_id`, `message_id`, `event_type`, `attempt`, `duration_ms` e resultado. Para tracing, propague um `trace_id` no payload ou em atributos da mensagem quando o produtor vem de uma requisição HTTP.

## Testes locais

Para testes de unidade, não chame AWS. Envolva o cliente em uma interface pequena ou teste o handler separado do transporte. O código mais importante é a função que transforma mensagem em efeito de negócio, não o loop infinito do consumer.

Para integração local, LocalStack ajuda a validar `SendMessage`, `ReceiveMessage`, `DeleteMessage`, DLQ e atributos. Mesmo assim, rode pelo menos um teste em ambiente AWS real antes de confiar em timeout, IAM, políticas de fila e redrive policy.

## Checklist de produção

Antes de colocar SQS em produção com Go, revise:

1. O payload tem versão e identidade estável.
2. O consumer é idempotente.
3. `VisibilityTimeout` cobre o tempo normal de processamento.
4. A fila tem DLQ configurada com limite de tentativas.
5. Logs e métricas mostram sucesso, falha, latência e backlog.
6. O worker respeita `context.Context` e encerra bem no deploy.
7. IAM permite apenas as ações necessárias na fila correta.
8. Mensagens grandes usam S3 ou banco como armazenamento auxiliar.

SQS em Go é uma boa escolha quando o time quer focar no produto e não na operação de broker. A parte difícil não é chamar `SendMessage`; é desenhar consumers que toleram duplicidade, falha, retry e deploy. Se você trata idempotência, DLQ e observabilidade como requisitos desde o começo, SQS vira uma peça simples e robusta para backend, plataforma e integrações na AWS.
