Autor: George Silva
Uma migração de SQL Server para Postgres sem downtime
Ele não estava falhando, mas cada viagem estava custando mais do que deveria.
Aqui na Delfinance, a operação vinha crescendo mês a mês, com volume significativo de transações Pix. Com esse crescimento, a parte mais cara de escalar começou a aparecer com força: o banco de dados. A maior parte do nosso core bancário ainda estava em SQL Server e, além de storage e tráfego de rede, existia um componente inevitável: licenciamento.
Foi aí que tomamos a decisão: migrar o storage da principal tabela do Pix de SQL Server para PostgreSQL. Só havia um detalhe… o trem estava mais rápido do que nunca! A missão era clara: migrar sem indisponibilidade do serviço (zero downtime), com impacto controlado e monitorado nos fluxos assíncronos.
O plano
A tabela no SQL Server era grande e sem particionamento. Não havia um problema grave visível para os usuários, mas a migração era uma oportunidade de corrigir pontos estruturais e preparar a base para crescer melhor e de forma sustentável.
As principais decisões foram:
- Recriar a tabela no Postgres com particionamento, melhorando organização e operabilidade.
- Usar dual-write por um período para reduzir risco e garantir fluxos longos (como devoluções).
- Implementar fallback reads: ler do Postgres primeiro e, em caso de erro ou ausência, consultar o SQL Server como fonte de fallback.
Até aqui, tudo caminhava bem.
A falha: 3 “parafusos” soltos
Após semanas de implementação e testes, chegamos no tão temido deploy. Após a implantação, o serviço permaneceu estável por um longo tempo, até que, por meio de métricas e alertas operacionais, identificamos uma degradação no fluxo de devolução Pix. Justamente no fluxo que tínhamos tomado tanto cuidado!
Apesar das estratégias de dual-write e fallback read, houve uma lacuna no nosso checklist de performance: três índices necessários para o padrão de consulta do fluxo de devolução. O resultado foi imediato: consultas do fluxo de devolução, que precisam recuperar a transação original e verificar devoluções anteriores para calcular o valor total, começaram a falhar enquanto a partição crescia.
Apesar disso, o impacto para os clientes foi mínimo. O fluxo assíncrono de devoluções somado à implementação de retries automáticos, permitiu que a maior parte das transações fosse reprocessada com sucesso, sem perda de dados ou inconsistências financeiras.
Além disso, o volume de devoluções é bem menor que o de cash-outs tradicionais, o que limitou o impacto. Nos poucos casos afetados, houve apenas atraso na conclusão da devolução, sem necessidade de intervenção manual em larga escala.
O primeiro pensamento foi o clássico: “apaga e recria”. A gente ainda tinha dual-write e fallback a favor, mas isso significaria perder semanas de dados já acumulados no Postgres.
Precisávamos corrigir em produção, com o trem em movimento.
A correção: índices concorrentes + estratégia por partições
A solução foi usar a criação concorrente de índices (CREATE INDEX CONCURRENTLY), disponibilizada pelo Postgres, para reduzir bloqueios durante a criação de índices, e combinar isso com uma estratégia por partições para minimizar impacto.
A ideia era criar índices com impacto mínimo em leitura e escrita, mas havia limitações e riscos:
- Não era viável replicar integralmente a cardinalidade e concorrência de produção em homologação; mitigamos o risco com testes de carga, validação dos planos de execução e rollout controlado com monitoramento reforçado.
- Como a tabela era particionada, tínhamos uma limitação de não conseguir criar índices na tabela como um todo, parte do trabalho teria que ser feito partição a partição, pelo menos no começo.
Para reduzir risco e impacto aos clientes, dividimos o problema em etapas:
- Criar os índices nas partições antigas: isso geraria menos impacto, pois estas partições serviam apenas como base de consulta de registros antigos.
- Criar os índices nas partições futuras: como não haveria leitura ou escrita nestas partições, o impacto seria zero.
- E por último, atacar a partição quente: como estávamos no final do mês, ao invés de correr o risco de criar um tipo de lock na tabela. Esperamos ela se tornar uma partição ‘fria’, de somente leitura, como as mencionadas na 1ª etapa.
Por fim, resolvemos o trabalho, a princípio manual, de criar os índices sempre que uma nova partição fosse criada. Para isso, padronizamos a definição dos índices na tabela particionada e garantimos cobertura nas partições existentes, além de automatizar a aplicação em novas partições para evitar reincidência.
Conclusão
A lição foi simples: o Pix não pode parar, mesmo quando as “engrenagens” precisam ser trocadas no background. A migração não aconteceu porque estava “quebrado”, mas porque escala e custo fazem parte da engenharia tanto quanto latência e disponibilidade.
Ao usar dual-write e fallback reads, conseguimos trocar a fundação sem interromper o fluxo, e ainda aproveitamos a oportunidade para evoluir a estrutura com particionamento.
Esse é o tipo de mudança que o cliente não vê, mas sente seu impacto positivo. E é exatamente assim que trabalhamos na Delfinance. Temos o objetivo de evoluir junto com quem integra conosco, sem prejudicar a compatibilidade e sem comprometer a operação dos nossos clientes.
Seu produto depende de Pix e não pode parar? Venha conhecer nossa API do Pix. Temos um time de excelência, com suporte personalizado e monitoramento reativo e proativo, 24/7, que trabalha sempre priorizando compatibilidade e evolução contínua.
Tem interesse em saber como construímos aplicações resilientes e tolerante a falhas na Delfinance? Leia Idempotência: Reprocessar não deveria ser perigoso.






