Shopify troca Redis por MySQL em reservas de estoque e escala no Black Friday
Time de engenharia da Shopify reconstruiu o sistema de reserva de estoque sobre MySQL usando SKIP LOCKED e descobriu que o gargalo real estava nas conexões, não no banco.
A Shopify publicou como substituiu o Redis por MySQL no sistema de reservas de estoque durante o checkout, o mecanismo que impede que dois compradores levem a última unidade de um produto ao mesmo tempo. O sistema aguentou o pico do Black Friday de 2025, quando, segundo a empresa, os lojistas da plataforma atingiram US$ 5,1 milhões em vendas por minuto, alta de 11% sobre o ano anterior.
O problema com o modelo antigo
A proteção contra overselling tem duas operações: reservar (segura o item por alguns minutos quando o pagamento começa) e efetivar (deduz a quantidade do ledger de estoque quando o pagamento é aprovado). No desenho anterior, as reservas viviam no Redis (uma chave de quantidade por item, com DECR e INCR), enquanto o ledger ficava no MySQL.
Como os dois sistemas eram separados, a etapa de efetivação não podia ser envolvida em uma única transação atômica. Dependendo da ordem, isso abria espaço para overselling (item vendido mas nunca deduzido) ou underselling (item deduzido e ainda marcado como reservado). O Redis também não tinha consciência de múltiplas localizações de estoque e ainda cobrava o custo operacional de manter um cluster à parte.
A virada: SKIP LOCKED e uma linha por unidade
Tentativas anteriores de usar MySQL falharam porque uma única linha com coluna de quantidade não suportava a contenção. A solução veio com o SKIP LOCKED do MySQL 8: em vez de uma linha por item, o novo desenho usa uma linha por unidade vendável. Um item com 10 unidades vira 10 linhas, e reservar três significa mover três linhas numa transação. Se outra transação travou algumas linhas, o SKIP LOCKED as pula e devolve outras disponíveis, reduzindo a espera.
Manter tudo no mesmo banco do ledger garantiu ACID entre reservar e efetivar. Para não escalar mal (um item com 50 mil unidades em 10 locais viraria 500 mil linhas), a Shopify mantém um pool limitado a 1.000 linhas por combinação item/localização, reabastecido a partir do ledger. Se o pool esvazia em uma flash sale, o reabastecimento roda inline com um lock que evita o efeito manada.
Detalhes que valem a leitura
O post técnico traz decisões finas que interessam a quem opera banco relacional sob carga:
- Chave primária composta: o protótipo inicial com
idauto-incremento gerava dois locks por reserva (índice secundário + clustered). Trocar para uma chave composta (shop_id, inventory_item_id, inventory_group_id, id) reduziu para um lock por linha. - READ COMMITTED: o isolamento padrão
REPEATABLE READprovocava gap locks que travavam o reabastecimento. Mudar paraREAD COMMITTEDnessas transações liberou os inserts. - Ordem consistente de locks: padronizar a sequência de operações entre reserva e efetivação eliminou deadlocks.
- Batch com
UNION ALL: carrinhos com várias linhas buscam todas as unidades em uma única ida ao banco.
O gargalo estava na tubulação
A lição principal, segundo a equipe, não foi de design de banco. Em produção, o sistema batia num teto de throughput bem abaixo da meta, mesmo com latência aceitável, CPU sobrando e queries otimizadas. A causa: exaustão de conexões.
A solução foi instrumentar quem segurava as conexões e por quanto tempo. No lado da aplicação, cada statement SQL passou a levar um comentário de tag identificando o processo de negócio (/ conn_tag:checkout_completion /); na camada ProxySQL, um rastreamento parseava a tag e media o tempo de retenção por chamador. Ficou claro que outras partes do checkout, nunca otimizadas por nunca terem sido as primeiras a estourar o limite, seguravam conexões por tempo demais. A limpeza removeu 50% das leituras e 33% das transações no banco primário. Ajustar o innodb_thread_concurrency, configurado de forma conservadora anos antes, derrubou o resto do teto.
A migração foi feita em shadow mode: toda reserva era escrita no Redis (fonte da verdade) e no MySQL em paralelo, permitindo comparar resultados com tráfego real antes de virar a chave, pod por pod, com kill switch para reverter.
O que isso muda para quem escala no Brasil
A mensagem que a Shopify deixa é direta: cargas que antes pareciam exigir infraestrutura especializada (Redis, Kafka ou uma camada de coordenação custom para exclusão mútua de alto throughput) podem caber no banco relacional que você já tem. Para times brasileiros que enfrentam picos previsíveis, como Black Friday e lançamentos, dois recados práticos: revisite decisões antigas quando features novas (como SKIP LOCKED) e hardware evoluíram, e quando os números não fecham (CPU baixa mas fila alta), instrumente o caminho inteiro, porque a resposta costuma estar na tubulação, não no motor.
Fonte: Hacker News
Este artigo foi escrito por Redação iMasters, um agente de inteligência artificial com revisão editorial humana.



