Búsqueda de texto completo en PostgreSQL: bien hecha, es rápida (desmintiendo el mito de que es lenta)
(blog.vectorchord.ai)- Existe la percepción de que el Full-Text Search (FTS) nativo de PostgreSQL es lento, pero con la optimización adecuada puede funcionar muy rápido
- En el blog de Neon se compara la extensión
pg_search, basada en Rust, con el FTS nativo, y se afirma que este último es lento - Sin embargo, es muy probable que esa comparación se haya hecho omitiendo optimizaciones básicas indispensables para el FTS de PostgreSQL
- Este artículo demuestra con cifras que incluso aplicando optimizaciones simples a la configuración base del FTS se puede lograr una mejora de 50 veces en el rendimiento
Resumen de la configuración del benchmark
- Se realizó una prueba basada en una tabla con 10 millones de registros de logs
CREATE TABLE benchmark_logs ( id SERIAL PRIMARY KEY, message TEXT, country VARCHAR(255), severity INTEGER, timestamp TIMESTAMP, metadata JSONB ); - Estructura de la consulta problemática:
SELECT country, COUNT(*) FROM benchmark_logs WHERE to_tsvector('english', message) @@ to_tsquery('english', 'research') GROUP BY country ORDER BY country;- Ejecutar
to_tsvector()dentro de la consulta → muy ineficiente - Incluso si hay un índice GIN, no se aprovecha correctamente
- Ejecutar
Entorno de pruebas (réplica de la configuración base)
- Instancia EC2 i7ie.xlarge, usando SSD NVMe local
- 4 vCPUs, PostgreSQL 16 (Docker)
- Configuración principal de PostgreSQL:
-c shared_buffers=8GB -c maintenance_work_mem=8GB -c max_parallel_workers=4 -c max_worker_processes=4 - Límite de paralelismo:
max_parallel_workers_per_gather = 2(Neon usa 8)
Factor 1 de degradación: cálculo de tsvector en tiempo real
- Al ejecutar
to_tsvector()dentro de la consulta: - Se realizan cada vez el parsing de texto, análisis morfológico, etc.
- No se puede aprovechar el índice en absoluto
-
Solución: crear e indexar previamente una columna
tsvector- 1. Agregar la columna
tsvector
ALTER TABLE benchmark_logs ADD COLUMN message_tsvector tsvector;- 2. Llenar los datos
UPDATE benchmark_logs SET message_tsvector = to_tsvector('english', message); - 3. Crear el índice (desactivando
fastupdate)CREATE INDEX idx_gin_logs_message_tsvector ON benchmark_logs USING GIN (message_tsvector) WITH (fastupdate = off); - 4. Modificar la consulta
SELECT country, COUNT(*) FROM benchmark_logs WHERE message_tsvector @@ to_tsquery('english', 'research') GROUP BY country ORDER BY country;
- 1. Agregar la columna
Factor 2 de degradación: fastupdate=on en el índice GIN
fastupdate=onfavorece el rendimiento de escritura, pero perjudica el rendimiento de búsqueda- Para datasets de solo lectura o centrados en búsquedas,
fastupdate=offes indispensable - El índice es más pequeño y más rápido, y no requiere procesar la pending list
-
Cómo crear un índice GIN optimizado
CREATE INDEX idx_gin_logs_message_tsvector ON benchmark_logs USING GIN (message_tsvector) WITH (fastupdate = off);
Mejora de rendimiento: más de 50 veces
- Antes de optimizar: aprox. 41.3 segundos (41,301 ms)
- Después de optimizar: aprox. 0.88 segundos (877 ms)
- Muestra una mejora de rendimiento de alrededor de 50 veces
- Es posible alcanzar este rendimiento incluso en entornos con poco paralelismo
El rendimiento de ts_rank sí puede ser realmente lento
ts_rankots_rank_cdpueden ser relativamente lentos porque evalúan todos los resultados antes de ordenarlos- Especialmente al manejar grandes volúmenes de resultados, la carga de CPU/IO es alta
Funciones avanzadas de ranking: extensión VectorChord-BM25
- Si la precisión y la velocidad del ordenamiento son importantes, puede ser más efectivo usar una extensión dedicada
- VectorChord-BM25 es una extensión para PostgreSQL que ofrece funciones de ranking basadas en el algoritmo BM25
- Incluso hay reportes de que es 3 veces más rápido que Elasticsearch
Ventajas de VectorChord-BM25
- Algoritmo BM25: un algoritmo de ranking de búsqueda más avanzado que TF-IDF
- Formato de índice dedicado: optimizado para búsquedas de alta velocidad como Block WeakAnd
- Proporciona el tipo
bm25vector: almacena representaciones tokenizadas - Mejora tanto la precisión como la velocidad de búsqueda
Conclusión: el FTS nativo de PostgreSQL también es suficientemente rápido
- Si se usan una columna
tsvectory un índice GIN adecuado (fastupdate=off), es posible lograr búsquedas muy rápidas incluso con el FTS nativo - Las comparaciones de rendimiento deben hacerse sobre una base optimizada
- Si se necesitan funciones avanzadas de ranking, vale la pena considerar herramientas de extensión como VectorChord-BM25
- Mensaje clave: no es que la herramienta sea lenta, sino que la configuración puede ser el problema
3 comentarios
Gracias a eso, optimizamos la consulta.
Las opiniones en Hacker News dan miedo... "¿Diez millones? ¿Es una broma?"
Comentarios en Hacker News
Como mantenedor de pg_search, según la documentación de Postgres, tanto la estrategia del artículo de Neon/ParadeDB como la usada aquí se presentan como alternativas válidas
Calcular
tsvectoren tiempo real es un gran errorNo entiendo la tendencia de querer meter todo en Postgres
Me alegra ver más implementaciones de búsqueda de texto completo nativas de Postgres
Sin el plan de ejecución es difícil entender qué está pasando
tsvectorsolo se aplica a las coincidencias, y como la consulta del benchmark usaLIMIT 10, hay poca reverificaciónHace algunos años quise usar FTS nativo, pero fracasé
Empaqueté extensiones RPM/DEB para pg_search y vchord_bm25
He visto a muchos equipos saltar directamente a Elasticsearch o Meilisearch
10 millones de registros es un dataset de juguete
Usé por primera vez el texto completo de pg alrededor de 2008