PostgreSQL 17: optimizaciones que cambian consultas reales

Logo redondo de PostgreSQL en azul, marca del gestor de base de datos cuyas optimizaciones de la version 17 trato en el articulo

PostgreSQL 17 salio el 26 de septiembre de 2024 y, como suele ocurrir, parte del contenido serio de la version esta en mejoras silenciosas del planificador y la ejecucion, no en las funciones vistosas del anuncio. Seis meses despues en produccion ya puedo contar que optimizaciones han cambiado consultas reales en bases de datos donde trabajo. Este post se centra en las mejoras que afectan planes de consulta, no en las de administracion o backup.

El contexto: un par de bases de datos con entre 200 y 800 GB, cargas mixtas OLTP y reporting. Migre de 16 a 17 a finales de octubre, despues de dejar pasar tres semanas desde la salida para evitar el sobresalto tipico del mes de estreno.

Escaneos SAOP: lo que en otros motores llaman skip scan

La mejora mas util que he visto en consultas reales es el cambio en los escaneos de operadores de array escalar (SAOP por sus siglas en ingles: scalar array operator). Los SAOP son los que aparecen en consultas con WHERE columna IN (lista) o WHERE columna = ANY (array).

En PostgreSQL 16 y anteriores, cuando se usaba un indice B-tree para este tipo de consulta, el planificador ejecutaba un escaneo separado por cada valor de la lista. Si pedias WHERE usuario_id IN (1,2,3,4,5,6,7,8,9,10), eran diez accesos al indice aunque los datos estuvieran contiguos. Funcionaba pero era mucho mas trabajo del necesario.

PostgreSQL 17 combina esos valores en un unico escaneo del indice cuando la distribucion de valores lo permite. El planificador reconoce el patron y emite una sola pasada sobre el indice que salta entre rangos de valores sin volver a empezar. En nuestras consultas de usuario con listas de 20 a 100 IDs la mejora de latencia p99 es del 30 al 50 por ciento, y la reduccion de CPU del nodo de lectura es tambien notable bajo carga.

Es un cambio que no requiere tocar nada en la aplicacion. Funciona solo en cuanto el planificador detecta el patron. Ver los planes en EXPLAIN ANALYZE ayuda a confirmar que el numero de accesos al indice ha caido.

Streaming I/O: mejoras en escaneos secuenciales y ANALYZE

La otra mejora profunda es un cambio en la capa de lectura llamado streaming I/O. En versiones anteriores, cuando un escaneo secuencial o un ANALYZE necesitaba leer bloques del disco, lo hacia uno a uno. PostgreSQL 17 introduce un API interno que permite al motor anunciar que va a leer varios bloques seguidos, lo que el kernel aprovecha para hacer prefetch mas agresivo.

El impacto real depende mucho de la carga. En reporting con escaneos secuenciales grandes sobre tablas frias la mejora es del 15 al 25 por ciento menos de tiempo de pared, sobre todo cuando los datos no estan en cache. En ANALYZE, que es el que mas nos ha llamado la atencion, el tiempo para reestadisticas de tablas grandes cae entre un 20 y un 40 por ciento. Esto es importante porque ANALYZE es una tarea pesada que solemos ejecutar en ventanas nocturnas.

La mejora en consultas OLTP calientes es mas modesta, porque alli los bloques suelen estar en memoria. Pero para cargas de mantenimiento y reporting es uno de los motivos solidos para actualizar.

NOT IN y NOT EXISTS como anti-joins

Otro cambio del planificador que he visto mover el suelo es la mejora en el manejo de subconsultas correlacionadas con NOT IN o NOT EXISTS. En versiones anteriores el planificador tenia dificultades para transformarlas en anti-joins eficientes y a menudo acababa ejecutando bucles anidados con coste O(n x m). En 17 el planificador transforma mas casos automaticamente en anti-joins hash o merge cuando la estadistica lo justifica.

En una consulta de conciliacion que ejecutabamos a diario, una SELECT ... WHERE id NOT IN (SELECT id FROM otra_tabla WHERE condicion) que tardaba 18 segundos bajo a 2,5 despues de la migracion. El EXPLAIN muestra un anti-join donde antes habia un nested loop. No cambie la SQL.

Esta mejora afecta a muchas consultas tipicas de reporting historico o de diferencia entre dos conjuntos. Vale la pena revisar planes despues de migrar para ver cuales han cambiado.

MERGE con RETURNING y cobertura ampliada

MERGE aparecio en PostgreSQL 15 pero tenia limitaciones molestas. 17 anade dos cosas utiles.

La primera es la clausula RETURNING. Ahora un MERGE puede devolver las filas afectadas con informacion sobre que accion se aplico (INSERT, UPDATE o DELETE) mediante la funcion merge_action(). Esto simplifica casos de upsert donde la aplicacion necesita saber si la fila existia previamente o no. Antes habia que hacer roundtrip extra o usar trucos con triggers.

La segunda es la clausula WHEN NOT MATCHED BY SOURCE, que permite definir que hacer con filas del destino que no encuentran equivalente en la fuente (tipicamente borrarlas o marcarlas). Esto hace que MERGE cubra el patron completo de sincronizacion bidireccional que antes exigia varios pasos.

En mi caso hemos reescrito una sincronizacion de catalogo que ejecutamos cada hora, pasando de tres statements (INSERT, UPDATE, DELETE) a un unico MERGE. La logica es mas clara y el rendimiento marginalmente mejor, pero lo que mas ha ganado es mantenibilidad.

Gestion de memoria en VACUUM

VACUUM no es optimizacion de consultas pero afecta al rendimiento observable. PostgreSQL 17 introduce una estructura interna nueva (TidStore) para almacenar tuplas muertas durante VACUUM. La diferencia practica es que el limite de maintenance_work_mem deja de ser el cuello de botella que era antes: con TidStore las pasadas sobre indice se reducen y el uso de memoria es mas eficiente, lo que permite dejar maintenance_work_mem en valores moderados sin perder rendimiento.

En tablas grandes he visto caidas del tiempo de VACUUM del 20 al 35 por ciento con la misma configuracion de memoria. Esto importa mas en sistemas con actualizaciones intensivas donde el bloat acumulado es un problema recurrente.

Logical replication con failover

El ultimo cambio relevante que quiero senalar es la mejora en logical replication. En 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 logical replication para migraciones en caliente o para pipelines de datos es un cambio estructural que hace practicas cosas que antes eran dolorosas.

Como pensar la migracion

Mi recomendacion despues de seis meses es que PostgreSQL 17 es una actualizacion valiosa para cargas mixtas con escaneos de indice grandes, reporting, MERGE y logical replication. Si tu carga es OLTP pura con consultas muy simples, la mejora sera mas modesta pero no hay regresiones notables que justifiquen quedarse.

El cambio mas util que he sacado sin tocar aplicacion ha sido la mejora en SAOP y en NOT IN. Ambos cambian planes de consultas que llevan anos escritas sin modificacion. Revisa los EXPLAIN de tus top 10 consultas mas lentas despues de migrar: hay bastantes probabilidades de que alguna haya cambiado de plan a mejor y de que puedas cerrar un ticket de rendimiento abierto sin refactorizar nada.

Entradas relacionadas