- PgDog, un proxy de extensiones para PostgreSQL, adoptó enlaces directos de Rust en lugar de serialización con Protobuf para mejorar el rendimiento del análisis de SQL
- Reemplazó la estructura previa basada en Protobuf por conversión directa C–Rust (bindgen + wrappers generados con Claude), logrando una mejora de 5.45 veces en parsing y 9.64 veces en deparsing
- El cuello de botella de rendimiento se encontró en la función pg_query_parse_protobuf, y tras intentar con caché se hizo un cambio estructural para lograr una mejora de fondo
- Con Claude LLM se generaron automáticamente 6,000 líneas de código de conversión Rust–C, aplicadas a funciones clave como
parse, deparse, fingerprint y scan
- Con esta optimización, bajaron el uso de CPU y la latencia de PgDog, mejorando mucho su eficiencia como proxy de escalado horizontal para PostgreSQL
PgDog y los límites de Protobuf
- PgDog es un proxy para escalar PostgreSQL y usa internamente libpg_query para analizar consultas SQL
- Está escrito en Rust y antes se comunicaba con la librería en C mediante serialización/deserialización con Protobuf
- Protobuf es rápido, pero el enfoque de enlaces directos es todavía más rápido
- El equipo de PgDog hizo un fork de
pg_query.rs, eliminó Protobuf e implementó enlaces directos C–Rust
- Como resultado, el parsing de consultas fue 5.45 veces más rápido y el deparsing 9.64 veces más rápido
Resultados del benchmark
- El benchmark se puede reproducir en el repositorio fork de PgDog
pg_query::parse (Protobuf): 613 QPS
pg_query::parse_raw (directo C–Rust): 3357 QPS
pg_query::deparse (Protobuf): 759 QPS
pg_query::deparse_raw (directo Rust–C): 7319 QPS
Análisis del cuello de botella e intento con caché
- Tras analizar el tiempo de uso de CPU con el profiler samply, se confirmó que la función pg_query_parse_protobuf era el cuello de botella
- Se intentó una mejora parcial mediante caché
- Se usó una caché hash map basada en algoritmo LRU, guardando el AST con el texto de la consulta como clave
- En los casos con prepared statements, era posible reutilizarlo
- Sin embargo, algunos ORM generaban miles de consultas únicas, o bien drivers antiguos de PostgreSQL no soportaban prepared statements, por lo que la eficiencia de la caché era baja
Eliminación de Protobuf con ayuda de un LLM
- El equipo de PgDog usó Claude LLM para generar bindings de Rust sin Protobuf
- La IA funcionó bien dentro de un alcance de trabajo claro y verificable
- Claude mapeó estructuras C a estructuras Rust basándose en la especificación Protobuf de
libpg_query
- Tras dos días de iteración, completaron 6,000 líneas de código recursivo en Rust
- Se aplicó a las funciones
parse, deparse, fingerprint y scan, confirmando una mejora de rendimiento del 25% según pgbench
Detalles de implementación
- La conversión entre Rust y C se hace con funciones unsafe que mapean directamente las estructuras
- Las estructuras C se pasan a la API de Postgres para generar el AST, y luego se convierten recursivamente a Rust
- Cada nodo del AST se procesa con la función convert_node, que mapea cientos de tokens de la sintaxis SQL
- Existen funciones de conversión separadas para cada tipo de nodo, como SELECT o INSERT
- El resultado de la conversión reutiliza la estructura Protobuf existente (
protobuf::ParseResult), lo que permite validación en pruebas mediante comparación a nivel de bytes
- El algoritmo recursivo requiere menos asignaciones de memoria y aprovecha mejor la caché de CPU, por lo que fue más rápido que una implementación basada en iteraciones
- Una implementación iterativa resultó más lenta por asignaciones de memoria innecesarias y búsquedas en hash maps
Conclusión
- Al reducir la sobrecarga del parser de Postgres, PgDog logró bajar latencia, memoria y uso de CPU
- Con esta optimización, PgDog evoluciona como un proxy de extensiones para PostgreSQL más rápido y más barato de operar
- PgDog está contratando ingenieros para construir juntos la siguiente iteración del escalado horizontal de PostgreSQL
3 comentarios
Puede que yo esté malinterpretando el texto original, pero especialmente los artículos relacionados con Rust parecen escritos como si, dejando de lado lo esencial, se hubiera vuelto más rápido simplemente "por ser Rust".
El punto principal de este artículo es que el rendimiento mejoró al reducir la sobrecarga innecesaria de serialización.
Ahora que lo vuelvo a ver, tampoco parece ser un artículo que glorifique tanto a Rust, pero ¿será que otros artículos ya me hicieron formar una percepción negativa?
Yo también sentí que el título original, a diferencia del contenido real, estaba demasiado cargado hacia Rust y hacía ver que el foco era la mejora de rendimiento, así que lo ajusté un poco.
Como los artículos sobre Rust suelen mostrar esa tendencia con frecuencia, creo que conviene leerlos filtrándolos un poco.
Opiniones en Hacker News
El título hace parecer irónicamente que Rust dio una mejora de rendimiento de 5x, cuando en realidad se había vuelto más lento
El problema era que el software escrito en Rust tenía que usar
libpg_query, que está en C, pero como no podían conectarlo directamente, usaron unos bindings Rust–C basados en ProtobufEse enfoque era lento, así que al final, con ayuda de un LLM, reescribieron bindings nuevos, menos portables pero mucho más optimizados
Si lo hubieran escrito en C desde el principio, no habría hecho falta el proceso de conversión. O sea, el título más exacto habría sido “redujimos la pérdida de rendimiento causada por usar Rust”
Las capas de conversión dan portabilidad y seguridad, pero al final repiten copias, conversiones y serialización, y creo que son una de las causas que terminan haciendo más lenta a una app
Llamar librerías de C desde Rust es muy fácil, y ya existen muchos wrappers seguros
Casi nunca he visto una arquitectura con Protobuf en medio, y eso era el cuello de botella
El título se siente más como un meme de “lo reescribimos en Rust” para atraer clics
La librería original tenía un mal diseño que repetía serialización/deserialización, y la clave fue eliminar eso
Un título más preciso sería “reemplazamos Protobuf por una API común y se volvió 5 veces más rápido”
En Rust, los bindings con C son de lo más fácil y, si la API no es grande, bastante simples
Protobuf me parece una herramienta inadecuada para intercambiar datos en memoria
Da la impresión de que, gracias a los LLM, van a explotar las migraciones entre lenguajes
El título se presta un poco a confusión
En la práctica, la historia es “quitamos el paso de serialización con Protobuf y se volvió más rápido”
Permite que cliente y servidor se actualicen por separado y sigan funcionando, además de facilitar la comunicación entre varios lenguajes
En sistemas grandes, esa flexibilidad es muy importante
memcpyommapson mucho más rápidos, pero en el mundo Rust se evita ese tipo de métodos insegurosLa lentitud quizá no venía de Rust, sino de usar Protobuf como formato de almacenamiento generalizado
Al final, la clave fue simplificarlo para ajustarlo a un propósito específico
Meter Rust en el título parece más una decisión para generar clics
El autor original de
pg_queryexplicó el contextoEn un principio, en pganalyze lo usaban para parsear consultas de Postgres, encontrar referencias a tablas, y reescribir o formatear queries
Al inicio usaban JSON, pero luego cambiaron a Protobuf para poder ofrecer más fácilmente bindings con seguridad de tipos en varios lenguajes (Ruby, Go, Rust, Python, etc.)
Para lenguajes como Rust, FFI sí es mejor, pero en otros lenguajes la carga de mantenimiento es mayor
Apoya el intento de Lev, y planea agregar en el futuro funciones para acceder directamente a
libpg_queryvía FFIAun así, cuando el rendimiento no es crítico, Protobuf sigue siendo una opción más conveniente
Lo de “5 veces más rápido” hace pensar en el chiste de Cap’n Proto de que es “infinitamente rápido”
El título es exagerado, pero el trabajo real sí impresiona
No quitaron Protobuf por completo, sino que optimizaron la manera de usarlo
La frase “cambiamos X y se volvió 5 veces más rápido” casi siempre significa “arreglamos una implementación que estaba hecha un desastre”
La enseñanza principal es:
El FFI de Rust también tiene overhead, así que el logro real no vino del lenguaje sino del rediseño del flujo de datos y del trabajo de optimización
FlatBuffers es más rápido, pero se usa Protobuf porque lo mantiene una gran empresa
Al final, la idea de “si lo hizo Google, es seguro” no tiene mucho fundamento
code.google.com) y ya vi cómo terminó malSi lo único que hace falta es una estructura zero-copy con memoria compartida y campos de versión, no veo por qué usar Protobuf
Me parece que el rendimiento de Protobuf es casi un chiste
Habría que usar un formato zero-copy donde serializar sea prácticamente gratis
Por ejemplo, Lite³, que yo hice, es 242 veces más rápido que FlatBuffers
La razón por la que se usa Protobuf tiene que ver con el ecosistema, los esquemas, el tooling por lenguaje y muchas otras razones prácticas
En realidad no era un problema de Rust ni de Protobuf, sino de una implementación ineficiente de serialización en la capa de abstracción de PostgreSQL
pgdogquitó esa capa y pasó los datos directamente a través de la API en CSi eliminas funciones innecesarias, obviamente va a ser más rápido
Pero para algunas personas la serialización sigue siendo necesaria
Para ellas, un título como “cámbiate a Rust” transmite el mensaje equivocado
Al final, en la mayoría de los casos JSON es suficiente, y si de verdad necesitas más velocidad, lo ideal es evitar la serialización en sí
Esta es una comparación injusta
Usar un protocolo de serialización para comunicación IPC obviamente tiene overhead
Le queda perfecto esa frase de que “si mejora 20%, es una optimización; si mejora 10x, es porque estaba mal diseñado desde el inicio”