Não troque seu Postgres por uma reescrita em Rust (ainda): o mapa de riscos de um CTO

Por Diogo Hudson Dias
Database engineer reviewing Postgres and Rust code on dual monitors in a low-light operations room with server racks at night.

O Hacker News adora uma história de reescrita. A faísca desta semana: um banco de dados compatível com Postgres, reescrito em Rust, afirmando passar em 100% dos testes de regressão do Postgres. Impressionante. Mas, se você é o CTO responsável por RPO/RTO, sabe que 100% da suíte de testes não é 100% de produção. Seu trabalho não é torcer pela segurança de memória — é manter a receita fluindo e os dados sem surpresas. Este post oferece um mapa de riscos concreto e opinativo antes mesmo de você pensar em direcionar tráfego de produção para um Postgres-em-Rust greenfield.

O que “100% dos testes de regressão” realmente significa

A suíte de regressão do Postgres é um tesouro: ela verifica sintaxe SQL, comportamento do planner e do executor, tipos de dados, índices, algumas semânticas de MVCC e um longo rabo de funcionalidades centrais. Passar nela é um pré-requisito básico para compatibilidade de protocolo e SQL.

Mas isso não é um proxy de prontidão operacional. O que esses testes em grande parte não cobrem:

  • Durabilidade sob perda real de energia: comportamentos de fsync em diferentes filesystems (ext4, XFS, ZFS), tempos de group commit, detecção de página rasgada, limites de segmentos de WAL.
  • Cantos obscuros de replicação: comportamento de quórum em replicação síncrona/assíncrona, trocas de timeline, réplicas em cascata, standby atrasado e fencing de failover.
  • Vacuum e controle de bloat sob pressão: interações de autovacuum em altas taxas de escrita, HOT updates, exatidão do mapa de visibilidade ao longo de semanas de carga.
  • Caminhos de upgrade: upgrades in-place via pg_upgrade, migrações de catálogo entre versões major, estratégias de migração lógica.
  • Invariantes de observabilidade: estabilidade e precisão das views pg_stat_* em alta concorrência, hooks de auto_explain, semântica de reset de estatísticas.

Passar na suíte indica que o engine consegue “posar de Postgres”. Não diz o que acontece às 02h13 de um domingo durante uma partição de rede gradual quando seu primário sofre um stall de fsync e sua lógica de failover fica ansiosa.

Compatibilidade vai muito além de SQL

Se você já ficou de on-call do Postgres, sabe que o “banco de dados” é um ecossistema, não só um binário de servidor. Antes mesmo de levar uma reescrita em Rust para o lab, faça o inventário dos recursos que você realmente usa:

  • Extensões: pg_stat_statements, pgcrypto, pg_trgm, hstore, PostGIS, Timescale, HypoPG. A reescrita suporta o C-ABI do Postgres para extensões? Se não, quais equivalentes existem e quão maduros são?
  • Decodificação lógica: plugins de decodificação (wal2json, variantes de pgoutput), integração com Debezium, semântica de replication slots, interações com vacuum.
  • Foreign Data Wrappers (FDWs): você usa FDWs para S3, Kafka ou cross-DB? Como a reescrita os trata (se é que trata)?
  • Peculiaridades de protocolo e autenticação: comportamento em nível de wire com PgBouncer (Tx vs Session pooling), SCRAM-SHA-256, GSSAPI/Kerberos, políticas de renegociação de SSL.
  • Ferramentas de DDL e migração: Flyway, Liquibase, Django, Rails, Prisma, TypeORM. Algo pressupõe comportamentos de DDL transacional, advisory locks ou search_path?

Faça uma tabela simples de duas colunas para sua stack: “recurso usado” vs “status na reescrita em Rust”. Qualquer célula vermelha em extensões, decodificação lógica ou HA deve frear suas ambições de produção.

Maturidade operacional é 80% do valor do Postgres

Dez anos atrás, rodar Postgres em produção era ganhar seus galões. Hoje, o ecossistema o torna quase entediante — se você ficar nas trilhas mais batidas:

  • HA/Failover: Patroni, Stolon, repmgr. O engine em Rust se integra com eles? Se não, o que orquestra eleição de líder, fencing e bootstrap de réplicas?
  • Backup/PITR: base backups + arquivamento de WAL com pgBackRest ou WAL-G. Os formatos de arquivamento e a mecânica de restauração são compatíveis? Você consegue provar Point-in-Time Recovery para um timestamp arbitrário?
  • Upgrades: existe um equivalente de pg_upgrade ou você fará migrações lógicas a cada versão major?
  • Observabilidade: pg_stat_activity, pg_locks, pg_stat_replication, pg_stat_wal, pg_stat_io, auto_explain, amostragem de queries, higiene de log_line_prefix. Seus dashboards e alertas atuais plugam sem mudanças?
  • Vacuum, bloat e armazenamento: correção do mapa de visibilidade, estratégia de freeze, detecção e mitigação de bloat em relações. Você tem paridade com pg_stat_all_tables?

Se sua resposta for “vamos construir nossos próprios operators e fluxos de backup”, faça o orçamento à altura. Você está trocando uma década de memória muscular forjada pela comunidade por um runbook novo e sob medida.

Afirmativas de performance precisam de métricas “maçã com maçã”

A segurança de memória de Rust é uma vitória. Ela diz o que não vai acontecer (use-after-free), não o que vai (latência). Seja cético com gráficos escolhidos a dedo. Defina suas réguas:

  • Perfil de workload: OLTP (linhas pequenas, índices quentes), consultas de dashboard intensivas em leitura, varreduras analíticas? Use mixes representativos, não fantasias de pgbench -S.
  • SLOs de latência: p50 e p99 sob concorrência realista. Acompanhe o comportamento da cauda durante checkpoints, execuções de autovacuum e eventos de failover.
  • Paridade de recursos sob carga: JIT, parallel query, bitmap heap scans, pressão de work_mem, comportamento de spill de sort, group commit, trade-offs de synchronous_commit.
  • Hardware e FS: mesmo NVMe, mesmo filesystem, mesmas opções de montagem (barriers, noatime), mesmo kernel. Fixe CPU, isole IRQs, desative turbo se for para valer.

Defina gates rígidos. Por exemplo: “O engine em Rust deve manter p99 até 2x do nosso baseline de Postgres tunado a 2x do nosso QPS atual, com zero deltas de corretude observados por 7 dias, e RTO de failover ≤ 90 segundos.” Se não passar com folga, você está comprando risco, não performance.

Corretude dos dados sob estresse: o único benchmark que importa

A maioria dos desastres não parece um crash; parece progresso parcial. É aí que engines de banco de dados mostram seu valor. Avalie a reescrita com pensamento ao estilo Jepsen:

  • Loops de crash em hotspots de escrita: dê kill -9 no processo durante flush de WAL, corte a energia durante checkpoints, repita sob carga. Ao reiniciar, você vê detecção de página rasgada e replay consistente?
  • Integridade do WAL: segmentos corrompidos, arquivamento parcial de segmento, idempotência de archive/restore, ramificação de timelines. Você consegue restaurar de forma confiável para N-1 transações?
  • Verdade da replicação: réplica síncrona travando o primário, réplica assíncrona atrasando > 1 hora, partições de rede. Os acknowledgements de escrita estão alinhados com seu contrato em synchronous_standby_names?
  • Checksums e verificação: checksums de página ligados, scrubbing em background, equivalentes de pg_checksums, caminhos de detecção proativa em logs e métricas.

É fácil ser rápido no caminho feliz. É difícil estar certo quando o kernel mente sobre durabilidade, sua SAN está de mau humor e alguém tropeça em um PDU.

Um caminho pragmático de adoção se você ainda quiser entrar

Se você está decidido a fazer um test-drive de um Postgres em Rust, não comece trocando seu primário. Rode um programa estruturado:

  1. Segmente pelo raio de impacto: escolha um workload de baixo risco e baixo uso de recursos — analítica interna majoritariamente de leitura ou um novo microsserviço com um esquema simples. Evite PostGIS, compressão de séries temporais ou gatilhos pesados.
  2. Construa uma matriz de compatibilidade: liste todos os recursos que você usa (extensões, auth, CDC, FDWs) versus o suporte documentado da reescrita. Codifique por cores: verde/amarelo/vermelho. Pare se vir vermelho em CDC ou HA.
  3. Replique seu tráfego real: faça dual-write de uma pequena % das requisições de produção para ambos os bancos (ou reproduza traces de produção). Armazene hashes de resultados e compare. Condicione a promoção a zero deltas de corretude por 7–14 dias.
  4. Teste de paridade de CDC: se sua stack usa Debezium ou consumidores downstream, conecte-os ao engine em Rust. Verifique se não há eventos perdidos ou reordenados, especialmente sob churn de DDL.
  5. Drills de Backup/PITR: faça um base backup, arquive WAL e realize uma restauração cronometrada para T-5 minutos. Verifique contagens de linhas, checksums lógicos e invariantes no nível da aplicação.
  6. Game day de HA: force um crash do primário no pico de carga, valide RTO/RPO de failover contra seus SLOs e confirme que os clientes (via PgBouncer) reconectam sem tempestade de erros.
  7. Gates de rollout: promova para um único tenant/ambiente de baixo risco após 2 semanas limpas consecutivas. Mantenha um caminho de rollback imediato via pg_dump ou migração lógica de volta para o Postgres padrão.
  8. Plano de saída documentado: decida agora como você vai sair (dump/restore, cópia lógica). Valide em um dataset não trivial antes de expor qualquer coisa a clientes.

Coloque um timebox. Espere 6–8 semanas de operação em paralelo e 2–3 engenheiro-mês para chegar a uma decisão go/no-go com confiança. Qualquer coisa menos que isso é teatro.

Onde uma reescrita em Rust pode brilhar hoje

  • Implantações de borda ou embarcadas: se o engine oferece menor footprint ou melhor cold-start, pode ser ideal para caches de inferência locais ou apps offline-first.
  • Restrições rígidas de supply chain e segurança de memória: ambientes regulados que valorizam segurança de memória e uma base de código moderna em Rust podem aceitar trade-offs operacionais por uma superfície mais segura e estreita.
  • Fatias de funcionalidades greenfield: um serviço interno com esquemas simples e tráfego modesto pode servir de campo de prova sem enredar suas trilhas de receita centrais.

Nenhum desses deveria ser seu sistema de faturamento ou seu repositório de identidade de clientes.

Quando dizer não por enquanto

  • Implantações pesadas em extensões (PostGIS, Timescale, extensões C customizadas). Sem paridade de ABI, você gastará trimestres substituindo componentes maduros e suportados por sósias.
  • HA multirregião com RPOs apertados. Replicação entre regiões, fencing de promoção e prevenção de split-brain são cicatrizes que você não quer ganhar duas vezes.
  • Pipelines de CDC de alto volume alimentando analytics ou busca. Canto de decodificação lógica é onde bugs de corretude se escondem.
  • Workloads regulados exigindo controles documentados, cripto validada por FIPS, playbooks determinísticos de backup/restore e observabilidade amigável a auditorias.

Orce quanto isso realmente custa

Engines novos não substituem itens de linha; eles os criam. Ordem de grandeza para uma avaliação de verdade (não um fim de semana movido a blog):

  • Engenharia: 2–3 engenheiro-mês para infraestrutura, testes e observabilidade. Some 1 SRE familiarizado com seu Postgres atual para evitar falsas equivalências.
  • Ferramentas: escrever ou adaptar operators, scripts de backup, dashboards. Espere 1–2 semanas para atingir paridade com seus alertas atuais.
  • Tempo de caos: pelo menos dois game days (crash/failover e restore/PITR). Reserve 6–8 horas cada, com stakeholders presentes.
  • Risco de on-call: o primeiro trimestre em produção costuma carregar 2–3 alertas extras até os runbooks estabilizarem. Coloque esse estresse na conta da decisão.

Se um fornecedor estiver vendendo essa ideia, peça que co-financie ou equipe os testes. Se não topar, isso é um sinal.

O ângulo nearshore: alugue poder de teste, não heroísmo

Esse tipo de avaliação é perfeito para um pod nearshore: repetitivo, disciplinado, com timebox e mensurável. Com 6–8 horas de sobreposição de Brazil para os US e uma vantagem de custo de 20–30% sobre contractors nos US, você pode montar um pequeno squad para:

  • Construir a matriz de compatibilidade e os harnesses de teste.
  • Subir stacks de HA, backup e observabilidade em paralelo.
  • Rodar comparações de dual-write e diffs de corretude a partir de replays de tráfego real.
  • Automatizar game days: cortes de energia, loops de kill -9 e drills de PITR com agenda.

O resultado que você quer é entediante: dashboards verdes, diffs limpos e runbooks que um plantonista sonolento consegue seguir às 2 da manhã.

Em resumo

Rust não é o ponto. Corretude de dados e trabalho operacional braçal são. Uma reescrita de Postgres que acerte ambos ganhará seu lugar com o tempo. Até lá, trate “100% dos testes de regressão” como um convite para avaliar — não uma permissão para migrar. Rode um programa estruturado, escalone seus riscos e insista em resultados entediantes antes de mover qualquer coisa com o nome de um cliente.

Principais pontos

  • Passar na suíte de regressão do Postgres é necessário, mas está longe de ser suficiente para prontidão de produção.
  • Faça inventário de extensões, CDC, FDWs, autenticação e ferramentas; qualquer célula vermelha na matriz é um sinal de pare.
  • Maturidade operacional (HA, PITR, upgrades, observabilidade, higiene de vacuum) é 80% do trabalho real.
  • Defina gates de performance em p50/p99 sob carga e caos realistas, não microbenchmarks de caminho feliz.
  • Testes ao estilo Jepsen de crash, WAL e replicação são onde bugs de corretude aparecem — rode-os.
  • Adote via pilotos de baixo raio de impacto, dual-writing e gates de rollout com timebox; mantenha um plano de saída documentado.
  • Diga “ainda não” para workloads pesados em extensões, multirregião, centrados em CDC ou regulados.
  • Espere 2–3 engenheiro-mês para avaliar seriamente; considere um pod nearshore para conter custo e cronograma.

Ready to scale your engineering team?

Tell us about your project and we'll get back to you within 24 hours.

Start a conversation