⏱️ Lectura: 14 min

Un modelo de lenguaje de apenas 4.000 millones de parámetros logró generar planes de consulta un 44,7% más rápidos que los que produce Postgres por defecto, según un experimento documentado por el ingeniero Rohan Bansal. El hallazgo llega diez años después de que un estudio académico confirmara que los optimizadores de bases de datos, pese a décadas de investigación, siguen tomando decisiones lejos de la óptima.

📑 En este artículo
  1. TL;DR
  2. Introducción
  3. Qué pasó: cómo se entrenaron los planes de consulta
  4. Contexto e historia: por qué los optimizadores siguen fallando
  5. Detalles técnicos y rendimiento
  6. Cómo probarlo
    1. Linux (Debian / Ubuntu)
    2. macOS (Homebrew)
    3. Windows (PowerShell)
  7. Impacto y análisis
  8. Qué sigue
  9. Preguntas frecuentes
    1. ¿Este modelo reemplaza al optimizador de Postgres?
    2. ¿Por qué usar un modelo de solo 4B parámetros y no uno más grande?
    3. ¿Qué es GRPO?
    4. ¿Por qué el ordenamiento de joins es tan difícil?
    5. ¿Necesito modificar el código fuente de Postgres para probar esto?
    6. ¿Dónde puedo ver el código y los detalles completos del experimento?
  10. Referencias

El experimento, publicado por Bansal en su blog personal, combina ajuste fino supervisado (SFT) y aprendizaje por refuerzo (RL) sobre un modelo Qwen para que aprenda a sugerir hints de ejecución a Postgres antes de que el motor arme su propio plan.

TL;DR

  • Un modelo Qwen de 4B, ajustado con SFT y RL, redujo 44,7% la latencia en 113 consultas con múltiples joins frente al plan por defecto de Postgres.
  • Antes del entrenamiento, el modelo no lograba producir un plan válido para 99 de esas 113 consultas.
  • El rig de medición usó un nodo alquilado con 2x H100 para vLLM y el entrenador, más 4 contenedores Postgres en un desktop local.
  • El autor diseñó una variante propia de GRPO para lidiar con el ruido al medir tiempos de ejecución reales.
  • Se aplicó distilación off-policy sobre unas 500 trayectorias generadas por un agente basado en GPT-6 Astra.
  • El estudio de Leis et al. de 2015, repetido 10 años después, confirmó que los optimizadores de bases de datos siguen fallando en el ordenamiento de joins.
  • El sistema usa la extensión pg_hint_plan para inyectar hints como HashJoin, NestLoop, IndexScan y Leading sin tocar el motor de Postgres.

Introducción

Cuando Postgres recibe una consulta con varios JOIN, tiene que decidir en qué orden va a combinar las tablas y con qué algoritmo (hash join, nested loop o merge join). Esa decisión, tomada por el planificador de consultas, define si la consulta tarda 70 milisegundos o 700. El planificador estima costos con estadísticas internas, pero esas estimaciones se degradan rápido cuando hay correlaciones entre columnas o distribuciones poco uniformes.

La pregunta que se hizo Bansal es directa: si generar un buen plan de consulta es difícil, pero verificar si un plan es rápido o lento es trivial (basta con ejecutarlo y medir el tiempo), ¿por qué no dejar que un modelo de lenguaje aprenda a proponer planes de consulta mediante prueba y error, igual que aprende a jugar un juego con una función de recompensa clara?

Qué pasó: cómo se entrenaron los planes de consulta

El proceso de entrenamiento funciona por rollouts. Para una misma consulta SQL, el modelo Qwen genera cuatro candidatos distintos de planes de consulta en forma de hints. Cada candidato se envía a una instancia real de Postgres, que lo ejecuta y mide su latencia contra el plan por defecto del motor. Esa diferencia de tiempo se convierte en una recompensa escalar que se propaga hacia atrás para ajustar los pesos del modelo.

Ciclo de entrenamiento por refuerzo para planes de consulta en Postgres
Cuatro rollouts por consulta, cada uno midiendo latencia real contra Postgres. Foto de Vitaly Gariev en Unsplash

Un ejemplo del propio experimento ilustra el mecanismo. Para la consulta SELECT count(*) FROM title t JOIN movie_companies mc ON mc.movie_id = t.id JOIN company_name cn ON cn.id = mc.company_id WHERE cn.name = 'Toho', el plan por defecto de Postgres tardó 118 ms. El hint /*+ Leading((cn mc) t) */ lo bajó a 74 ms, mientras que /*+ NestLoop(t mc) */ lo empeoró a 163 ms. Esa varianza entre hints, medida rollout por rollout, es exactamente la señal que el algoritmo de RL usa para aprender qué estructuras de join funcionan mejor en qué contexto.

sequenceDiagram
    participant Q as Qwen4B
    participant P as Postgres
    participant R as Reward
    Q->>P: propone hint de plan
    P-->>Q: mide tiempo de ejecucion
    Q->>R: rollout con latencia
    R-->>Q: actualiza pesos via GRPO
    Note over Q,P: se repiten 4 rollouts por consulta
💡 Tip: Postgres, a diferencia de Oracle o SQL Server, no acepta hints nativos en el SQL estándar. Todo este mecanismo depende de la extensión externa pg_hint_plan, que interpreta comentarios especiales como /*+ HashJoin(a b) */ antes de que el planificador arme el plan real.

Contexto e historia: por qué los optimizadores siguen fallando

La pregunta de fondo (qué tan buenos son realmente los optimizadores de consultas) no es nueva. Un grupo de investigadores liderado por Viktor Leis la planteó formalmente en 2015 y la repitió una década después, encontrando que, pese a diez años adicionales de investigación en la industria y la academia, los optimizadores comerciales y de código abierto siguen produciendo planes lejos del óptimo en consultas con múltiples joins.

La razón técnica tiene nombre: el ordenamiento de joins (join ordering) es un problema NP-hard. Con solo diez tablas en una consulta, el número de árboles de join posibles crece de forma combinatoria, y ningún optimizador puede permitirse explorarlos todos en el tiempo que un usuario está dispuesto a esperar por un plan. Por eso los motores como Postgres recurren a heurísticas y estimaciones estadísticas que, cuando fallan, producen planes de consulta muy por debajo de lo posible.

Ese mismo problema es, paradójicamente, una buena noticia para el aprendizaje por refuerzo. Generar un plan óptimo es difícil, pero verificar si un plan es mejor que otro es tan simple como cronometrar dos ejecuciones. Cuando existe un único eje de optimización, en este caso el tiempo de ejecución, el problema se reduce a reforzar los comportamientos que producen planes de consulta más rápidos, sin necesidad de que un humano etiquete manualmente qué plan es correcto.

Detalles técnicos y rendimiento

El experimento usa una porción del dataset de IMDb, con tablas como title (alrededor de 1 millón de filas), movie_companies (unos 2 millones de filas, tabla de unión entre películas y compañías) y company_name (cerca de 100.000 filas). Es un esquema clásico de benchmarking de joins porque combina tablas grandes, tablas de unión y tablas de catálogo pequeñas, lo que obliga al optimizador a decidir entre múltiples estrategias razonables.

-- Esquema simplificado usado en el experimento (IMDb)
CREATE TABLE title (
  id integer PRIMARY KEY,
  title text,
  production_year integer,
  kind_id integer
);

CREATE TABLE movie_companies (
  id integer PRIMARY KEY,
  movie_id integer,   -- FK -> title.id
  company_id integer, -- FK -> company_name.id
  company_type_id integer,
  note text
);

CREATE TABLE company_name (
  id integer PRIMARY KEY,
  name text,
  country_code text
);

Para evitar que el ruido del sistema operativo contaminara la señal de recompensa, el autor tuvo que resolver un problema de infraestructura poco discutido en papers de RL: la contención de la caché de páginas de Linux entre contenedores concurrentes. Si dos contenedores Postgres comparten memoria de caché de forma desigual, el mismo plan puede medir tiempos distintos entre corridas, lo que confunde al algoritmo de entrenamiento. El rig final separó el entrenamiento (un nodo alquilado con 2x H100 corriendo vLLM y el proceso de entrenamiento) de la medición (4 contenedores Postgres corriendo en un desktop local), y diseñó una variante propia de GRPO (Group Relative Policy Optimization) pensada específicamente para normalizar recompensas en un entorno de medición ruidoso.

Además del RL puro, el modelo recibió una etapa de distilación off-policy: cerca de 500 trayectorias generadas por un agente basado en GPT-6 Astra sirvieron como ejemplos de comportamiento antes de que el entrenamiento por refuerzo afinara la política. Esta combinación (SFT con trayectorias de un modelo más grande, seguido de RL con recompensas verificables) es el mismo patrón que se usa hoy para entrenar modelos de razonamiento matemático o de generación de código, aplicado aquí a un dominio muy distinto: planes de consulta de bases de datos relacionales.

Hint (pg_hint_plan)Cuándo usarlaVentajaLimitación
HashJoin(a b)Tablas grandes sin índice útil en la condición de joinBuen rendimiento si el hash cabe en memoriaConsume work_mem, puede volcar a disco si el hash es grande
NestLoop(a b)Una de las tablas es pequeña o ya está muy filtradaBaja sobrecarga con pocas filasDegrada mal si la estimación de filas es incorrecta
IndexScan(a)Existe un índice selectivo sobre la columna filtradaEvita leer la tabla completaContraproducente si la selectividad es baja
Leading((a b) c)Se conoce el orden óptimo entre tres o más tablasElimina la búsqueda combinatoria del optimizadorHay que revisarlo si cambian esquema o datos

⚠️ Ojo: El ordenamiento de joins es un problema NP-hard: no hay garantía de que el modelo, ni Postgres, encuentren el óptimo global, solo un plan mejor que el default en el conjunto de consultas evaluado. El propio autor reporta el resultado sobre 113 consultas específicas del dataset de IMDb, no como una garantía universal para cualquier carga de trabajo.

Cómo probarlo

No hace falta reproducir el entrenamiento por refuerzo para experimentar con la idea central: forzar planes de consulta alternativos en Postgres y medir la diferencia vos mismo con pg_hint_plan.

Linux (Debian / Ubuntu)

# Instalar Postgres y las cabeceras de desarrollo
sudo apt install postgresql postgresql-server-dev-16
git clone https://github.com/ossc-db/pg_hint_plan.git
cd pg_hint_plan && make && sudo make install

macOS (Homebrew)

# Instalar Postgres con Homebrew
brew install postgresql@16
git clone https://github.com/ossc-db/pg_hint_plan.git
cd pg_hint_plan && make USE_PGXS=1 && make USE_PGXS=1 install

Windows (PowerShell)

# Requiere PostgreSQL instalado y Visual Studio Build Tools
git clone https://github.com/ossc-db/pg_hint_plan.git
cd pg_hint_plan
nmake /f win32.mak
nmake /f win32.mak install

Con la extensión instalada, el primer paso es ver qué plan elige Postgres por su cuenta:

-- Activar la extension en la base de datos
CREATE EXTENSION pg_hint_plan;

-- Ver el plan por defecto
EXPLAIN (ANALYZE, BUFFERS)
SELECT count(*)
FROM title t
JOIN movie_companies mc ON mc.movie_id = t.id
JOIN company_name cn ON cn.id = mc.company_id
WHERE cn.name = 'Toho';

Ese comando muestra el plan real elegido y el tiempo de ejecución medido. El siguiente paso es forzar un hint distinto y comparar, exactamente como hacía cada rollout del entrenamiento:

-- Verificar que la extension esta activa y loguea los hints aplicados
SET pg_hint_plan.debug_print = 'on';

/*+ HashJoin(mc cn) */
EXPLAIN (ANALYZE, BUFFERS)
SELECT count(*)
FROM title t
JOIN movie_companies mc ON mc.movie_id = t.id
JOIN company_name cn ON cn.id = mc.company_id
WHERE cn.name = 'Toho';

-- En el log de Postgres deberia aparecer una linea
-- "pg_hint_plan: hint syntax OK" confirmando que el hint se aplico

La línea de log con pg_hint_plan: hint syntax OK es la forma de verificar que el hint realmente se aplicó y no fue ignorado por un error de sintaxis, algo fácil de pasar por alto la primera vez que se usa la extensión.

Impacto y análisis

El resultado más llamativo del experimento no es solo la reducción de 44,7% en latencia, sino el punto de partida: antes del entrenamiento, el modelo Qwen de 4B ni siquiera lograba producir un plan sintácticamente válido para 99 de las 113 consultas evaluadas. Eso significa que la mayor parte de la ganancia no vino de afinar un modelo que ya entendía el dominio, sino de enseñarle desde cero una tarea estructurada que no dominaba en absoluto.

Comparacion de latencia entre planes de consulta con y sin RL
El modelo pasó de no generar planes válidos a superar el default de Postgres. Foto de Vitaly Gariev en Unsplash

Para un equipo de ingeniería, esto no implica reemplazar mañana al optimizador de Postgres por un modelo de 4B corriendo en producción. Cada consulta requeriría una llamada de inferencia antes de ejecutarse, lo que añade latencia y costo de cómputo que solo se justifica en consultas analíticas pesadas, no en transacciones OLTP de milisegundos donde el propio overhead del modelo superaría cualquier ahorro. El propio diseño del experimento (medir contra 113 consultas de un benchmark específico de IMDb) tampoco garantiza que el modelo generalice a esquemas y distribuciones de datos distintos sin volver a entrenarse.

💭 Clave: La variante de GRPO del experimento existe porque medir el tiempo real de una consulta en Postgres varía entre corridas por la caché de páginas de Linux. Sin normalizar ese ruido, el algoritmo de RL terminaría reforzando planes que parecen rápidos por casualidad, no por diseño.

Lo que sí queda demostrado es el patrón general: cuando una tarea tiene una función de recompensa verificable y barata de calcular (en este caso, cronometrar una consulta SQL), un modelo relativamente pequeño puede superar a un sistema heurístico maduro con décadas de ingeniería detrás, siempre que se le dé suficiente señal de refuerzo. Es el mismo principio detrás de los modelos de razonamiento matemático entrenados con RL, aplicado a un problema de sistemas.

Qué sigue

El propio Bansal plantea el experimento como una prueba de concepto, no como un producto terminado. Los pasos lógicos siguientes incluyen ampliar el benchmark más allá de las 113 consultas de IMDb, medir el costo de inferencia del modelo contra el ahorro real en tiempo de ejecución para saber en qué punto deja de valer la pena, y explorar si el mismo enfoque de RL con recompensa verificable funciona para otras decisiones del optimizador, como la elección de índices o la configuración de work_mem por consulta.

📖 Resumen en Telegram: Ver resumen

Probalo vos: instalá pg_hint_plan en una base de prueba con datos de IMDb y compará el plan por defecto contra un hint manual con EXPLAIN (ANALYZE, BUFFERS) para ver la diferencia con tus propios ojos.

Preguntas frecuentes

¿Este modelo reemplaza al optimizador de Postgres?

No. El experimento propone hints externos vía pg_hint_plan; Postgres sigue siendo el motor que ejecuta la consulta y decide los detalles de bajo nivel de cada operador.

¿Por qué usar un modelo de solo 4B parámetros y no uno más grande?

Porque la tarea (elegir entre un conjunto acotado de hints estructurados) no requiere el conocimiento general de un modelo grande, y un modelo pequeño es más barato de correr por consulta si se busca usarlo en producción.

¿Qué es GRPO?

Group Relative Policy Optimization es una técnica de aprendizaje por refuerzo que compara varios rollouts generados para la misma entrada entre sí, en lugar de contra un modelo de valor separado. El autor construyó una variante propia para tolerar el ruido de medición de Postgres.

¿Por qué el ordenamiento de joins es tan difícil?

Porque es un problema NP-hard: el número de formas posibles de combinar varias tablas crece de forma combinatoria, y ningún optimizador puede evaluarlas todas en tiempo razonable.

¿Necesito modificar el código fuente de Postgres para probar esto?

No. Alcanza con instalar la extensión pg_hint_plan, que se carga como cualquier otra extensión de Postgres sin recompilar el motor.

¿Dónde puedo ver el código y los detalles completos del experimento?

El artículo original de Rohan Bansal, con el detalle completo del rig de entrenamiento y los resultados, está disponible en su blog personal.

Referencias

📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

Imagen destacada: Foto de Branko Stancevic en Unsplash


Andrés Morales

Desarrollador e investigador en inteligencia artificial. Escribe sobre modelos de lenguaje, frameworks, herramientas para devs y lanzamientos open source. Cubre papers de ML, ecosistema de startups tech y tendencias de programación.

0 Comentarios

Deja un comentario

Marcador de posición del avatar

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.