- O PgDog, um proxy de extensão para PostgreSQL, adotou bindings diretos em Rust em vez de serialização com Protobuf para aumentar o desempenho do parsing de SQL
- A estrutura anterior baseada em Protobuf foi substituída por conversão direta C–Rust (bindgen + wrappers gerados pelo Claude), resultando em ganhos de 5,45x no parsing e 9,64x no deparsing
- O gargalo de desempenho foi identificado na função pg_query_parse_protobuf, e após tentativas com cache, uma mudança estrutural foi feita para obter uma melhoria fundamental
- Usando o Claude LLM, a equipe gerou automaticamente 6.000 linhas de código de conversão Rust–C e aplicou isso a funções principais como
parse, deparse, fingerprint e scan
- Com essa otimização, o uso de CPU e a latência do PgDog diminuíram, melhorando bastante sua eficiência como proxy de escalabilidade horizontal para PostgreSQL
PgDog e os limites do Protobuf
- O PgDog é um proxy para escalar o PostgreSQL e usa internamente o libpg_query para fazer o parsing de consultas SQL
- Ele é escrito em Rust e, antes, se comunicava com a biblioteca em C por meio de serialização/desserialização com Protobuf
- Embora o Protobuf seja rápido, a abordagem com bindings diretos é ainda mais rápida
- A equipe do PgDog fez um fork de
pg_query.rs, removeu o Protobuf e implementou bindings diretos C–Rust
- Como resultado, o parsing de consultas ficou 5,45x mais rápido, e o deparsing 9,64x mais rápido
Resultados do benchmark
- O benchmark pode ser reproduzido no repositório forkado do PgDog
pg_query::parse (Protobuf): 613 QPS
pg_query::parse_raw (C–Rust direto): 3357 QPS
pg_query::deparse (Protobuf): 759 QPS
pg_query::deparse_raw (Rust–C direto): 7319 QPS
Análise do gargalo e tentativa com cache
- Ao analisar o tempo de uso de CPU com o profiler samply, a função pg_query_parse_protobuf foi identificada como o gargalo
- Houve uma tentativa de melhorar parcialmente com cache
- Foi usado um cache HashMap com algoritmo LRU, armazenando a AST com o texto da consulta como chave
- Em casos com prepared statements, a reutilização era possível
- Porém, alguns ORMs geravam milhares de consultas únicas, e drivers antigos do PostgreSQL não suportavam prepared statements, o que reduzia a eficiência do cache
Removendo o Protobuf com ajuda de LLM
- A equipe do PgDog usou o Claude LLM para gerar bindings em Rust sem Protobuf
- A IA funcionou bem dentro de um escopo de trabalho claro e verificável
- Com base na especificação Protobuf do
libpg_query, o Claude mapeou structs em C para structs em Rust
- Após 2 dias de trabalho iterativo, foram concluídas 6.000 linhas de código Rust recursivo
- Isso foi aplicado às funções
parse, deparse, fingerprint e scan, confirmando uma melhora de 25% no pgbench
Detalhes da implementação
- A conversão entre Rust e C usa funções unsafe para mapear diretamente as structs
- As structs em C são passadas para a API do Postgres para gerar a AST, depois convertida recursivamente para Rust
- Cada nó da AST é tratado pela função convert_node, que mapeia centenas de tokens da gramática SQL
- Há funções de conversão separadas para cada tipo de nó, como SELECT e INSERT
- O resultado da conversão reutiliza a struct Protobuf existente (
protobuf::ParseResult), permitindo validação por comparação em nível de bytes durante os testes
- O algoritmo recursivo faz menos alocações de memória e aproveita melhor o cache de CPU, sendo mais rápido do que uma implementação baseada em loops
- A implementação com loops acabou ficando mais lenta por causa de alocações desnecessárias de memória e consultas a HashMap
Conclusão
- Ao reduzir a sobrecarga do parser do Postgres, o PgDog corta latência, memória e uso de CPU
- Com essa otimização, o PgDog evolui como um proxy de escalabilidade para PostgreSQL mais rápido e mais barato de operar
- O PgDog está recrutando engenheiros para construir junto a próxima iteração da escalabilidade horizontal do PostgreSQL
3 comentários
Talvez eu esteja interpretando mal o texto original, mas especialmente os textos sobre Rust parecem ser escritos como se tivesse ficado mais rápido "por ser Rust", deixando a essência de lado.
O ponto principal deste texto é que o desempenho melhorou por reduzir a sobrecarga de serialização desnecessária.
Agora, revendo, talvez também não seja exatamente um texto exaltando Rust desse jeito, mas será que acabei criando uma percepção negativa por causa de outros textos?
Eu também achei que o título original parecia "Rust demais" em relação ao conteúdo real, dando a impressão de que o foco era a melhora de desempenho, então fiz um pequeno ajuste.
Como textos sobre Rust costumam mostrar essa tendência com frequência, acho que é preciso ler filtrando um pouco.
Comentários no Hacker News
O título faz parecer ironicamente que Rust deu um ganho de desempenho de 5x, quando na verdade o sistema estava mais lento
O problema era que um software escrito em Rust precisava usar a
libpg_query, escrita em C, mas como não conseguia se conectar diretamente, acabou usando um binding Rust–C baseado em ProtobufComo essa abordagem era lenta, no fim reescreveram os bindings com ajuda de LLM, de forma menos portátil porém muito mais otimizada
Se tivesse sido escrito em C desde o começo, esse processo de conversão não teria sido necessário. Ou seja, o título mais correto seria algo como “reduziu a perda de desempenho causada pelo uso de Rust”
Camadas de conversão trazem portabilidade e segurança, mas no fim acabam repetindo cópia, conversão e serialização, e isso é uma das causas de lentidão em aplicações
Chamar bibliotecas C a partir de Rust é muito fácil, e já existem muitos wrappers seguros
Quase nunca se vê uma arquitetura com Protobuf no meio, e esse era o gargalo
O título parece mais um meme do tipo “reescrevemos em Rust” para gerar cliques
A biblioteca original tinha um design ruim, repetindo serialização/desserialização, e o ponto principal foi remover isso
Um título mais preciso seria “trocaram Protobuf por uma API comum e ficou 5x mais rápido”
Bindings de C em Rust estão entre os casos mais simples e, se a API não for grande, tudo fica bem direto
Protobuf me parece uma ferramenta inadequada para troca de dados em memória
Acho que, com ajuda de LLM, veremos uma explosão de ports para várias linguagens
O título é um pouco enganoso
Na prática, a história é “ficou mais rápido depois que removeram a etapa de serialização em Protobuf”
Ele permite que cliente e servidor sejam atualizados de forma independente e continuem funcionando, além de facilitar comunicação entre várias linguagens
Em sistemas grandes, esse tipo de flexibilidade é muito importante
memcpyoummapsão bem mais rápidos, mas o ecossistema Rust tende a evitar essas abordagens insegurasTalvez a lentidão não fosse por causa de Rust, nem de Protobuf em si, mas por usarem Protobuf como formato genérico de armazenamento
No fim, o principal foi simplificar tudo para o objetivo específico
Colocar Rust no título parece ter sido uma escolha para atrair cliques
O autor original de
pg_queryexplicou o contextoOriginalmente, isso era usado na pganalyze para analisar queries do Postgres, encontrar referências a tabelas e reescrever ou formatar queries
No início usavam JSON, mas depois migraram para Protobuf para facilitar bindings com segurança de tipos em várias linguagens (Ruby, Go, Rust, Python etc.)
Para linguagens como Rust, FFI é melhor, mas em outras o custo de manutenção é maior
Ele apoia a tentativa do Lev e pretende adicionar, no futuro, funções para acessar
libpg_querydiretamente via FFIAinda assim, quando desempenho não é essencial, Protobuf continua sendo uma opção mais conveniente
O “5x mais rápido” lembra a piada do Cap’n Proto de ser “infinitamente rápido”
O título é exagerado, mas o trabalho em si é impressionante
Não eliminaram completamente o Protobuf; eles otimizaram a forma de uso
A frase “trocamos X e ficou 5x mais rápido” normalmente quer dizer “consertamos uma implementação bagunçada”
A principal lição é:
O FFI de Rust também tem overhead, então o mérito real não veio da linguagem, e sim do redesenho do fluxo de dados e do esforço de otimização
FlatBuffers é mais rápido, mas Protobuf continua sendo usado porque é mantido por uma grande empresa
No fim, essa ideia de que “se foi feito pelo Google, então é seguro” não tem muito fundamento
code.google.com) e vi tudo morrer depoisSe o objetivo é só uma estrutura zero-copy com memória compartilhada e campos de versão, não vejo motivo para usar Protobuf
Acho o desempenho do Protobuf uma piada
O certo seria usar um formato zero-copy em que serialização é praticamente gratuita
Por exemplo, o Lite³, que eu criei, é 242x mais rápido que FlatBuffers
Existem muitos motivos práticos para escolher Protobuf: ecossistema, schema, tooling por linguagem etc.
Na prática, o problema não era Rust nem Protobuf, e sim a implementação ineficiente de serialização na camada de abstração do PostgreSQL
O pgdog removeu essa camada e passou os dados diretamente para a API em C
Se você remove funcionalidade desnecessária, é natural que fique mais rápido
Mas ainda existem casos em que algumas pessoas precisam de serialização
Para essas pessoas, um título dizendo “troque para Rust” passa a mensagem errada
No fim, na maioria dos casos JSON já basta, e se realmente for preciso mais velocidade, o ideal é evitar serialização por completo
Isso é uma comparação injusta
Usar um protocolo de serialização em comunicação IPC obviamente traz overhead
É um caso perfeito para a frase: “se melhorou 20%, foi otimização; se melhorou 10x, então já estava mal feito desde o início”