<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rodolfo Henrique</title>
    <description>The latest articles on DEV Community by Rodolfo Henrique (@rodolfocoding).</description>
    <link>https://dev.to/rodolfocoding</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3137981%2F1df1eb0f-b5c2-4b76-a654-26c51dfc6e4f.jpg</url>
      <title>DEV Community: Rodolfo Henrique</title>
      <link>https://dev.to/rodolfocoding</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rodolfocoding"/>
    <language>en</language>
    <item>
      <title>O mesmo pedido chegou quatro vezes. O SQS não estava errado</title>
      <dc:creator>Rodolfo Henrique</dc:creator>
      <pubDate>Sun, 20 Sep 2026 21:09:28 +0000</pubDate>
      <link>https://dev.to/rodolfocoding/o-mesmo-pedido-chegou-quatro-vezes-o-sqs-nao-estava-errado-211m</link>
      <guid>https://dev.to/rodolfocoding/o-mesmo-pedido-chegou-quatro-vezes-o-sqs-nao-estava-errado-211m</guid>
      <description>&lt;p&gt;Este artigo é a continuação do lab de &lt;a href="https://medium.com/@rodolfo.coding/o-visibility-timeout-do-sqs-que-n%C3%A3o-processou-o-pedido-duas-vezes-2598624cadd8" rel="noopener noreferrer"&gt;visibility timeout no SQS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No primeiro lab eu tentei fabricar uma duplicata atrasando o relógio da fila. Não funcionou. A Lambda estende o visibility timeout enquanto processa. O alvo era o relógio. O alvo errado.&lt;/p&gt;

&lt;p&gt;O que ficou em aberto era outra coisa: o &lt;strong&gt;produtor&lt;/strong&gt;. E se eu mesmo mandar o mesmo pedido duas, quatro, dez vezes?&lt;/p&gt;

&lt;p&gt;Foi isso que eu fiz. Abaixo está o que subi, por que subi, e o que cada print mostra.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que eu queria descobrir
&lt;/h2&gt;

&lt;p&gt;O Amazon SQS (Simple Queue Service) é uma fila. Você coloca uma mensagem. Um consumidor tira a mensagem e trabalha.&lt;/p&gt;

&lt;p&gt;Fila &lt;strong&gt;standard&lt;/strong&gt; entrega &lt;em&gt;pelo menos uma vez&lt;/em&gt;. A documentação da Lambda diz a mesma coisa do outro lado: o event source mapping (a ligação fila → função) também processa pelo menos uma vez. O código precisa ser &lt;strong&gt;idempotente&lt;/strong&gt;: processar o mesmo pedido uma vez ou dez vezes deixa o mesmo resultado.&lt;/p&gt;

&lt;p&gt;Isso cobre a &lt;em&gt;mensagem&lt;/em&gt;. Não cobre o &lt;em&gt;pedido&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Imagine o checkout. O usuário clica em pagar. A rede falha. Ele clica de novo. O backend manda duas mensagens. O SQS aceita as duas. Cada uma ganha um &lt;code&gt;MessageId&lt;/code&gt; novo. Para a fila, são dois recados. Para o caixa, é o mesmo &lt;code&gt;ORDER-003&lt;/code&gt; de 150 reais.&lt;/p&gt;

&lt;p&gt;Pergunta do lab:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Se várias mensagens representam o mesmo pedido, o worker cobra uma vez ou cobra cada mensagem?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Dois identificadores. Não misture.
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Identificador&lt;/th&gt;
&lt;th&gt;Quem cria&lt;/th&gt;
&lt;th&gt;O que significa&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MessageId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O SQS, a cada &lt;code&gt;SendMessage&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Aquela cópia na fila&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;orderId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Você, no JSON do body&lt;/td&gt;
&lt;td&gt;O pedido de negócio&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;O &lt;code&gt;MessageId&lt;/code&gt; nomeia a mensagem. O &lt;code&gt;orderId&lt;/code&gt; nomeia o pedido. Idempotência neste lab trava o &lt;strong&gt;pedido&lt;/strong&gt;, não a mensagem.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que eu subi na AWS
&lt;/h2&gt;

&lt;p&gt;Tudo em &lt;code&gt;us-east-1&lt;/code&gt;. Sem fila FIFO. Sem gateway de pagamento. A "cobrança" é um log chamado &lt;code&gt;charge_applied&lt;/code&gt;. Se esse log aparece, o lab considera que o caixa rodou.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PowerShell (send.ps1)
        |
        v
SQS standard: aws-lab-sqs-idempotency
        |
        |  uma mensagem por invocação (batch size 1)
        v
Lambda: aws-lab-sqs-idempotency-worker
        |
        +-- CloudWatch Logs  (o que aconteceu)
        +-- DynamoDB         (a trava do orderId)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Peça&lt;/th&gt;
&lt;th&gt;Nome&lt;/th&gt;
&lt;th&gt;Para que serve&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fila&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aws-lab-sqs-idempotency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recebe as cópias do pedido&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Função&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aws-lab-sqs-idempotency-worker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lê a fila e "cobra"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tabela&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aws-lab-sqs-idempotency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Guarda o &lt;code&gt;orderId&lt;/code&gt; com um write condicional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/aws/lambda/aws-lab-sqs-idempotency-worker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Evidência no Insights&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A função tem um interruptor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;IDEMPOTENCY=0&lt;/code&gt;: chegou mensagem, cobra. Sem perguntar nada.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IDEMPOTENCY=1&lt;/code&gt;: antes de cobrar, tenta gravar o &lt;code&gt;orderId&lt;/code&gt; no DynamoDB. Se o item já existe, não cobra.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por que batch size 1: cada mensagem vira uma invocação. Assim duas cópias podem correr ao mesmo tempo, em vez de cair no mesmo lote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que a guarda não é um &lt;code&gt;if&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Este código parece idempotência e não é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Duas Lambdas leem ao mesmo tempo. As duas veem "não existe". As duas cobram. As duas gravam depois. A verificação e a reserva precisam ser &lt;strong&gt;a mesma operação&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No DynamoDB isso é um &lt;code&gt;PutItem&lt;/code&gt; com condição: "grave este &lt;code&gt;orderId&lt;/code&gt; só se ele ainda não existir".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ddb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PutItemCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;S&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;S&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLAIMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;S&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messageId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;S&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;claimedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;S&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;ConditionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;attribute_not_exists(orderId)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leitura do código, em português:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tente criar o item com PK &lt;code&gt;orderId&lt;/code&gt; e status &lt;code&gt;CLAIMED&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Se ninguém gravou essa PK ainda, você ganhou (&lt;code&gt;claim_won&lt;/code&gt;). Aí você cobra e marca &lt;code&gt;COMPLETED&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Se a PK já existe, o DynamoDB recusa com &lt;code&gt;ConditionalCheckFailedException&lt;/code&gt;. Você perdeu (&lt;code&gt;claim_lost&lt;/code&gt;), loga &lt;code&gt;skip_duplicate&lt;/code&gt; e &lt;strong&gt;não cobra&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;CLAIMED&lt;/code&gt; é reserva, não recibo. Eu não marco &lt;code&gt;COMPLETED&lt;/code&gt; antes de cobrar. Se eu marcasse "já processado" e a cobrança falhasse, o retry pularia um pedido que nunca saiu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experimento 1: interruptor desligado
&lt;/h2&gt;

&lt;p&gt;O que eu fiz: subi o lab com &lt;code&gt;IDEMPOTENCY=0&lt;/code&gt; e mandei mensagens do mesmo pedido.&lt;/p&gt;

&lt;p&gt;Por que: para ver o comportamento sem trava. Se o worker cobra cada mensagem, o Insights tem que mostrar &lt;strong&gt;mais cobranças do que pedidos&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O que eu vi no Logs Insights (Analytics de logs, visão &lt;strong&gt;Tabelas de pesquisa&lt;/strong&gt;), numa janela em que só existem três tipos de evento:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;eventType&lt;/th&gt;
&lt;th&gt;O que significa&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;process_start&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A Lambda começou a tratar a mensagem&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;charge_applied&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O caixa rodou&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;process_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A invocação terminou&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Não tem &lt;code&gt;claim_won&lt;/code&gt; nem &lt;code&gt;skip_duplicate&lt;/code&gt;. A guarda estava desligada.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjhiufa52pz5wjogr96dd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjhiufa52pz5wjogr96dd.png" alt="Quatro process_start, quatro charge_applied, quatro process_end. Sem eventos de claim." width="799" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Olhe a tabela de baixo: três linhas, todas com 4. Quatro invocações, quatro cobranças.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Depois eu contei só o efeito. A consulta pega &lt;code&gt;charge_applied&lt;/code&gt; e pergunta: quantas cobranças, quantos &lt;code&gt;orderId&lt;/code&gt; distintos, quantos &lt;code&gt;MessageId&lt;/code&gt; distintos?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fields @timestamp, @message
| filter @message like /"type":"charge_applied"/
| parse @message /"orderId":"(?&amp;lt;orderId&amp;gt;[^"]+)"/
| parse @message /"messageId":"(?&amp;lt;messageId&amp;gt;[^"]+)"/
| stats count(*) as charges, count_distinct(orderId) as pedidos, count_distinct(messageId) as mensagens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;charges&lt;/th&gt;
&lt;th&gt;pedidos&lt;/th&gt;
&lt;th&gt;mensagens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5cnvcc8kkyeo51qylxz5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5cnvcc8kkyeo51qylxz5.png" alt="4 charges, 1 pedido, 4 mensagens" width="800" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Quatro MessageId. Um orderId. Quatro cobranças. A fila tratou quatro recados. O caixa tratou o mesmo pedido quatro vezes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;O mesmo número aparece em outro recorte da mesma consulta.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpusbrit3yd4f9rvp2zmr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpusbrit3yd4f9rvp2zmr.png" alt="Outro recorte da consulta de cobranças: de novo 4, 1 e 4" width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para o SQS, o teste passou: quatro mensagens, quatro invocações, zero erro. Para o negócio, o cliente foi cobrado quatro vezes.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que idempotência quis dizer aqui
&lt;/h2&gt;

&lt;p&gt;Caminho que eu usei:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;orderId&lt;/code&gt; (o pedido) → mensagem (&lt;code&gt;MessageId&lt;/code&gt;) → Lambda → efeito (&lt;code&gt;charge_applied&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Idempotente: aplicar &lt;code&gt;ORDER-003&lt;/code&gt; uma vez ou dez vezes deixa &lt;strong&gt;uma&lt;/strong&gt; cobrança de 150.&lt;/p&gt;

&lt;p&gt;Não é "o SQS joga a segunda mensagem fora". O SQS pode (e neste lab, deve) entregar as dez. O destino é que recusa o segundo efeito.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experimento 2: interruptor ligado, dez cópias
&lt;/h2&gt;

&lt;p&gt;O que eu fiz:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;destroy&lt;/code&gt; do lab antigo e &lt;code&gt;deploy&lt;/code&gt; de novo.&lt;/li&gt;
&lt;li&gt;Esperei a Lambda ficar ativa e o event source mapping em &lt;code&gt;Enabled&lt;/code&gt;, batch size 1.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.\set-idempotency.ps1 -Enabled 1&lt;/code&gt;. A variável da função ficou &lt;code&gt;IDEMPOTENCY=1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Mandei o mesmo pedido dez vezes:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\send.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-OrderId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ORDER-003&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Copies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O body impresso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ORDER-003"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"CUSTOMER-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;150.00&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dez linhas. Dez &lt;code&gt;MessageId&lt;/code&gt; diferentes. &lt;code&gt;orderId=ORDER-003&lt;/code&gt; em todas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbc0c3jxu1nk3yx1nzdt4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbc0c3jxu1nk3yx1nzdt4.png" alt="Terminal: dez SendMessage de ORDER-003, cada um com MessageId novo" width="799" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;O que observar: a fila não juntou nada. Dez recados. Um pedido.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Por que dez, e não duas: para forçar várias invocações no mesmo &lt;code&gt;orderId&lt;/code&gt; com a guarda ligada.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que o Insights mostrou nesse pedido
&lt;/h2&gt;

&lt;p&gt;Filtrei &lt;code&gt;orderId = "ORDER-003"&lt;/code&gt;. Vieram 20 correspondências, nestes tipos:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;process_start&lt;/code&gt;, &lt;code&gt;claim_lost&lt;/code&gt;, &lt;code&gt;skip_duplicate&lt;/code&gt;, &lt;code&gt;process_end&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffe4krhj5e0pn0l0nvk18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffe4krhj5e0pn0l0nvk18.png" alt="Contagem só de ORDER-003: start, claim_lost, skip_duplicate, end. Vinte eventos." width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Leia assim: a mensagem chegou, tentou gravar a PK, perdeu, logou skip e encerrou. Sem nova cobrança nesse caminho.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A linha a linha é o mesmo filtro, evento por evento. Só &lt;code&gt;ORDER-003&lt;/code&gt;. &lt;code&gt;claim_lost&lt;/code&gt; e &lt;code&gt;skip_duplicate&lt;/code&gt; andam juntos. Os &lt;code&gt;MessageId&lt;/code&gt; mudam. O pedido não.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsvz40xyi7t9rt9qqu3m2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsvz40xyi7t9rt9qqu3m2.png" alt="Timeline ORDER-003: várias mensagens, o mesmo orderId, claim_lost e skip_duplicate" width="799" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  O que a tabela mostrou nesse pedido
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;get-item&lt;/code&gt; de &lt;code&gt;ORDER-003&lt;/code&gt; devolveu &lt;strong&gt;um&lt;/strong&gt; item:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Campo&lt;/th&gt;
&lt;th&gt;Valor&lt;/th&gt;
&lt;th&gt;Por que importa&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;orderId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ORDER-003&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A chave do pedido&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;COMPLETED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A reserva virou recibo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;messageId&lt;/code&gt; / &lt;code&gt;completedMessageId&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;73fca7ac-78e9-4ea4-a1bf-71a92e2fbda8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;É o envio 1/10 daquela corrida&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claimedAt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-09-20T20:58:23.240Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quando a primeira cópia ganhou o PutItem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;completedAt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-09-20T20:58:28.142Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cerca de 5 s depois. O worker espera 4 s (&lt;code&gt;WORK_MS=4000&lt;/code&gt;) e então marca completed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scan&lt;/td&gt;
&lt;td&gt;1 item&lt;/td&gt;
&lt;td&gt;Não nasceu um item por mensagem&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A fila aceitou dez &lt;code&gt;MessageId&lt;/code&gt;. O destino ficou com um &lt;code&gt;ORDER-003&lt;/code&gt; em &lt;code&gt;COMPLETED&lt;/code&gt;. As cópias no Insights perderam o claim e não cobraram.&lt;/p&gt;

&lt;h2&gt;
  
  
  Os prints do grupo inteiro (com a guarda já visível)
&lt;/h2&gt;

&lt;p&gt;No log group sem filtrar &lt;code&gt;orderId&lt;/code&gt;, numa janela em que os eventos de claim já existem:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;eventType&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;process_end&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;charge_applied&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;skip_duplicate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claim_lost&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;process_start&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claim_won&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1a6r0ahcjn607ssa68ea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1a6r0ahcjn607ssa68ea.png" alt="Grupo inteiro: um claim_won, dois claim_lost, um skip_duplicate, cinco charge_applied" width="799" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Isso mostra a guarda em ação no grupo: alguém ganhou o claim, alguém perdeu, alguém pulou a cobrança.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A consulta de cobranças no grupo inteiro, recorte mais largo: 7 charges, 3 pedidos, 7 mensagens. São vários &lt;code&gt;orderId&lt;/code&gt; no mesmo log group.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpr8wscebem4ma2syu5yk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpr8wscebem4ma2syu5yk.png" alt="Agregado do grupo: 7 charges, 3 pedidos, 7 mensagens" width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lado a lado
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Sem guarda&lt;/th&gt;
&lt;th&gt;Com guarda, pedido &lt;code&gt;ORDER-003&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;O que eu mandei&lt;/td&gt;
&lt;td&gt;várias mensagens do mesmo pedido&lt;/td&gt;
&lt;td&gt;10 &lt;code&gt;SendMessage&lt;/code&gt; com o mesmo &lt;code&gt;ORDER-003&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O que a fila fez&lt;/td&gt;
&lt;td&gt;aceitou cada &lt;code&gt;MessageId&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;aceitou cada &lt;code&gt;MessageId&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O que o Insights mostrou&lt;/td&gt;
&lt;td&gt;4 start, 4 charge, 4 end&lt;/td&gt;
&lt;td&gt;20 eventos de start / &lt;code&gt;claim_lost&lt;/code&gt; / &lt;code&gt;skip_duplicate&lt;/code&gt; / end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O que a tabela mostrou&lt;/td&gt;
&lt;td&gt;a guarda estava desligada&lt;/td&gt;
&lt;td&gt;um item &lt;code&gt;ORDER-003&lt;/code&gt; / &lt;code&gt;COMPLETED&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Efeito no caixa&lt;/td&gt;
&lt;td&gt;4 cobranças para 1 &lt;code&gt;orderId&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;um pedido reservado e concluído; as cópias visíveis não cobraram&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A fila fez o trabalho dela nas duas vezes. A diferença está no destino.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que este lab não promete
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;SQS standard &lt;strong&gt;não&lt;/strong&gt; vira exactly-once porque você usou Lambda. Continua at-least-once.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;if (exists) return&lt;/code&gt; &lt;strong&gt;não&lt;/strong&gt; é a guarda. Duas leituras ao mesmo tempo furam isso.&lt;/li&gt;
&lt;li&gt;FIFO com &lt;code&gt;MessageDeduplicationId&lt;/code&gt; ajuda a não &lt;em&gt;enviar&lt;/em&gt; a mesma mensagem de novo por alguns minutos. Não substitui a tabela no destino quando &lt;strong&gt;você quer&lt;/strong&gt; enfileirar o mesmo pedido várias vezes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  O que eu levo
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;MessageId&lt;/code&gt; é a mensagem. &lt;code&gt;orderId&lt;/code&gt; é o pedido. A trava vai no pedido.&lt;/li&gt;
&lt;li&gt;Sem guarda, quatro mensagens do mesmo &lt;code&gt;orderId&lt;/code&gt; foram quatro &lt;code&gt;charge_applied&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A reserva é o &lt;code&gt;PutItem&lt;/code&gt; com &lt;code&gt;attribute_not_exists(orderId)&lt;/code&gt;. Quem perde loga &lt;code&gt;claim_lost&lt;/code&gt; e &lt;code&gt;skip_duplicate&lt;/code&gt; e não cobra.&lt;/li&gt;
&lt;li&gt;Idempotência neste desenho é: a fila pode entregar dez vezes; o caixa do pedido roda uma.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>aws</category>
      <category>sqs</category>
      <category>dynamodb</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
