Índices GIN para FTS
Los índices GIN hacen que tsvector @@ tsquery sea rápido a escala. Crea sobre una columna de vector almacenada, ajusta fastupdate y gin_pending_list_limit, y planifica el mantenimiento después de importaciones masivas.
Receta
-- Preferido: indexar columna tsvector almacenada
CREATE INDEX CONCURRENTLY articles_fts_gin
ON articles USING gin (search_vector);
-- Verificar uso del índice
EXPLAIN (COSTS OFF)
SELECT id FROM articles
WHERE search_vector @@ plainto_tsquery('english', 'postgresql index');
-- Bitmap Index Scan on articles_fts_ginCuándo usar esto: La latencia de búsqueda excede tu SLO en escaneos secuenciales, o EXPLAIN muestra Seq Scan en filtros @@.
Ejemplo de Trabajo
-- Patrón de carga masiva con indexación diferida
BEGIN;
ALTER TABLE articles DROP CONSTRAINT IF EXISTS articles_pkey;
-- (usar tabla de staging UNLOGGED en pipelines reales)
TRUNCATE articles RESTART IDENTITY;
COPY articles (title, body) FROM '/tmp/articles.csv' CSV HEADER;
-- search_vector es GENERATED STORED; reconstruir índice después de la carga masiva
COMMIT;
-- Reconstruir GIN después de una importación grande (más rápido que incremental para algunas cargas de trabajo)
REINDEX INDEX CONCURRENTLY articles_fts_gin;
ANALYZE articles;
SELECT pg_size_pretty(pg_relation_size('articles_fts_gin')) AS gin_size;Lo que esto demuestra:
tsvectorgenerado se mantiene actualizado durante COPYREINDEX CONCURRENTLYevita bloqueos de escritura largos post-importaciónANALYZEactualiza estadísticas para la elección entre bitmap y seq scan- Monitoreo de tamaño previene sorpresas en disco
Análisis Profundo
GIN vs GiST para FTS
| Método de acceso | Tiempo de construcción | Velocidad de consulta | Costo de actualización | Elección típica para FTS |
|---|---|---|---|---|
| GIN | Más lento, más grande | Lecturas más rápidas | Mayor costo de inserción | Predeterminado para búsqueda |
| GiST | Construcción más rápida | Lecturas más lentas | Menor costo de inserción | Raro para FTS |
La documentación de PostgreSQL recomienda GIN para tsvector a menos que una carga de trabajo de micro-actualizaciones intensiva en escrituras demuestre que GiST es mejor en benchmarks.
Patrones de Definición de Índice
-- Columna única
CREATE INDEX ON docs USING gin (doc_tsv);
-- Índice parcial solo para contenido publicado
CREATE INDEX docs_published_fts ON docs USING gin (doc_tsv)
WHERE status = 'published';
-- Cargas de trabajo combinadas de jsonb + FTS: índices separados, no fusionarfastupdate y Lista Pendiente
-- fastupdate por índice (PostgreSQL 9.5+)
ALTER INDEX articles_fts_gin SET (fastupdate = on);
SHOW gin_pending_list_limit; -- configuración del clúster, predeterminada 4MBLa lista pendiente agrupa inserciones pequeñas antes de fusionarlas en el árbol GIN principal. Las importaciones grandes pueden beneficiarse de fastupdate = off durante la carga, y luego reindexar.
Comandos de Mantenimiento
-- Después de importación masiva o actualización de versión
REINDEX INDEX CONCURRENTLY articles_fts_gin;
-- Índices FTS de tabla completa
REINDEX TABLE CONCURRENTLY articles;
-- Verificación de hinchazón (simplificada)
SELECT indexrelid::regclass,
pg_size_pretty(pg_relation_size(indexrelid))
FROM pg_stat_user_indexes
WHERE indexrelid::regclass::text LIKE '%fts%';Interacción con Autovacuum
Las UPDATE intensivas en columnas de texto regeneran tsvector y agitan las páginas GIN. Ajusta autovacuum en tablas de búsqueda:
ALTER TABLE articles SET (
autovacuum_vacuum_scale_factor = 0.02,
autovacuum_analyze_scale_factor = 0.01
);Trampas Comunes
- Indexación de expresión sin garantía IMMUTABLE - Algunos wrappers de
to_tsvectorfallan la creación del índice. Solución: Columna generada almacenada o columna mantenida por trigger. - CREATE INDEX sin CONCURRENTLY - Bloquea escrituras en tablas grandes. Solución: Siempre usar
CONCURRENTLYen producción. - Se olvidó ANALYZE - El planificador elige seq scan a pesar del índice. Solución:
ANALYZEdespués de reindexar; verificarenable_bitmapscan. - Consultas con mucho OR omiten el índice - BitmapOr de múltiples escaneos GIN sigue estando bien; selectividad muy baja puede resultar en seq scan. Solución: Aumentar
random_page_costsolo después de medir. - Hinchazón de la lista pendiente - Inserciones frecuentes y pequeñas hinchan la lista pendiente. Solución:
VACUUMperiódico ogin_clean_pending_list()en versiones compatibles. - Reindexar durante horas pico -
REINDEX CONCURRENTLYtodavía consume I/O. Solución: Programar ventana de mantenimiento; limitar workers paralelos.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Índice GiST | Tasa de escritura extrema, corpus muy pequeño | La búsqueda intensiva en lectura domina |
| Sin índice (seq scan) | < 50k filas, búsqueda solo para administradores | Búsqueda orientada al usuario a escala |
| GIN particionado por rango de tiempo | Búsqueda de archivo de series temporales | Consultas uniformes en todo el historial |
| Índice de búsqueda externo | Corpus más allá de FTS de un solo nodo | Requisitos transaccionales fuertes |
Preguntas Frecuentes
¿Qué tan grande es un índice GIN?
A menudo 50-150% del tamaño de la columna tsvector dependiendo de la cardinalidad del lema. Medir con pg_relation_size.¿Fallos de CONCURRENTLY?
Deja un índice inválido. Eliminar el índice inválido e intentar de nuevo; verificar claves duplicadas en restricciones únicas durante la construcción.¿Múltiples columnas tsvector?
Índices GIN separados por columna a menos que las consultas siempre las combinen con OR (raro).¿Incluir columnas?
GIN no soporta INCLUDE. Almacenar campos de visualización en el heap o un btree que cubra el id.¿Replicación retrasada en REINDEX?
Las réplicas reconstruyen al reproducir o mediante reindexación manual; planificar mantenimiento coordinado.¿Confusión con jsonb_path_ops?
Eso es para la clase de operación GIN de jsonb, no para tsvector. Usar las operaciones tsvector predeterminadas.¿fillfactor?
Generalmente predeterminado 90. Reducirlo rara vez ayuda a GIN; reindexar soluciona la hinchazón mejor.¿Construcción de índice en paralelo?
PostgreSQL soporta CREATE INDEX en paralelo cuando max_parallel_maintenance_workers > 0.¿Monitoreo?
Rastrear idx_scan, idx_blks_read en pg_stat_user_indexes para el índice FTS.¿Eliminar durante migración?
Usar el patrón de recreación CONCURRENTLY en migraciones de expandir-contraer.Relacionado
- Conceptos Básicos de Búsqueda de Texto Completo - Resumen de FTS
- tsvector y tsquery - contenido del vector
- GIN y GiST - métodos de acceso
- REINDEX CONCURRENTLY - reconstrucciones en línea
- Mejores Prácticas de Búsqueda de Texto Completo - lista de verificación de importación masiva
Versiones de la pila: Esta página fue escrita para PostgreSQL 18.4 (estable 18, mantenimiento 17), pgvector 0.8+, PostGIS 3.5+, pgbouncer 1.x, y Patroni 3.x.