Optimizando ORDER BY en una consulta de búsqueda de texto completo

8

Tengo una gran mesa entitiescon ~ 15 millones de registros. Quiero encontrar las 5 filas superiores que coinciden con 'hockey' en su name.

Tengo un índice de texto completo name, que se utiliza:gin_ix_entity_full_text_search_name

Consulta:

 SELECT "entities".*,
         ts_rank(to_tsvector('english', "entities"."name"::text),
         to_tsquery('english', 'hockey'::text)) AS "rank0.48661998202865475"
    FROM "entities" 
         WHERE "entities"."place" = 'f' 
              AND (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'hockey'::text)) 
         ORDER BY "rank0.48661998202865475" DESC LIMIT 5

Duración 25,623 ms

Explicar el plan
1 Límite (costo = 12666.89..12666.89 filas = 5 ancho = 3116)
2 -> Ordenar (costo = 12666.89..12670.18 filas = 6571 ancho = 3116)
3 Clave de clasificación: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'hockey' '' :: tsquery))
4 -> Escaneo de montón de mapa de bits en entidades (costo = 124.06..12645.06 filas = 6571 ancho = 3116)
5 Vuelva a verificar Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)
6 Filtro: (NO lugar)
7 -> Escaneo de índice de mapa de bits en gin_ix_entity_full_text_search_name (costo = 0.00..123.74 filas = 6625 ancho = 0)
8 Índice Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)

No entiendo por qué verifica la condición del índice dos veces. (Plan de consulta paso 4 y 7). ¿Es por mi condición booleana ( not place)? Si es así, ¿debo agregarlo a mi índice para obtener una consulta muy rápida? ¿O la condición de clasificación lo hace lento?

EXPLAIN ANALYZE salida:

  Límite (costo = 4447.28..4447.29 filas = 5 ancho = 3116) (tiempo real = 18509.274..18509.282 filas = 5 bucles = 1)
  -> Ordenar (costo = 4447.28..4448.41 filas = 2248 ancho = 3116) (tiempo real = 18509.271..18509.273 filas = 5 bucles = 1)
         Clave de clasificación: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'test' '' :: tsquery))
         Método de clasificación: top-N heapsort Memoria: 19kB
     -> Escaneo de montón de mapa de bits en entidades (costo = 43.31..4439.82 filas = 2248 ancho = 3116) (tiempo real = 119.003..18491.408 filas = 2533 bucles = 1)
           Vuelva a verificar Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery)
           Filtro: (NO lugar)
           -> Escaneo de índice de mapa de bits en gin_ix_entity_full_text_search_name (costo = 0.00..43.20 filas = 2266 ancho = 0) (tiempo real = 74.093..74.093 filas = 2593 bucles = 1)
                 Índice Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery)
 Tiempo de ejecución total: 18509.381 ms

Aquí están mis parámetros de base de datos. Está alojado por Heroku, en los servicios de Amazon. Lo describen como tener 1,7 GB de RAM, 1 unidad de procesamiento y una base de datos de máximo 1 TB.

nombre | configuración actual
------------------------------ + ------------------- -------------------------------------------------- ------------------------------------
 version | PostgreSQL 9.0.7 en i486-pc-linux-gnu, compilado por GCC gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3, 32 bits
 comando_archivo | prueba -f /etc/postgresql/9.0/main/wal-ed/ARCHIVING_OFF || envdir /etc/postgresql/9.0/resource29857_heroku_com/wal-ed/env wal-e wal-push% p
 archive_mode | en
 archive_timeout | 1 minuto
 checkpoint_completion_target | 0.7
 checkpoint_segments | 40
 client_min_messages | aviso
 cpu_index_tuple_cost | 0.001
 cpu_operator_cost | 0,0005
 cpu_tuple_cost | 0.003
 eficaz_caché_tamaño | 1530000kB
 hot_standby | en
 lc_collate | en_US.UTF-8
 lc_ctype | en_US.UTF-8
 listen_addresses | * *
 log_checkpoints | en
 log_destination | syslog
 log_line_prefix | % u [AMARILLO]
 log_min_duration_statement | 50ms
 log_min_messages | aviso
 logging_collector | en
 mantenimiento_trabajo_mem | 64MB
 max_ connections | 500
 max_prepared_transactions | 500
 max_stack_depth | 2MB
 max_standby_archive_delay | -1
 max_standby_streaming_delay | -1
 max_wal_senders | 10
 puerto | 
 random_page_cost | 2
 codificación_servidor | UTF8
 shared_buffers | 415MB
 ssl | en
 syslog_ident | resource29857_heroku_com
 TimeZone | UTC
 wal_buffers | 8MB
 wal_keep_segments | 127
 wal_level | hot_standby
 work_mem | 100MB
 (39 filas)

EDITAR

Parece que ORDER BYes la parte lenta:

d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
     ts_rank(to_tsvector('english', "entities"."name"::text),
     to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities" 
     WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text)) 
     LIMIT 5;

QUERY PLAN                                                                         
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=43.31..53.07 rows=5 width=24) (actual time=76.583..103.623 rows=5 loops=1)
->  Bitmap Heap Scan on entities  (cost=43.31..4467.60 rows=2266 width=24) (actual time=76.581..103.613 rows=5 loops=1)
     Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
     ->  Bitmap Index Scan on gin_ix_entity_full_text_search_name  (cost=0.00..43.20 rows=2266 width=0) (actual time=53.592..53.592 rows=1495 loops=1)
           Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
 Total runtime: 103.680 ms

Vs. con ORDER BY:

d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
     ts_rank(to_tsvector('english', "entities"."name"::text),
     to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities" 
     WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text)) 
     ORDER BY "rank0.48661998202865475" DESC
     LIMIT 5;

QUERY PLAN                                                                           
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit  (cost=4475.12..4475.13 rows=5 width=24) (actual time=15013.735..15013.741 rows=5 loops=1)
->  Sort  (cost=4475.12..4476.26 rows=2266 width=24) (actual time=15013.732..15013.735 rows=5 loops=1)
     Sort Key: (ts_rank(to_tsvector('english'::regconfig, (name)::text), '''banana'''::tsquery))
     Sort Method:  top-N heapsort  Memory: 17kB
     ->  Bitmap Heap Scan on entities  (cost=43.31..4467.60 rows=2266 width=24) (actual time=0.872..15006.763 rows=1495 loops=1)
           Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
           ->  Bitmap Index Scan on gin_ix_entity_full_text_search_name  (cost=0.00..43.20 rows=2266 width=0) (actual time=0.549..0.549 rows=1495 loops=1)
                 Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
 Total runtime: 15013.805 ms

Bit Todavía no entiendo por qué esto es más lento. Parece que está obteniendo la misma cantidad de filas de Bitmap Heap Scan, pero ¿lleva mucho más tiempo?

xlash
fuente
Si aumentar work_mem no proporciona un aumento de rendimiento suficiente, muestre los resultados de EXPLAIN ANALYZE en lugar de simplemente EXPLAIN. También ayuda a mostrar los resultados de ejecutar la consulta en esta página: wiki.postgresql.org/wiki/Server_Configuration Una breve descripción del hardware también ayuda.
kgrittn
Aquí hay otro EXPLICAR ANÁLISIS: (ver edición en mi publicación anterior)
xlash
Debo señalar que no es probable que la configuración de PostgreSQL que muestres esté cerca de ser óptima. Sin embargo, eso es lo suficientemente fuera de tema que probablemente debería abordarse en una pregunta separada. Te recomiendo que lo hagas.
kgrittn
Si tiene problemas para comprender la salida de EXPLAIN ANALYZE, hay una página Wiki que debería ayudar: wiki.postgresql.org/wiki/Using_EXPLAIN Mucha gente encuentra útil la página explicando.depesz.com ; es posible que desee buscar la ayuda y probarla.
kgrittn

Respuestas:

8

Lo que aún no entiendo es por qué esto es más lento.

Que ordenar las filas costará algo es obvio. ¿Pero por qué tanto?
Sin ORDER BY rank0...Postgres solo podemos

  • elija las primeras 5 filas que encuentre y deje de buscar filas tan pronto como tenga 5.

    Bitmap Heap Scan en entidades ... filas = 5 ...

  • luego calcule ts_rank()solo para 5 filas.
En el segundo caso, Postgres tiene que

  • busque todas las filas (1495 según su plan de consulta) que califiquen.

    Bitmap Heap Scan en entidades ... filas = 1495 ...

  • calcular ts_rank()para todos ellos.
  • ordénelos todos para encontrar los primeros 5 según el valor calculado.
Intente ORDER BY namesolo ver el costo de la computación to_tsquery('english', 'hockey'::text))para las filas superfluas y cuánto queda por recuperar más filas y ordenarlas.

Erwin Brandstetter
fuente
El almacenamiento en caché viene en el camino ... más o menos da un rendimiento tan malo. 10 segundos 1500 filas. Entiendo tu explicación. Que tiene sentido. Pero mientras hago una búsqueda de texto ... ¿alguna forma de construir mi índice para una clasificación de calidad adecuada sin extraer todo?
xlash
5

Un escaneo de mapa de bits funciona así: el índice se escanea para encontrar las ubicaciones de las tuplas que coinciden con las condiciones del índice. El mapa de bits podría pasar por una combinación lógica con los resultados de otros escaneos de mapas de bits, utilizando lógica booleana en los bits. Luego, se accede a las páginas que contienen los datos en orden de número de página, para reducir el acceso al disco y, con suerte, convertir algunas lecturas aleatorias en lecturas secuenciales.

Es posible que una exploración de índice de mapa de bits se vuelva "con pérdida" para caber en la memoria; esto reduce su precisión desde el nivel de tupla hasta el nivel de página. Si aumenta work_mem (al menos para esta consulta), podría evitarlo. No podrá omitir la exploración de montón de mapa de bits en la línea 4 o el filtro en la línea 6, pero podría omitir la verificación en la línea 5.

kgrittn
fuente
1
Aumentó de 100 MB a 650 MB, no dio diferencia de rendimiento. (
work_mem
5

Dado que la pregunta editada hace que parezca que el objetivo es elegir algunas de las mejores coincidencias, en lugar de una lista de todo lo que coincide, estoy publicando una respuesta separada para eso, ya que es un problema bastante diferente.

Es posible que desee considerar un índice KNN - GiST (para K Vecino más cercano - Árbol de búsqueda generalizada), que puede extraer directamente del índice en orden de similitud, por lo que no necesita leer al azar todas esas tuplas de montón y ordenar hacia abajo para encontrar las K mejores coincidencias.

Hasta la fecha, no creo que nadie haya implementado el soporte KNN-GIST para las consultas tsearch (aunque estoy seguro de que se puede hacer, es solo cuestión de que alguien se tome el tiempo para hacerlo), pero tal vez el soporte trigram (que está hecho) funcionará para su aplicación. La principal diferencia es que las búsquedas de trigram no usan diccionarios para derivaciones y sinónimos como lo hace tsearch; solo encontrarás coincidencias de palabras exactas.

Para probar los trigramas para su ejemplo, probablemente desee indexar "nombre" de esta manera:

CREATE INDEX entities_name_trgm ON entities USING gist (name gist_trgm_ops);

Entonces puedes buscar así:

SELECT
    *,
    name <-> 'banana' AS sim
  FROM entities 
  WHERE name % 'banana'
  ORDER BY sim DESC
  LIMIT 5;

Tenga en cuenta los operadores utilizados y el ORDER BYuso del alias de la columna "similitud". No me alejaría demasiado de este patrón al probarlo. El índice en el tsvector no se utiliza para esta búsqueda.

A menos que haya problemas con su configuración actual (que fácilmente podría llevar a toda su máquina virtual a una paginación desesperada por sobrecompromiso de memoria), probablemente le gustará realmente el rendimiento de esto. Si tiene el comportamiento que desea es lo que no sé.

kgrittn
fuente