elasticsearch, introducción al cálculo del score o la relevancia de un documento

Elasticsearch

El _score es representado por un valor decimal. Su valor determina el orden en el resultado de las búsquedas y es calculado internamente por elasticsearch.

Para calcular este valor elasticsearch utiliza un algoritmo que depende del tipo de query o búsqueda que realicemos.

El algoritmo que utiliza elasticsearch por defecto para calcular el _score o la relevancia de los documentos es el

  • TF/IDF, term frequency/inverse document frequency

que calcula cómo de similar es el contenido de un campo de texto con respeto al texto indicado en la query.

TF/IDF o term frequency/inverse document frequency

Este algoritmo tiene en cuenta los siguiente criterios

  • term frequency, cuantas veces aparece el término dentro del campo de texto. Cuantas más veces aparezca el término más relevante será el documento y mayor _score obtendrá

  • inverse document frequency, cuantas veces aparece el término con respecto a todos los documentos, es decir, con respecto a todo el índice. Cuantas más veces aparezca en más documentos, la relevancia será menor.

  • field length norm, cuál es la longitud del campo donde se hace la búsqueda. Cuanto más pequeño sea la longitud del campo donde aparece el término, la relevancia será mayor.

La combinación de varios tipos de queries diferentes afecta al cálculo del _score, que igualmente se combina para obtener un único valor.

El parámetro explain

Aunque es complejo de entender, podemos utilizar este parámetro para obtener el detalle de cómo se ha calculado el _score

curl -XPOST http://localhost:9200/usuarios/perfil/_search?explain -d '{
  "query": {
    "query_string": {
      "default_field": "nombre",
      "query": "michael",
      "default_operator": "AND"
    }
  }
}'
# Respuesta, tendremos el documento encontrado junto con información habitual y la explicación del cálculo del score
"_explanation": 
{
   "took": 1,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 2,
      "max_score": 0.2169777,
      "hits": [
         {
            "_shard": 2,
            "_node": "PmaatxiyRriKzPJMsBQD5g",
            "_index": "usuarios",
            "_type": "perfil",
            "_id": "1",
            "_score": 0.2169777,
            "_source": {
               "nombre": "Michael michael Page Jordan",
               "edad": 36
            },
            "fields": {
               "_timestamp": 1443090715454
            },
            "_explanation": {
               "value": 0.2169777,
               "description": "weight(nombre:michael in 0) [PerFieldSimilarity], result of:",
               "details": [
                  {
                     "value": 0.2169777,
                     "description": "fieldWeight in 0, product of:",
                     "details": [
                        {
                           "value": 1.4142135,
                           "description": "tf(freq=2.0), with freq of:",
                           "details": [
                              {
                                 "value": 2,
                                 "description": "termFreq=2.0"
                              }
                           ]
                        },
                        {
                           "value": 0.30685282,
                           "description": "idf(docFreq=1, maxDocs=1)"
                        },
                        {
                           "value": 0.5,
                           "description": "fieldNorm(doc=0)"
                        }
                     ]
                  }
               ]
            }
         }
      ]
   }
}

Por el momento no vamos a entrar en analizar en detalle la respuesta proporcionada por éste parámetro, pero vemos que cada uno de los criterios visto anteriormente está presente.

El valor total del score es 0.2169777, que se ha obtenido teniendo en cuenta los criterios:

term frequency, aparece 2 veces el término michael dentro del documento encontrado.

"value": 1.4142135,
"description": "tf(freq=2.0), with freq of:",

inverse document frequency, la relevancia del término michael en todo el índice

"value": 0.30685282,
"description": "idf(docFreq=1, maxDocs=1)"

field length norm, la relevancia del término michael teniendo en cuenta la longitud del campo del documento encontrado.

"value": 0.5,
"description": "fieldNorm(doc=0)"

Todos estos valores dan como resultado final el score 0.2169777.

El API explain

Al igual que el parámetro explain tenemos también disponible el API _explain, que nos da la misma información pero además nos dice el motivo de que nuestro documento encaje o no con nuestra búsqueda.

curl -XGET http://localhost:9200/usuarios/perfil/1/_explain -d '{
  "query": {
    "query_string": {
      "default_field": "nombre",
      "query": "michael",
      "default_operator": "AND"
    }
  }
}'
# Respuesta
{
   "_index": "usuarios",
   "_type": "perfil",
   "_id": "1",
   "matched": true,
   "explanation": {
      "value": 0.2169777,
      "description": "weight(nombre:michael in 0) [PerFieldSimilarity], result of:",
      "details": [
         {
            "value": 0.2169777,
            "description": "fieldWeight in 0, product of:",
            "details": [
               {
                  "value": 1.4142135,
                  "description": "tf(freq=2.0), with freq of:",
                  "details": [
                     {
                        "value": 2,
                        "description": "termFreq=2.0"
                     }
                  ]
               },
               {
                  "value": 0.30685282,
                  "description": "idf(docFreq=1, maxDocs=1)"
               },
               {
                  "value": 0.5,
                  "description": "fieldNorm(doc=0)"
               }
            ]
         }
      ]
   }
}

Controlar y modificar el algoritmo del cálculo de la relevancia o score de los documentos

Podemos hacerlo y esperamos verlo en otros artículos ya que es un tema bastante amplio y complejo .

Anuncios

elasticsearch, importar ficheros CSV utilizando el API Bulk de Java

Elasticsearch

Cuando se trata de cargas masivas de datos, no podemos utilizar el API Java directamente para indexar uno por uno todos los documentos. Se puede hacer pero no es práctico desde el punto de vista del rendimiento. En estos casos necesitamos que la carga sea rápida y nos de un feedback de lo que ha ocurrido.

En estos casos debemos utilizar el API Bulk de elasticsearch. Tenemos dos formas:

  • Invocar directamente al API Rest de bulk. Útil cuando ya disponemos de un fichero Bulk generado previamente. Podemos cargarlo utilizando el comando curl.

  • Utilizar el Java Bulk API de la librería cliente de elasticsearch. Útil en los casos de no disponer de un fichero Bulk o bien nos sentimos más cómodos utilizando Java.

En cualquiera de los casos es importante saber que el tamaño del fichero Bulk influye directamente en el rendimiento de la carga. Cuanto más grande es el fichero de Bulk la carga se irá degradando con el tiempo. Por tanto debemos realizar las cargas por bloques para conseguir el mejor rendimiento. El tamaño de cada bloque de datos depende como es lógico del tamaño de cada uno de los documentos a indexar.

La recomendación dada por elasticsearch es de 5 a 15 MB por cada bulk. No se suele utililizar el número de documentos como referencia ya que como es obvio el tamaño de un tipo de documento varia, y por tanto no es lo mismo 10000 documento de 1 KB cada uno que 10000 documentos de 10 KB.

En el ejemplo que vamos a ver hemos puesto un tamaño de bulk muy pequeño, ya que el fichero csv con datos es un fichero de poco tamaño.

También influye el número de shards primarios y replicas que tengamos en nuestro índice. Recordemos que cuantos más replicas tengamos más rápido son las búsquedas, y cuantos más shards primarios tengamos más rápido serán las cargas bulk y las indexaciones (rapidez en la indexación de domentos)

Dentro del Java API tenemos dos posibilidades de realizar la carga. La opción elegida depende como siempre de lo que necesitemos.

Bulk API

Permite enviar en 1 sola petición varios documentos.

El uso de este API es sencillo y se corresponde con

BulkRequestBuilder bulkRequest = client.prepareBulk();
bulkRequest.add(...);
bulkRequest.add(...);
...
BulkResponse bulkResponse = bulkRequest.execute().actionGet();

Se va creando el bulk según se van añadiendo los documentos con bulkRequest.add(…);. Y una vez tenemos todo el bulk preparado se lo enviamos de una sola vez a elasticsearch bulkRequest.execute().actionGet().

Es el más fácil de utilizar. Dentro de cada add(…) van las operaciones de bulk, las cuales pueden ser indexar nuevos documentos, actualizar alguno existente en base al id, borrar documentos etc.

Bulk Processor

Permite enviar en 1 sola petición varios documentos, pero basados en el tamaño de los documentos, en el número de documentos o en un tiempo determinado. Además permite la carga en el mismo thread o en varios diferentes.

BulkProcessor bulkProcessor = BulkProcessor.builder(client, new BulkProcessor.Listener() {
            ...
})
.setBulkActions(5000) 
.setFlushInterval(TimeValue.timeValueSeconds(10)) 
.setConcurrentRequests(1) 
.build();
bulkProcessor.add(new IndexRequest(indice, tipoDocumento, id).source( ... ));
bulkProcessor.add(new IndexRequest(indice, tipoDocumento, id).source( ... ));
bulkProcessor.add(new IndexRequest(indice, tipoDocumento, id).source( ... ));
...

Como vemos en este caso estamos indexando nuevos documentos, pero al igual que en el caso anterior podemos estar borrando y actualizando documentos.

La principal diferencia con respecto al anterior API consiste en delegar al API el envío del bulk basado en este caso en alcanzar los 5000 documentos, o 10 segundos. Lo que llegue antes.

Este es el API que vamos a utilizar en el ejemplo.

Importar ficheros CSV en Elasticsearch

Este proyecto puede ser descargado desde GitHub, lo podeis encontrar en GitHub, Importar ficheros CSV en elasticsearch.

Descripción

El principal objetivo de este proyecto es el de mostrar el uso del API Bulk de elasticsearch y presentar un proyecto que pueda ser descargado y modificado por cualquiera. Para ello hemos creado este proyecto que carga cualquier fichero CSV en elasticsearch. Delegamos la creación del mapping a elasticsearch, aunque como siempre es recomentable que lo hagamos nosotros mismos según los requisitos y funcionalidades que necesitemos.

Formato del fichero CSV

Primera fila. Nombre de las columnas separadas por el carácter pipe |. Este nombre de las columnas será también el utilizado por el documento JSON que irá a elasticsearch.

Resto de filas. Valores de las columnas separados por el caráter pipe |.

Un fichero de ejemplo se encuentra en este mismo proyecto, es el fichero example.csv. Su estructura es:

Orden|Apellido|Total
1|GARCIA|1476378
2|GONZALEZ|929938
3|RODRIGUEZ|928305

Formato del tipo de documento de elasticsearch

Su estructura depende el fichero CSV. Así por ejemplo, para el fichero CSV incluido en este proyecto estos son documentos Json válidos para elasticsearch.

{"orden": "1", "apellido": "GARCIA", "total": "1476378" }
{"orden": "2", "apellido": "GONZALEZ", "total": "929938" }
{"orden": "3", "apellido": "RODRIGUEZ", "total": "928305" }

Versión de frameworks y software utilizado

  • Maven 3
  • Spring 4.2.1.RELEASE
  • Log4j 1.2.14
  • Elasticsearch Java API client 1.7.1
  • Java 8

Clases Principales y ficheros de configuración

  • loquemeinteresadelared.conf.AppConfig. Clase de configuración basada en anotaciones de Spring.
  • loquemeinteresadelared.MainProcess. Clase main que inicia la carga del fichero csv.
  • loquemeinteresadelared.LoadCsvImpl. Clase principal que organiza la carga del fichero CSV en elasticsearch.
  • loquemeinteresadelared.csv.CsvManagerImpl. Lee el fichero CSV línea a línea.
  • loquemeinteresadelared.es.ESManagerImpl. Se conecta a elasticsearch utilizando Transport client y prepara la carga bulk.
  • default.properties y development.properties. Son ficheros de configuración para un entorno local y un entorno de desarrollo donde se guarda la configuración de acceso a elasticsearch, la ruta del fichero csv, la configuración del bulk etc. Sus valores pueden ser sobrescritos por propiedades de la Jvm en el arranque de la aplicación.

Ejecución

Arranque por defecto, entorno local.
Arranque por defecto.

loquemeinteresadelared.MainProcess 

Arranque utilizando la propiedad profile de spring.

loquemeinteresadelared.MainProcess -Dspring.profiles.active=default

Arranque entorno development

loquemeinteresadelared.MainProcess -Dspring.profiles.active=development

Elasticsearch, diferencias entre los tipos de búsquedas query_string, match y term

Elasticsearch

Cuando empezamos a utilizar elasticsearch por primera vez las utilizaremos indistintamente, pero pronto nos daremos cuenta de que el resultado devuelto en cada una de ellas es diferente. Es importante entender la diferencia entre estos tipos de queries para poder utilizarlas correctamente y para ello no hay nada mejor que ver las diferencias con unos ejemplos.

Para ello utilizaremos un índice de frases celebres de varios actores. Cada frase será indexada en un tipo de documento llamado frase, sobre el que modificaremos su mapping por defecto para utilizar el analizador en español.

Pero antes de todo ello, vamos a describir brevemente qué es el proceso de indexado.

Proceso de indexado

El proceso de indexado consiste en guardar y hacer disponible los documentos para su búsqueda. Lo que elasticsearch hace al indexar es analizar los textos, utilizando un analizador, para extraer los términos que lo forman, y finalmente crear un índice invertido, sobre el que realizar las búsquedas.

Analizar consiste en descomponer un texto en términos individuales, y cada uno de ellos normalizarlos en una forma estandar, para facilitar sus búsquedas.

Estos términos normalizados son guardados en el índice invertido, sobre el que posteriormente se realizarán las búsquedas.

Un índice invertido consiste en una tabla donde se guarda en qué documentos aparece cada uno de estos términos.

La principal diferencia entre los diferentes tipos de búsquedas radica en la utilización o no del analizador antes de buscar los terminos en el indice invertido. Las búsquedas por terminos no utilizan el analizador, mientras que las búsquedas de textos o fulltext si lo hacen.

Sigue leyendo

Elasticsearch, los módulos Network, Transport y Http

Elasticsearch

Estos módulos permiten configurar el comportamiento de red de elasticsearch y juega un papel importante a la hora de entender como elasticsearch publica sus servicios. Su comportamiento lo podemos configurar gracias a estos módulos.

Network settings

Este módulo permite configurar el comportamiento común de los módulos Transport y Http. No vamos a entrar en todas las opciones de configuración, si no solo en aquellas que son más importantes.

Las propiedades principales de este módulo son

Sigue leyendo

Elasticsearch – NoNodeAvailableException: None of the configured nodes are available: []

Elasticsearch

Esta excepción org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [] nos la podemos encontrar cuando desde una aplicación Java nos conectamos a un cluster de Elasticsearch utilizando la librería Java de ES.

Para conectarnos a un clustar de elasticsearh desde una aplicación Java tenemos dos opciones posibles

  • Transport client
  • Node client (Esta opción no la vamos a comentar en este articulo)

Ambos clientes se conectan y se comunican con el cluster utilizando el puerto 9300. Como sabemos, este puerto 9300 es el utilizado por los nodos del cluster para comunicarse entre ellos.

Sigue leyendo

Elasticsearch se cae en máquinas virtuales basadas en OpenVZ al utilizar la librería sigar

Elasticsearch

Este bug ocurre en todas las versiones de elasticsearch y jsdk probadas ejecutándose en las últimas versiones del kernel de las máquinas virtuales basadas en OpenVZ. Desde ES 1.4 hasta la 1.5.1 y desde la jsdk 1.7 update 55 hasta la jsdk 1.8 update 45.

No es un error del kernel en sí, ni de ES, si no un error en la librería sigar que elasticsearch utiliza para la monitorización del sistema, la que utiliza para determinar el estado de la CPU, la memoria, información del sistema de ficheros etc.

Sigue leyendo

Elasticsearch – Vulnerabilidad CVE-2015-1427, ejecución de código remoto

Elasticsearch

Todo software no esta libre de errores ni de vulnerabilidades, y cuando mas joven es un producto, más posibilidades tenemos de encontrarnos con alguna vulneabilidad.

Es el caso de las versiones de Elasticsearch 1.3.0-1.3.7 y 1.4.0-1.4.2, y es el caso del articulo que vamos a comentaros. Todos los que usamos Elasticsearch sabemos que debemos ponerlo detrás de un firewall y dejarlo solo accesible por nuestras aplicaciones. Si no lo hacemos estamos dejando el servidor accesible a cualquiera, pudiendo realizar consultas, modificar indices, etc, ya que las API de acceso a Elasticserch están disponibles sin ningún tipo de autenticación/autorización.

Teniendo esto presente, nosotros nos encontramos con esta vulnerabilidad en uno de nuestro servidores VPS que teníamos para pruebas. Bueno, no estamos seguros al 100%, pero todo lo que pudimos investigar nos ha llevado a esta vulnerabilidad. En nuestra máquina se estaba ejecutando uno o varios comandos ubicados en el directorio /tmp y con el usuario del proceso de elasticsearch. Estos procesos estaban generando más tráfico del normal y todo el trafico tenia como destinos una o varias direcciones IP de china y todas al puerto 80 o http de esas máquinas.

Sigue leyendo