Lecciones aprendidas al mejorar la búsqueda de texto completo en Snyk con Elasticsearch
Sergey Vasilkov
4 de noviembre de 2021
0 minutos de lecturaElasticsearch es un motor de búsqueda de código abierto muy popular. Gracias a su velocidad en tiempo real y a su API robusta, es una opción frecuente entre los desarrolladores que necesitan agregar capacidades de búsqueda de texto completo a sus proyectos. Además de su popularidad general, también es el motor al que estamos migrando actualmente la funcionalidad de informes de Snyk para issues. Cuando terminemos de ajustar todo en issues, empezaremos a usar Elasticsearch en otras áreas de informes.

Aunque Elasticsearch es potente, al principio también puede parecer complicado (a menos que tengas experiencia con motores de búsqueda). Pero como hace poco aprendí mucho sobre esta herramienta al implementarla en Snyk, pensé en compartir algunos conocimientos contigo, ¡de desarrollador a desarrollador!
A veces, un analizador estándar no es suficiente
Elasticsearch es rápido porque trata los distintos datos de manera distinta. Entre la gran variedad de tipos de campos, Elasticsearch cuenta con campos de texto, es decir, campos habituales para contenido textual (como cadenas de caracteres). Para que la información almacenada en esos campos se pueda buscar, Elasticsearch analiza el texto al ingerirlo y convierte los datos en tokens (términos). Luego almacena esos tokens y otra información relevante, como la longitud y la posición, en el índice. De forma predeterminada, usa un analizador estándar para analizar el texto.
Según la documentación oficial: «El analizador estándar ofrece compatibilidad inmediata con la mayoría de los idiomas naturales y casos de uso. Si eliges usar el analizador estándar tal como está, no necesitas ninguna configuración adicional».
En casos básicos, el análisis estándar puede ser suficiente. Veamos cómo funciona. Al ejecutar el siguiente comando, podremos ver el análisis generado para cualquier texto que proporcionemos:
Aquí podemos ver exactamente qué hace el analizador estándar: divide el texto en tokens y genera información de servicio para cada segmento, que luego almacena en el índice.
Buscar en el texto indexado
Una vez indexado el contenido, podemos probar la búsqueda de texto completo con un analizador estándar. Crearemos un documento de ejemplo con dos campos: title y description. Elasticsearch asignará automáticamente el mapeo del documento y detectará los campos de texto.
Ahora busquemos «express» con la siguiente consulta:
No se devolverá ningún documento, aunque tengamos «expression» en el título. Este resultado es el esperado (bueno, era de esperarse porque ya me había topado con esto), ya que el analizador no creó ningún token «express» (como podemos ver en los tokens generados que obtuvimos antes).
Si cambiamos la consulta de búsqueda a «expression», se devolverá el documento:
Hay consultas más costosas que permiten resolver este problema de coincidencia de tokens, como las consultas difusas y de comodín, pero consumen muchos recursos de procesamiento. Así que, en lugar de gastar ciclos de CPU en el problema, debemos reevaluar nuestro analizador.
Está claro que un analizador estándar puede no ser suficiente si queremos resultados de búsqueda mejores y más flexibles. Para lograrlo, configuraremos un analizador personalizado. Estos serán nuestros requisitos:
Usar consultas más cortas
Búsqueda que no distinga entre mayúsculas y minúsculas
Para lograrlo, seguiremos estos pasos:
Crear un analizador personalizado
Configurar un tokenizador más adecuado para este caso de uso
Habilitar el nuevo analizador personalizado para los campos elegidos en el índice de Elasticsearch
Profundizar en los analizadores
Con el analizador estándar, nos facilitamos el trabajo al evitar tener que entender realmente qué hacía el analizador. Ahora que vamos a crear uno personalizado, necesitamos entender qué ocurre durante el análisis de texto:
Los filtros de caracteres (opcionales) se aplican al texto que se analiza para eliminar caracteres.
Un tokenizador divide el texto en tokens o términos. Esto puede hacerse de distintas maneras: generar tokens por espacios en blanco, por letras, etc.
Los filtros de tokens (opcionales) realizan cambios adicionales en los tokens, como convertirlos a minúsculas, eliminar ciertos tokens y más.
Un analizador es una combinación de tokenizadores y filtros. Analiza el texto y lo prepara para la búsqueda. Todo analizador debe tener un solo tokenizador, pero puede tener pocos o muchos filtros de ambos tipos. El orden de las operaciones nunca cambia: filtro de caracteres > tokenizador > filtro de tokens.
Crear nuestro primer analizador personalizado
Como vimos antes, de forma predeterminada todos los campos de texto usan el analizador estándar. Elasticsearch ofrece una gran variedad de analizadores integrados que se pueden usar en cualquier índice sin necesidad de configurarlos, y también puedes crear los tuyos. Para este caso de uso, tendremos que crear uno propio.
Los analizadores se pueden configurar en distintos niveles: índice, campo o consulta. Crearemos un analizador personalizado y lo especificaremos para un campo. Así se configura un analizador personalizado. Nota:Las configuraciones se almacenan en los ajustes del índice:
Esto es lo que hicimos:
Se especifica un nuevo analizador personalizado llamado
my_analyzerpara el índicemy_analyzerusa el tokenizadorstandardy el filtrolowercasemy_analyzerse habilita como analizador de la propiedad title en el mapeo del índice
Este ejemplo es bastante sencillo y no cambia el comportamiento del analizador estándar. Ahora podemos probar algunos ajustes para que sea posible buscar palabras incompletas sin distinguir entre mayúsculas y minúsculas.
Elegir y configurar un tokenizador mejor
En el ejemplo anterior usamos el tokenizador estándar; ahora es momento de reemplazarlo por edge_ngram, que divide los tokens según su longitud (no por los espacios en blanco). Y si te preguntas por qué uso edge_ngram en lugar de stemmer, ¡sigue leyendo!
Esto es lo que hicimos:
my_tokenizerusa el tokenizadoredge_ngramCreará tokens de entre
3y8caracteresLos tokens incluirán solo letras y dígitos
Elegir y configurar un filtro de tokens mejor
El analizador que creamos antes ahora permite buscar con palabras incompletas de entre 3 y 8 caracteres. Lo último que falta es hacer que la búsqueda no distinga entre mayúsculas y minúsculas. Para lograrlo, hay que agregar el filtro de tokens lowercase adecuado a my_analyzer:
Resultado final
Después de todos los cambios, ya podemos crear un índice nuevo con un analizador personalizado. Antes de cambiar el analizador, vale la pena tener en cuenta que no se puede actualizar su configuración en campos existentes mediante la API de actualización de mapeos.
Así se verán los ajustes y los mapeos de nuestro índice de prueba básico:
Ahora podemos agregar un documento y ejecutar distintas consultas para verificar que la búsqueda funcione como esperamos:
Buscar títulos que contengan «express» u otros tokens incompletos, sin distinguir entre mayúsculas y minúsculas:
Ten en cuenta que no configuramos un analizador personalizado para la propiedad text de nuestro índice, así que ahí seguimos usando la búsqueda de siempre con un analizador estándar.
¿Qué sigue?
En este artículo sencillo, configuramos un analizador personalizado para lograr una búsqueda de texto más flexible en Elasticsearch y vimos algunos ajustes y configuraciones básicos y específicos. Elasticsearch ofrece una gran variedad de analizadores, tokenizadores, filtros y normalizadores integrados, opciones de búsqueda para excluir términos y mucho más.
Al combinarlos, podemos lograr excelentes resultados de búsqueda personalizados. Si estás empezando con Elasticsearch, espero que esto te haya resultado útil. No te pierdas las próximas novedades: tenemos pensado compartir más sobre nuestro recorrido con la búsqueda. ¡Lo próximo serán las agregaciones! (Puedes contar con eso...)
Si te gustaría formar parte de Snyk y crear excelentes herramientas de seguridad para desarrolladores, consulta nuestras vacantes abiertas en ingeniería.
