PostgreSQL 17: optimizaciones que cambian consultas reales
Actualizado: 2026-05-03
PostgreSQL 17 salió el 26 de septiembre de 2024 y, como suele ocurrir, la parte seria del release está en mejoras silenciosas del planificador y la ejecución. Seis meses en producción ya permiten evaluar qué optimizaciones han cambiado consultas reales en bases de datos con carga mixta OLTP y reporting. Este post se centra en los cambios que afectan planes de consulta, no en administración ni backup.
Puntos clave
- Los escaneos SAOP combinan valores de
IN (lista)en una sola pasada sobre el índice, con mejoras del 30–50 % en latencia p99 para listas de 20–100 IDs. - El streaming I/O reduce entre un 15 y un 40 % el tiempo de escaneos secuenciales fríos y de
ANALYZEsobre tablas grandes. NOT INyNOT EXISTSse transforman automáticamente en anti-joins hash o merge cuando las estadísticas lo justifican.MERGEgana la cláusulaRETURNINGyWHEN NOT MATCHED BY SOURCE, completando el patrón de sincronización bidireccional.- TidStore reduce el tiempo de VACUUM entre un 20 y un 35 % con la misma configuración de memoria.
- La replicación lógica soporta failover sin resincronización completa.
Escaneos SAOP: lo que otros motores llaman skip scan
La mejora más útil que he visto en consultas reales es el cambio en los escaneos de operadores de array escalar (SAOP). Los SAOP aparecen en consultas con WHERE columna IN (lista) o WHERE columna = ANY (array).
En PostgreSQL 16 y anteriores, el planificador ejecutaba un escaneo separado por cada valor de la lista. Con WHERE usuario_id IN (1,2,3,...,10), eran diez accesos al índice aunque los datos estuvieran contiguos.
PostgreSQL 17 combina esos valores en un único escaneo del índice cuando la distribución lo permite. El planificador reconoce el patrón y emite una sola pasada que salta entre rangos de valores sin reiniciar. En consultas con listas de 20 a 100 IDs, la mejora de latencia p99 es del 30 al 50 %, y la reducción de CPU del nodo de lectura es notable bajo carga.
Este cambio no requiere tocar nada en la aplicación. Revisar EXPLAIN ANALYZE confirma que el número de accesos al índice ha caído. Conecta bien con las prácticas de observabilidad de base de datos que describimos al hablar de FinOps de infraestructura IA.
Streaming I/O: escaneos secuenciales y ANALYZE
La otra mejora profunda es un cambio en la capa de lectura. En versiones anteriores, los escaneos secuenciales leían bloques uno a uno. PostgreSQL 17 introduce una API interna que permite al motor anunciar que va a leer varios bloques seguidos, lo que el kernel aprovecha para hacer prefetch más agresivo.
El impacto real depende de la carga:
- En reporting con escaneos secuenciales grandes sobre tablas frías: 15–25 % menos de tiempo de pared.
- En
ANALYZEsobre tablas grandes: caída del 20 al 40 % en tiempo de reestadísticas. - En consultas OLTP calientes (datos en cache): mejora más modesta.
La mejora en ANALYZE es especialmente valiosa porque es una tarea pesada que suele ejecutarse en ventanas nocturnas.
NOT IN y NOT EXISTS como anti-joins
Otro cambio que ha movido el suelo es la mejora en subconsultas correlacionadas con NOT IN o NOT EXISTS. En versiones anteriores el planificador a menudo acababa ejecutando bucles anidados con coste O(n × m). En PostgreSQL 17, más casos se transforman automáticamente en anti-joins hash o merge cuando las estadísticas lo justifican.
En una consulta de conciliación diaria, un SELECT ... WHERE id NOT IN (SELECT id FROM otra_tabla WHERE condicion) que tardaba 18 segundos bajó a 2,5 segundos tras la migración. El EXPLAIN muestra un anti-join donde antes había un nested loop. No se cambió la SQL.
Esta mejora afecta a muchas consultas típicas de reporting histórico o de diferencia entre conjuntos. Merece la pena revisar los planes después de migrar.
MERGE con RETURNING y cobertura ampliada
MERGE apareció en PostgreSQL 15 pero tenía limitaciones molestas. La versión 17 añade dos cosas útiles:
- Cláusula RETURNING. Un
MERGEpuede devolver las filas afectadas con información sobre qué acción se aplicó (INSERT, UPDATE o DELETE) mediante la funciónmerge_action(). Simplifica casos de upsert donde la aplicación necesita saber si la fila existía previamente. - Cláusula WHEN NOT MATCHED BY SOURCE. Permite definir qué hacer con filas del destino que no encuentran equivalente en la fuente (tipicamente borrarlas o marcarlas). Esto cubre el patrón completo de sincronización bidireccional que antes exigía varios pasos.
En la práctica, hemos reescrito una sincronización de catálogo que se ejecutaba cada hora, pasando de tres statements (INSERT, UPDATE, DELETE) a un único MERGE. La lógica es más clara y el mantenimiento notablemente mejor.
Gestión de memoria en VACUUM
PostgreSQL 17 introduce una estructura interna nueva (TidStore) para almacenar tuplas muertas durante VACUUM. El límite de maintenance_work_mem deja de ser el cuello de botella que era antes: con TidStore las pasadas sobre índice se reducen y el uso de memoria es más eficiente.
En tablas grandes, la caída del tiempo de VACUUM es del 20 al 35 % con la misma configuración de memoria. Esto importa en sistemas con actualizaciones intensivas donde el bloat acumulado es un problema recurrente. Relacionado con la gestión operativa en Kubernetes 1.32, donde el control de recursos también impacta directamente en la fiabilidad del sistema.
Replicación lógica con failover
En PostgreSQL 17, un suscriptor puede continuar desde un nuevo publisher cuando el publisher original falla, sin necesidad de volver a sincronizar el contenido completo. Para equipos que usan replicación lógica para migraciones en caliente o pipelines de datos, es un cambio estructural que hace prácticas cosas que antes eran dolorosas.
Cómo pensar la migración
PostgreSQL 17 es una actualización valiosa para cargas mixtas con:
- Escaneos de índice grandes (SAOP)
- Reporting histórico o de diferencia de conjuntos (NOT IN / anti-joins)
- Operaciones de sincronización (MERGE)
- Mantenimiento en tablas grandes (VACUUM / ANALYZE / replicación lógica)
Si la carga es OLTP pura con consultas muy simples, la mejora será más modesta, pero no hay regresiones notables que justifiquen quedarse.
El cambio más útil obtenido sin tocar aplicación es la mejora en SAOP y en NOT IN. Ambos cambian planes de consultas que llevan años escritas sin modificación. Revisa los EXPLAIN de tus diez consultas más lentas después de migrar: hay bastantes probabilidades de que alguna haya cambiado de plan y puedas cerrar un ticket de rendimiento sin refactorizar nada.