Optimización de consultas ordenadas en Elasticsearch para obtener resultados más rápidos

blog-thumb-search-charts.png

En Elasticsearch, es muy común solicitar que los resultados se ordenen por un campo determinado. Hemos invertido una gran cantidad de tiempo y esfuerzo en optimizar las consultas ordenadas a fin de hacerlas más rápidas para nuestros usuarios. En este blog se describirán algunas de las optimizaciones de clasificación para campos numéricos y de fecha.

Cómo funcionan las consultas ordenadas

Cuando desees encontrar documentos que coincidan con un filtro y solicitar que los resultados se ordenen por un determinado campo, Elasticsearch examinará los valores de documento de este campo para todos los documentos que coincidan con el filtro y elegirá los K documentos principales con los valores más altos. En el peor de los casos, con un filtro muy amplio (p. ej., búsqueda match_all), tenemos que examinar y comparar los valores de todos los documentos en un índice. En el caso de índices grandes, esto puede llevar mucho tiempo.

Una forma de optimizar las consultas ordenadas en un campo en particular es utilizar clasificación por índices y ordenar todo el índice en este campo. Si el índice está ordenado por campo, sus valores de documento también se ordenan. Por lo tanto, para ordenar los K documentos principales por campo, simplemente tenemos que tomar los primeros K documentos y ni siquiera necesitamos examinar el resto, lo que hace que las consultas ordenadas sean superrápidas.

Valores de documento para un campo <X> (izquierda) y los mismos valores de documento para el campo<X> en un índice ordenado por campo <X> (derecha).
Valores de documento para un campo (izquierda) y los mismos valores de documento para el campo en un índice ordenado por campo (derecha).

La clasificación por índices es una gran solución, pero solo se puede realizar de una única manera. La clasificación por índices no es útil para consultas ordenadas que utilicen criterios de clasificación diferentes, como descendente o ascendente, campos diferentes o combinaciones diferentes de las establecidas en una definición de clasificación por índices. Por lo tanto, se necesitaban otros enfoques más flexibles para acelerar las consultas ordenadas.

Optimización de consultas ordenadas numéricamente con la búsqueda distance_feature

En el pasado, aceleramos significativamente las consultas basadas en términos ordenadas por _score almacenando para cada bloque de documentos su máximo impacto: una combinación de frecuencia de términos y longitud del documento. Durante la búsqueda, podemos juzgar rápidamente si un bloque de documentos es competitivo observando su impacto máximo. Si un bloque no es competitivo, podemos omitir todo este bloque de documentos, lo que agiliza considerablemente las búsquedas.

Pensamos que podríamos aplicar un enfoque similar para acelerar las consultas ordenadas en campos numéricos o de fecha. Resultó posible sustituyendo ordenar por una búsqueda distance_feature. La búsqueda distance_feature es una búsqueda interesante que devuelve los K documentos principales más cercanos a un origen determinado. Si utilizamos el valor mínimo para el campo como origen, obtendremos los K documentos principales clasificados en orden ascendente. Utilizar el valor máximo como origen nos dará los K documentos principales en orden descendente.

La propiedad más interesante de la búsqueda distance_feature para nosotros es que puede omitir eficientemente bloques de documentos no competitivos. Para ello, se basa en las propiedades de los árboles BKD que se utilizan en Elasticsearch para indexar campos numéricos y de fecha. De manera similar a cómo un índice de entradas para un campo de texto se divide en bloques de documentos, un índice BKD se divide en celdas y cada celda conoce sus valores mínimo y máximo. Por lo tanto, una búsqueda distance_feature que solo examine los valores mínimos y máximos de las celdas puede omitir eficientemente las celdas no competitivas de los documentos. Para que esta optimización de clasificación funcione, un campo numérico o de fecha debe estar indexado y tener valores de documento.

Al sustituir la clasificación de los valores de los documentos con una búsqueda distance_feature, podríamos lograr grandes aceleraciones (en algunos sets de datos, ganancias de hasta 35 veces). Introdujimos esta optimización de clasificación en campos largos y de fecha en Elasticsearch 7.6.

Optimización de consultas ordenadas con search_after

Nos alegramos de ver estas aceleraciones, pero todavía no teníamos una buena solución para ordenar con el parámetro search_after. Ordenar con search_after es muy común, ya que los usuarios suelen estar interesados no solo en la primera página de resultados, sino también en las siguientes. Hemos decidido que, en lugar de nuestro enfoque actual de reescribir consultas ordenadas en Elasticsearch, una mejor solución sería hacer que los comparadores y los recopiladores dentro de Lucene realicen esta optimización de clasificación y omitan documentos no competitivos. Como los comparadores y los recopiladores de Lucene ya se ocupan de search_after, esto también nos permitiría tener una solución para este problema. El mismo código de Elasticsearch que utilizaba la búsqueda distance_feature para omitir bloques de documentos no competitivos se agregó a los comparadores numéricos de Lucene.

Hemos introducido esta optimización de clasificación con el parámetro search_after en Elasticsearch 7.16. Inmediatamente observamos grandes aceleraciones (hasta 10 veces) en algunos de nuestros puntos de referencia de rendimiento nocturnos:

Optimizaciones de clasificación con search_after en índices con varios segmentos (izquierda) e índices fusionados forzosamente en un solo segmento (derecha).  Antes de las optimizaciones (13 de septiembre de 2021), la clasificación descendente con search_after tardaba entre 1400 y 1800 ms; después de las optimizaciones, entre 200 y 300 ms.
Optimizaciones de clasificación con search_after en índices con varios segmentos (izquierda) e índices fusionados forzosamente en un solo segmento (derecha). Antes de las optimizaciones (13 de septiembre de 2021), la clasificación descendente con search_after tardaba entre 1400 y 1800 ms; después de las optimizaciones, entre 200 y 300 ms.

Optimización de la clasificación en varios segmentos

Un shard se compone de varios segmentos. Como Elasticsearch examina los segmentos secuencialmente en el momento de la búsqueda, sería muy beneficioso comenzar a procesar con el segmento que sea el mejor candidato para contener documentos con los valores K más altos. Una vez que recopilamos documentos con los valores K más altos, podemos omitir muy rápidamente otros segmentos que solo contengan valores peores.

Con qué segmentos comenzar a procesar depende en gran medida del caso de uso. Para los índices de series temporales, la solicitud más común es ordenar los resultados en el campo de marca horaria descendente, ya que los últimos eventos son los más interesantes de observar. Para optimizar este tipo de clasificación para índices de series temporales, comenzamos a ordenar segmentos en el campo @timestamp descendente, de modo que podamos comenzar a procesar con un segmento que contenga los datos más recientes, idealmente recopilar en el primer segmento los documentos más recientes por marca horaria y omitir todos los demás segmentos. Esto resultó en una aceleración bastante buena para consultas ordenadas en el campo de marca horaria descendente.

A medida que los segmentos más pequeños se fusionan en segmentos más grandes, no queremos terminar con nuevos segmentos donde los documentos más recientes estén al final. Para obtener segmentos fusionados más equilibrados, introdujimos una nueva política de fusión que intercala los segmentos antiguos y nuevos, es decir, los documentos de los segmentos antiguos y nuevos se organizan en orden mixto en un nuevo segmento combinado. Esto también nos permite encontrar eficientemente los documentos más recientes.

Optimización de la clasificación en varios shards

El poder de Elasticsearch está en su búsqueda distribuida, y cualquier optimización estaría incompleta sin pensar en un aspecto distribuido. Dado que algunas búsquedas pueden abarcar cientos de shards (p. ej., búsquedas en índices de series temporales), sería muy beneficioso comenzar con sets "correctos" de shards y shards de acceso directo que no deberían contener resultados competitivos. Y hemos implementado exactamente este enfoque. Desde Elasticsearch 7.6, preclasificamos los shards según el valor máximo/mínimo del campo de clasificación principal, lo que nos permite iniciar una búsqueda distribuida con un set de shards que son los mejores candidatos para contener los valores más altos. Desde Elasticsearch 7.7 acortamos la fase de búsqueda utilizando los resultados de otros shards, es decir, una vez que recopilamos los valores principales del primer set de shards, podemos omitir completamente el resto de shards ya que todos sus valores posibles son peores que los valores de clasificación inferiores calculados en shards anteriores. En muchos índices de series temporales generados por máquinas, los documentos siguen una política de ciclo de vida del índice, que comienza en hardware de rendimiento optimizado y termina en hardware de costo optimizado antes de eliminarse. Este mecanismo de omisión de shards a menudo significa que los usuarios pueden enviar una búsqueda amplia y disfrutar de un rendimiento de búsqueda definido por su hardware de rendimiento optimizado, ya que se omiten los shards en el hardware más lento y económico (lo que hace que el uso de snapshot buscable sea particularmente eficiente).

Implicaciones para los usuarios

¿Cómo puedes, como usuario de Elasticsearch, aprovechar estas optimizaciones de clasificación? Estas optimizaciones de clasificación solo funcionan si no necesitas realizar un seguimiento del número exacto de resultados totales a una solicitud y la solicitud no contiene agregaciones. Si necesitas saber el número exacto de resultados totales, no podemos realizar ninguna omisión, ya que necesitamos contar todos los documentos que coinciden con un filtro. El valor predeterminado de track_total_hits se establece en 10 000, lo que significa que la optimización de clasificación solo comienza una vez que recopilamos 10 000 documentos. Si estableces este valor en un número menor o en "falso", Elasticsearch comienza la optimización de clasificación mucho antes, lo que significa respuestas más rápidas.

Recientemente, Kibana también comenzó a enviar solicitudes en las que track_total_hits está deshabilitado, por lo que ordenar consultas en Kibana también debería ser más rápido.

Pruébalo

Los clientes existentes de Elastic Cloud pueden acceder a muchas de estas características directamente desde la consola de Elastic Cloud. Si eres nuevo en Elastic Cloud, echa un vistazo a nuestras guías de inicio rápido (videos de capacitación breves para que puedas dar los primeros pasos rápido) o nuestros cursos de capacitación gratuitos sobre conceptos básicos. Siempre puedes comenzar sin costo con una prueba gratuita de 14 días de Elastic Enterprise Search. O descarga la versión autogestionada del Elastic Stack de forma gratuita.