Índices concurrentes de PostgreSQL: esperas y recuperación de construcciones fallidas
Un comando de creación concurrente de índices que está esperando y un índice no válido que queda tras un fallo requieren respuestas diferentes. Utiliza diagnósticos que tengan en cuenta la fase, comprobaciones del catálogo y el procedimiento de recuperación PACE para elegir una intervención y verificar el estado previsto de la migración.
Imagina un reintento hipotético de una migración que informa de que ya existe una relación. La migración continúa, pero nadie ha comprobado si el índice se puede utilizar. PostgreSQL puede dejar un índice no válido tras un fallo en la creación concurrente; IF NOT EXISTS no valida la definición existente. La documentación del comando explica ambos comportamientos.
Empieza por el proceso del servidor. ¿Sigue construyendo el índice o está esperando? Determina si la construcción ha terminado antes de plantearte la recuperación. Después, compara el índice real con la migración prevista. Un aviso de que el nombre ya existe es una comprobación insuficiente para dar el trabajo por terminado.
Esta guía aborda tablas de aplicación ordinarias, sin particiones. Las plantillas SQL se han revisado contrastándolas con las fuentes, pero no se han ejecutado. Adapta los nombres hipotéticos de public.orders y comprueba la base de datos y los privilegios necesarios. Consulta la documentación de la versión principal desplegada y ensaya los cambios en un entorno aislado.
Qué cambia CREATE INDEX CONCURRENTLY y qué no
La compatibilidad con las escrituras no implica ausencia de bloqueos ni de esperas
Un CREATE INDEX ordinario bloquea las escrituras en la tabla y permite las lecturas habituales. CREATE INDEX CONCURRENTLY permite las escrituras normales durante la construcción, pero sigue necesitando coordinación y esperas. Solo puede ejecutarse una construcción concurrente a la vez en una misma tabla. Debe ejecutarse fuera de un bloque de transacción. PostgreSQL documenta estas restricciones.
Dos recorridos, esperas de transacciones y la transición a la validez
La creación concurrente registra una entrada no válida en el catálogo antes de realizar dos recorridos de la tabla en transacciones separadas. Antes de los recorridos espera a las transacciones de modificación pertinentes y, después del segundo recorrido, a las instantáneas más antiguas. Esa entrada no válida puede pertenecer a una construcción que avanza correctamente. No la modifiques hasta saber lo contrario. Los recorridos y las esperas adicionales también pueden hacer que la creación concurrente tarde más. Consulta la secuencia de construcción.
Diagnostica las esperas de construcción de índices de PostgreSQL por fase y proceso del servidor
Consulta pg_stat_progress_create_index antes de estimar el progreso
Conéctate a la base de datos de destino. Esta plantilla de solo lectura filtra por tabla, conserva los identificadores originales y convierte en null los identificadores no disponibles cuyo valor es cero:
SELECT p.pid, p.command, p.phase, p.relid,
NULLIF(p.index_relid, 0) AS index_relid,
p.lockers_total, p.lockers_done,
NULLIF(p.current_locker_pid, 0) AS current_locker_pid,
p.blocks_total, p.blocks_done
FROM pg_stat_progress_create_index AS p
WHERE p.datname = current_database()
AND p.relid = to_regclass('public.orders');Si el objeto de destino no existe, se obtiene null y no hay coincidencias. Verifica el identificador antes de interpretar un resultado vacío. Consulta la documentación de to_regclass.
Interpreta los contadores en el contexto de la fase indicada. Las definiciones de progreso de PostgreSQL distinguen los siguientes trabajos.
| Fase indicada | Siguientes indicios que deben inspeccionarse | Límites de la interpretación |
|---|---|---|
waiting for writers before build | Recuentos de procesos que mantienen bloqueos, proceso bloqueador actual y responsable de la transacción | No es un porcentaje de recorrido de la tabla |
building index y sus subfases | Contadores de bloques o tuplas en observaciones sucesivas y telemetría de recursos | La información disponible depende del método de acceso |
waiting for writers before validation | Escritores pertinentes y sus transacciones | Una espera de coordinación posterior, que no implica necesariamente un fallo |
index validation: scanning index, sorting tuples o scanning table | Nombre de la fase y contadores disponibles | Los recorridos del índice y de la tabla miden trabajos diferentes |
waiting for old snapshots | Proceso bloqueador actual y actividad relacionada con las instantáneas | Investiga el contexto de las transacciones antes de intervenir |
En la fila de validación anterior, "sorting tuples" y "scanning table" también llevan el prefijo de fase completo "index validation:". La reindexación concurrente puede indicar "waiting for readers before marking dead" y "waiting for readers before dropping"; la misma referencia de progreso describe esas esperas.
Los totales de bloques describen la fase actual. No permiten determinar el progreso de principio a fin, y los totales con valor cero o no disponibles no permiten estimar un porcentaje.
Correlaciona el proceso del servidor que espera con los indicios de transacciones y bloqueos
Inspecciona los procesos de construcción junto con los procesos bloqueadores actuales indicados mediante esta otra plantilla de solo lectura.
WITH progress AS (
SELECT pid, current_locker_pid
FROM pg_stat_progress_create_index
WHERE datname = current_database()
AND relid = to_regclass('public.orders')
), participants AS (
SELECT pid FROM progress
UNION
SELECT current_locker_pid FROM progress
WHERE current_locker_pid > 0
)
SELECT a.pid, a.state, a.xact_start, a.backend_xmin,
a.wait_event_type, a.wait_event,
pg_blocking_pids(a.pid) AS lock_blocking_pids
FROM pg_stat_activity AS a
JOIN participants AS t ON t.pid = a.pid;pg_blocking_pids informa de los procesos que causan bloqueos, pero no puede explicar todas las demoras relacionadas con las instantáneas. Un array vacío no descarta una espera. Del mismo modo, un xact_start o un backend_xmin antiguo requiere investigación; no identifica una sesión que deba terminarse. Coordínate con el responsable de la transacción. Esta consulta incluye el proceso de construcción y el proceso bloqueador actual indicado, no un árbol recursivo de bloqueadores. Investiga por separado los PID de los bloqueadores devueltos. La función puede devolver cero para un bloqueo causado por una transacción preparada; cero no es un proceso del servidor que deba terminarse. Consulta la misma referencia de la función.
Como comparación fuera de PostgreSQL, la guía de Optijara sobre observabilidad de la construcción en TensorRT examina cómo distinguir un trabajo prolongado de un fallo.
Interpreta con cautela las filas ausentes y la visibilidad parcial
La ausencia de una fila de progreso no permite concluir nada. Comprueba el PID del proceso del servidor que hayas conservado y el resultado del cliente; después, correlaciona los errores del servidor con el estado del catálogo. Confirma la selección de la base de datos y los permisos del rol de supervisión. Los detalles de otras sesiones pueden estar restringidos. Las transacciones de supervisión prolongadas también pueden dejar las observaciones desactualizadas; repítelas fuera de esa transacción. La documentación de supervisión de actividad explica ambas limitaciones.
Inspecciona los índices no válidos antes de reintentar con IF NOT EXISTS
Comprueba indisvalid, indisready, indislive y la definición real
Inspecciona el índice con ese nombre y la tabla a la que realmente pertenece. Un registro de migraciones no permite determinar ninguna de las dos cosas. Las uniones siguientes se basan en el catálogo de relaciones y el catálogo de espacios de nombres documentados.
SELECT ns.nspname AS index_schema, ix.relname AS index_name,
tn.nspname AS table_schema, t.relname AS table_name,
i.indisunique, i.indisvalid, i.indisready, i.indislive,
pg_get_indexdef(i.indexrelid) AS definition
FROM pg_index AS i
JOIN pg_class AS ix ON ix.oid = i.indexrelid
JOIN pg_namespace AS ns ON ns.oid = ix.relnamespace
JOIN pg_class AS t ON t.oid = i.indrelid
JOIN pg_namespace AS tn ON tn.oid = t.relnamespace
WHERE ns.nspname = 'public'
AND ix.relname = 'orders_customer_id_idx';Compara la definición con la migración aprobada, incluidas la unicidad y el método de acceso. Comprueba el orden de las claves, las expresiones, las columnas incluidas y el predicado. pg_get_indexdef reconstruye esa definición.
En pg_index, indisvalid describe la validez para las consultas. indisready indica si el índice está preparado para mantenerse durante las inserciones y actualizaciones; indislive registra si sigue activo. Un indicador de actividad falso significa que el índice se está eliminando y debe ignorarse a todos los efectos. Un índice no válido puede seguir añadiendo sobrecarga a las escrituras. El indicador de no validez, por sí solo, no identifica una construcción concurrente fallida.
La construcción de índices únicos requiere especial cuidado. La aplicación de la unicidad frente a otras transacciones comienza cuando se inicia el segundo recorrido de la tabla, antes de que el índice sea válido. Un fallo durante ese recorrido puede dejar un índice no válido que siga rechazando escrituras duplicadas. El indicador de no validez tampoco permite certificar que los datos existentes satisfagan la unicidad. Las advertencias sobre la construcción concurrente explican estos tiempos.
Por qué la idempotencia basada en el nombre no equivale a una migración correcta
IF NOT EXISTS suprime un error cuando ya existe un nombre de relación. No reconstruye un índice no válido ni garantiza una definición equivalente. La incidencia #52583 de Rails describe la trampa de los tiempos de espera agotados y los reintentos que se deriva de ello. Ese informe público no demuestra que Rails disponga actualmente de una corrección. La referencia del comando de PostgreSQL define el comportamiento subyacente.
El procedimiento de recuperación PACE: fase, evaluación, elección y comprobación de la disponibilidad
Fase y evaluación: recopila indicios antes de cambiar el estado
PACE es un marco editorial que se presenta aquí. Antes de cambiar el estado, guarda el error original y la fase actual. Conserva la identidad y la definición del índice, junto con el contexto del responsable de la transacción.
Antes de eliminar el índice con ese nombre, comprueba si alguna restricción lo utiliza.
SELECT conname, contype, conrelid::regclass AS owning_table
FROM pg_constraint
WHERE conindid = to_regclass('public.orders_customer_id_idx');pg_constraint.conindid identifica los índices que respaldan restricciones. Un resultado vacío deja pendientes la investigación de otras dependencias y de la propiedad. Añadir CASCADE no sustituye esa revisión.
Elección: esperar, eliminar y volver a crear, o reindexar
Elige una vía a partir del estado constatado. Un bucle de reintentos incondicional omite esa decisión.
| Estado constatado | Siguiente acción aprobada | Precaución necesaria |
|---|---|---|
| Construcción activa con el progreso esperado | Observar | Comparar indicios de observaciones sucesivas teniendo en cuenta la fase |
| Construcción activa en espera | Investigar y coordinar la resolución | Confirmar la transacción pertinente y su responsable |
| Construcción independiente fallida con la definición prevista | Corregir la causa y, después, eliminar y volver a crear, o realizar una reindexación concurrente a nivel de índice si es apto | Constatar que la construcción ha terminado y que la recuperación está autorizada |
| Índice válido con una definición que no coincide | Diseñar una migración separada | No considerar el nombre existente como criterio de aceptación |
| Restos de una reindexación concurrente fallida | Seguir la vía documentada específica para esos restos | Inspeccionar la identidad, las dependencias y el índice válido que se conserva |
Utiliza la siguiente plantilla de eliminación y recreación, que no se ha ejecutado, solo cuando haya terminado la construcción fallida y las comprobaciones de dependencias permitan la eliminación. Presupone que la definición aprobada es exactamente un índice B-tree no único sobre public.orders(customer_id).
DROP INDEX CONCURRENTLY public.orders_customer_id_idx;
CREATE INDEX CONCURRENTLY orders_customer_id_idx
ON public.orders USING btree (customer_id);Envía comandos separados de nivel superior fuera de los bloques de transacción. Si la eliminación falla, detente e inspecciona de nuevo. CREATE INDEX no acepta un nombre de índice cualificado con un esquema; cualificar public.orders sitúa el índice en ese esquema. Comprueba las restricciones específicas de la creación y la eliminación concurrente.
Para un índice no válido que sea apto y cuya definición deba conservarse, la vía alternativa es la siguiente.
REINDEX INDEX CONCURRENTLY public.orders_customer_id_idx;Resuelve primero la causa. La reindexación no puede corregir datos duplicados ni cambiar una definición incorrecta, y no elimina la carga de trabajo que causa el bloqueo. Indica el índice explícitamente: solo la forma a nivel de índice puede reconstruir de manera concurrente un índice no válido. No la sustituyas por una reindexación concurrente de toda la tabla. Comprueba el proceso y las exclusiones.
Una reindexación concurrente fallida puede dejar un sustituto _ccnew no válido, que la vía documentada elimina antes de reintentar. En cambio, un _ccold no válido puede ser la copia antigua que queda tras una reconstrucción correcta. Puede haber sufijos numéricos. Verifica el índice válido que se conserva y las dependencias antes de aprobar la eliminación. Nunca elimines índices en bloque basándote en el sufijo. PostgreSQL documenta las distintas vías de limpieza.
Comprobación de la disponibilidad: verifica las condiciones finales previstas
Tras la recuperación, exige que el comando finalice correctamente y repite la consulta al catálogo. Comprueba que el esquema y la tabla coinciden con la definición prevista; confirma los indicadores de validez, preparación y actividad. La validez no obliga al planificador a seleccionar el índice, y las transacciones más antiguas pueden retrasar su uso. La guía de Optijara para decidir sobre las sondas de Kubernetes explora la diferencia más amplia entre la salud de un proceso y su disponibilidad para el uso.
Mantén un registro de observaciones en lugar de inventar un umbral universal de tiempo transcurrido.
| Medición | Momento de captura | Decisión que permite fundamentar |
|---|---|---|
| Fase y contadores aplicables | Observaciones sucesivas durante la construcción | El trabajo avanza o requiere investigación |
| PID del proceso bloqueador, contexto de la transacción y eventos de espera | Durante una espera | Resolución coordinada con el responsable |
| Resultado del comando, definición e indicadores del catálogo | Antes de la recuperación y después de finalizar | Se ha alcanzado o no la condición final prevista |
| CPU/E/S y latencia y errores de la aplicación | Antes, durante y después de la operación | Impacto en la carga de trabajo que requiere una respuesta operativa |
Estas son mediciones propuestas, no resultados de pruebas de rendimiento. El JSON siguiente resume el marco y la regla para dar el trabajo por terminado.
{
"framework": "pace",
"layers": ["phase", "assess", "choose", "establish_readiness"],
"completionRequires": ["successful_command", "intended_definition", "valid_ready_live"],
"automaticDestructiveRetry": false
}Errores de los equipos al reintentar migraciones
Confundir un aviso de reintento correcto con un índice válido
Un aviso de reintento responde a una pregunta más limitada que la que necesita resolver la migración. Comprueba el catálogo y la definición por separado. Mantén esa comprobación de aceptación en los flujos de trabajo de CI/CD asistidos por IA, incluidas las migraciones propuestas por automatización.
Reintentar antes de corregir la causa de la construcción fallida
Otro intento no resuelve los datos duplicados ni una transacción abierta. Conserva el error inicial para que la recuperación aborde el fallo que realmente observaste. Utiliza la siguiente lista de comprobación antes de continuar.
| Punto de control | Indicios conservados |
|---|---|
| Registrar la intención | Esquema, tabla y definición completa del índice |
| Configurar la ejecución | DDL concurrente fuera de las transacciones que lo engloban |
| Conservar el fallo | Error original y resultado del comando |
| Inspeccionar antes de reintentar | Fase, proceso del servidor, catálogo y dependencias |
| Aprobar la recuperación | Responsable identificado y vía apta |
| Aceptar la finalización | Comando ejecutado correctamente e índice coincidente, válido, preparado y activo |
Tratar la limpieza destructiva como idempotencia genérica
Mantén la limpieza destructiva fuera de la lógica genérica de reintentos. Exige aprobación humana y una nueva comprobación del estado antes de actuar sobre sesiones o índices no válidos. Las referencias de construcción concurrente y reindexación explican por qué los nombres y los indicadores intermedios no son motivos suficientes para eliminar un índice.
Advertencias: particiones, restricciones, acceso a la supervisión y coste operativo
Dónde termina el procedimiento para tablas ordinarias
La documentación citada no permite la creación concurrente sobre una tabla principal particionada. Construye de forma concurrente los índices de las particiones hoja y, después, planifica por separado el ensamblaje del índice principal. No des por hecho que esa operación sobre la tabla principal carece de bloqueos. PostgreSQL describe esta vía.
DROP INDEX CONCURRENTLY acepta un índice y prohíbe CASCADE. Debe ejecutarse fuera de un bloque de transacción. Excluye los índices de tablas particionadas y la eliminación directa de índices que respaldan restricciones UNIQUE o PRIMARY KEY. Comprueba si se permite la eliminación. La reindexación concurrente excluye los catálogos del sistema y los índices de restricciones de exclusión. Sus comprobaciones de aptitud son independientes.
Por qué la concurrencia implica un compromiso y no una garantía de rendimiento
Los recorridos adicionales consumen CPU y E/S. Reserva tiempo de ingeniería para los ensayos y la supervisión; después, define una ventana de observación y un plan de respuesta para la carga de trabajo. La documentación de construcción concurrente advierte sobre la carga operativa.
Protege el texto de las consultas y los detalles sensibles de la actividad al compartir indicios de un incidente. Ten en cuenta las restricciones de acceso a la supervisión. Estas plantillas no se ejecutaron en un entorno de prueba de PostgreSQL durante esta revisión. No se observó ninguna espera ni recuperación, y no se afirma ningún resultado de producción ni de pruebas de rendimiento. Ensaya la vía seleccionada antes de utilizarla en operaciones.
Puntos clave
- 1Consulta la fase de construcción indicada antes de interpretar el tiempo transcurrido o los contadores de bloques como progreso.
- 2Un índice no válido puede pertenecer a una construcción activa; constata el fallo antes de autorizar la limpieza.
- 3IF NOT EXISTS comprueba la existencia del nombre, no la equivalencia de la definición ni la validez del índice.
- 4Utiliza PACE para relacionar los indicios de la fase, la evaluación del catálogo, la recuperación aprobada y las comprobaciones de disponibilidad.
- 5La reindexación concurrente es una opción para índices aptos, no una solución para datos duplicados ni para una definición errónea.
- 6Verifica la finalización del comando y el índice previsto, válido, preparado y activo, respetando las advertencias sobre la visibilidad de las transacciones.
Conclusión
Empieza por la fase. Si la construcción ha fallado, determina qué queda y corrige la causa antes de elegir una vía de recuperación aprobada. Un aviso de reintento no permite certificar el índice. Exige que el comando finalice correctamente y verifica la definición prevista y el estado válido, preparado y activo, teniendo en cuenta los límites documentados de visibilidad de las transacciones. Si tu equipo quiere una segunda revisión de las medidas de protección de sus migraciones de base de datos, Optijara puede ayudar a evaluar el plan.
Preguntas frecuentes
¿Por qué está esperando CREATE INDEX CONCURRENTLY y evita todos los bloqueos?
Permite las escrituras normales en la tabla, pero sigue coordinándose con otras transacciones. Inspecciona `pg_stat_progress_create_index.phase`, el PID de construcción y el proceso bloqueador actual indicado. Un resultado vacío de `pg_blocking_pids` no descarta todas las esperas. Consulta los [informes de progreso](https://www.postgresql.org/docs/current/progress-reporting.html) y las [funciones de bloqueos](https://www.postgresql.org/docs/current/functions-info.html).
¿Cómo distingo una construcción activa de un índice no válido tras un fallo?
Correlaciona el progreso, la actividad del proceso del servidor, los errores del comando y el estado del catálogo. El estado no válido aparece durante la construcción, por lo que `indisvalid=false` por sí solo no demuestra un fallo ni autoriza la eliminación. Si no hay progreso visible, comprueba la selección de la base de datos, los permisos y el resultado del proceso del servidor. Consulta el [comportamiento de la construcción concurrente](https://www.postgresql.org/docs/current/sql-createindex.html) y la [visibilidad de la supervisión](https://www.postgresql.org/docs/current/monitoring-stats.html).
¿Reconstruye IF NOT EXISTS un índice de PostgreSQL no válido?
No. Suprime un error cuando ya existe un nombre de relación, sin validar la equivalencia ni reconstruir un índice no válido. Inspecciona la definición y el estado del catálogo, corrige la causa del fallo y elige una vía de recuperación aprobada. Consulta la [referencia de CREATE INDEX](https://www.postgresql.org/docs/current/sql-createindex.html).
¿Puedo recuperar un índice no válido sin eliminarlo primero?
Sí, `REINDEX INDEX CONCURRENTLY` puede reconstruir un índice no válido que sea apto y tenga la definición prevista. No puede corregir datos duplicados, una definición errónea ni la carga de trabajo que provoca la espera. Resuelve la causa, comprueba las restricciones y verifica las condiciones finales. Consulta [REINDEX](https://www.postgresql.org/docs/current/sql-reindex.html); [eliminar y volver a crear](https://www.postgresql.org/docs/current/sql-createindex.html) es otra opción documentada.
¿Puede un índice único no válido seguir rechazando escrituras duplicadas?
Sí. La aplicación de la unicidad frente a otras transacciones comienza cuando se inicia el segundo recorrido de la tabla, antes de que el índice sea válido. Si la construcción concurrente de un índice único falla durante el segundo recorrido, el índice no válido sigue imponiendo la unicidad después. El estado no válido no certifica que sea inocuo ni que todos los datos sean únicos. Consulta las [advertencias sobre la construcción concurrente de índices únicos](https://www.postgresql.org/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY).
Fuentes
- https://www.postgresql.org/docs/current/sql-createindex.html
- https://www.postgresql.org/docs/current/progress-reporting.html
- https://www.postgresql.org/docs/current/catalog-pg-index.html
- https://www.postgresql.org/docs/current/sql-dropindex.html
- https://www.postgresql.org/docs/current/sql-reindex.html
- https://www.postgresql.org/docs/current/monitoring-stats.html
- https://www.postgresql.org/docs/current/functions-info.html
- https://github.com/rails/rails/issues/52583
- https://www.postgresql.org/docs/current/catalog-pg-constraint.html
- https://www.postgresql.org/docs/current/catalog-pg-class.html
- https://www.postgresql.org/docs/current/catalog-pg-namespace.html
- https://pganalyze.com/blog/postgres-create-index
Escrito por
Hamza DiazHamza Diaz es el fundador de Optijara, donde crea agentes de IA prácticos, sistemas de automatización y flujos de trabajo de Copilot para empresas de servicios. Escribe sobre operaciones de IA, estrategia de agentes e implementación real para equipos que quieren sistemas útiles en lugar de promesas vacías.
