Table of Contents

Primeros pasos con ElasticSearch

La existencia de ElasticSearch se debe a que desafortunadamente, la mayoría de las bases de datos son malas opciones para la extracción de “conocimiento / información procesable” (actionable knowledge) de los datos almacenados. Con las bases de datos tradicionales se puede filtrar por fecha y hora o valores exactos, pero no pueden llevar a cabo búsqueda de texto basadas en sinónimos ni ordenar documentos por relevancia, no generan análisis ni permiten trabajar con ingentes cantidades de datos en tiempo real de forma cómoda y rápida.

Elasticsearch está escrito en Java y utiliza Lucene internamente para la indexación y procesos de búsqueda, su objetivo es hacer la búsqueda de texto completo fácil ocultando las complejidades de Lucene gracias a una API REST sencilla. ElasticSearch es capaz de escalar a cientos de servidores y petabytes de datos estructurados y no estructurados.

Elasticsearch utiliza JavaScript Object Notation o JSON, como el formato de serialización para los documentos, el cual es apoyado por la mayoría de los lenguajes de programación. JSON se ha convertido en el formato estándar utilizado por el movimiento NoSQL debido a que es simple, conciso y fácil de leer.

Algunos ejemplos de empresas que están utilizando ES en la actualidad.

Wikipedia utiliza Elasticsearch para proporcionar la búsqueda de texto completo y sugerencias. The Guardian utiliza Elasticsearch combinar registros de visitantes con las redes sociales en base a los comentarios de los artículos. StackOverflow combina búsqueda de texto completo, geolocalización y encontrar preguntas y respuestas relacionadas. GitHub utiliza Elasticsearch para consultar 130 mil millones de líneas de código.

Analogía Elasticsearch / MySQL

Índice Un índice es un espacio de nombres lógico mapeado en uno o más fragmentos primarios los cuales pueden tener ninguna o varias réplica. Es una colección de documentos que tienen características similares. Los índices están identificados por un nombre, el cual usaremos a la hora de indexar, buscar, actualizar y borrar. Indexar un documento es crear un documento para que pueda ser buscado.

El índice es el lugar (lógico) donde Elasticsearch almacena los datos de modo que se pueden dividir a su vez en trozos más pequeños denominados fragmentos. Sería como una tabla en el mundo de las bases de datos. La estructura del índice se prepara para una rápida y eficiente búsqueda de texto completo pero sin almacenar los valores originales. Elasticsearch puede contener muchos índices situados en una máquina o repartidos en varios nodos. Cada índice es construido de uno o más fragmentos (shards), y cada fragmento puede tener muchas réplicas (réplicas).

Documento Usando la analogía de las bases de datos relacionales, un documento es una tabla, normalmente es un documento Json. Los documentos se dividen a su vez en campos (fields), y puede estar varias veces en un mismo documento (multivalued). Cada campo tiene un tipo de dato (texto, número, fecha, etc.), los cuales pueden ser también complejos y albergar otros subdocumentos o tablas.

Afortunadamente la asignación a tipo de dato se puede determinar automáticamente (sin embargo, puede ser recomendable utilizar asignaciones). Un documento en ElasticSearch tiene que tener el mismo tipo de dato para todos los campos comunes. Esto significa, que todos los documentos con un campo “título” debe tener el mismo tipo de dato, por ejemplo, string.

Los documentos no necesitan tener una estructura fija, pudiendo tener un conjunto de campos diferentes. Los campos no tienen por qué que ser conocidos durante el desarrollo de la aplicación. Desde el punto de vista del cliente, un documento es un objeto JSON.

Cada documento está almacenado en un índice y tiene su propio identificador único (que puede ser generado automáticamente por ElasticSearch) y tipo de documento. Un documento debe tener un identificador único en relación con el tipo de documento. Esto significa que en un solo índice, dos documentos pueden tener el mismo identificador único si no son del mismo tipo de documento.

Tipo de documento En Elasticsearch, un índice puede almacenar muchos objetos con diferentes propósitos. El tipo de documento nos permite diferenciar fácilmente entre los objetos en un índice determinado a la vez que facilita la manipulación de datos.

Mapping (Asignaciones) En cuanto a la preparación de un texto para ser indexado y posteriormente buscado, como se ha dicho anteriormente, cada campo del documento debe ser analizado correctamente en función de su tipo. Por ejemplo, se requieren procesos de análisis diferente para los campos numéricos que para los de texto, los números no pueden ser ordenados alfabéticamente. Elasticsearch almacena la información sobre los campos dentro del mapping.

Ya sabemos que Elasticsearch almacena datos en uno o más índices. Cada índice puede contener documentos de varios tipos. También sabemos que cada documento tiene muchos campos y cómo Elasticsearch trata estos campos se define por las asignaciones (mapping). Pero hay más. Desde el principio, Elasticsearch fue creado como una solución distribuida que puede manejar miles de millones de documentos y cientos de solicitudes de búsqueda por segundo. Esto se debe a varios conceptos importantes que vamos a describir con más detalle a continuación.

Nodo y Clúster Elasticsearch puede trabajar de forma autónoma en una sola instancia de servidor. Sin embargo, para procesar grandes conjuntos de datos, lograr tolerancia a fallos y alta disponibilidad, Elasticsearch se pueden ejecutar en muchos servidores y funcionar de forma cooperativa en clúster, denominando a cada servidor de la formación nodo.

Shard (Fragmento) Cuando se tiene un gran número de documentos, se puede llegar a un punto en el que un único nodo puede no ser suficiente, debido a limitaciones de memoria RAM, capacidad de disco duro, potencia de procesamiento o simplemente la incapacidad para responder a las solicitudes de clientes lo suficientemente rápido. En tal caso, los documentos se pueden dividir en partes más pequeñas llamadas fragmentos (donde cada fragmento es un índice de Apache Lucene separado).

Cada fragmento se puede colocar en un servidor diferente, y por lo tanto, sus datos se puede transmitir entre los nodos del clúster. Cuando se consulta un índice dividido en varios fragmentos, Elasticsearch envía la consulta a cada fragmento relevante y fusiona el resultado acelerando por tanto la indexación, como es de suponer ese proceso es totalmente transparente para el cliente. Durante una indexación es posible especificar cuantos shards y réplicas deben crearse, según la configuración predeterminada son 5 shards y una réplica, es decir 10 incides lucene repartidos por el clúster. Al modificar el documento los 10 shards que lo componen deben también actualizarse.

Réplica Con el fin de aumentar el rendimiento de consulta o lograr una alta disponibilidad, se pueden usar réplicas de fragmento. Una réplica es sólo una copia exacta de un shard, y cada fragmento puede tener cero o más réplicas. Elasticsearch puede tener muchos fragmentos idénticos y uno de ellos es elegido a la hora de tener que realizar cambios sobre el incide. Este fragmento especial se llama un fragmento primario, y los demás se llaman fragmentos de réplica. Cuando se pierde el fragmento primario (por ejemplo, un servidor que contiene el fragmento de datos no está disponible), el clúster cambia el estado de un fragmento de réplica para ser el nuevo fragmento primario.

Gateway Elasticsearch puede manejar muchos nodos cuando se utiliza en modo clúster. De forma predeterminada, cada nodo tiene esta información almacenada localmente, que se sincroniza entre los nodos. El estado del clúster está en manos del Gateway.

Relación entre réplicas, fragmentos y rendimiento.

NOTA: En una indexación las réplicas sólo se utilizan como un lugar adicional para almacenar los datos. Cuando se ejecuta una consulta, de forma predeterminada, Elasticsearch intentará equilibrar la carga entre el fragmento y sus réplicas de una manera inteligente.

Glosario de terminos de ElasticSearch

Instalación / Directorios del paquete Elasticsearch

Para utilizar Elasticsarch solo es necesario descargarse el paquete / directorio comprimido, descomprimir y ejecutar, así de simple. También hay repositorios oficiales para las distros más importantes.

Elasticsearch Web: https://www.elastic.co/products/elasticsearch

Crear initscript para Elasticsearch: https://github.com/elastic/elasticsearch-servicewrapper/tree/master/service

Directorios de una instalación de Elasticsearch.

Problema con Java y elasticsearch.

[WARN ][common.network           ] failed to resolve local host, fallback to loopback

Solución: agregar el hostname del sistema a /etc/hosts para localhost.

127.0.0.1 HOSTNAME

Actualizar la Versión de ElasticSearch

Hay muchas manera de actualizar ElasticSearch, el método básico que requiere paralización del servicio por nodo es el siguiente. Se recomienda leer la documentación oficial.

  1. Parar el servicio ElasticSearch.
  2. Descomprimir la nueva versión y copiar los directorios data, config y plugins de la antigua versión a la nueva.
  3. Arrancar la nueva versión de ElasticSearch.

Configuración de Elasticsearch y recomendaciones.

Vigilar la cantidad el límite de descriptores de fichero que se pueden tener abiertos (> 35000) en /etc/security/limits.conf (Unix).

JVM: Establecer el límite de memoria a 1024 de la maquina virtual de Java y si se desea monitorizar para ajustar.

Valores a tener en cuenta en elasticsearch.yml.

Puertos de ElasticSearch.

Utiliza dos puertos, el 9200 y 9000, si no está disponible va probando con el siguiente +1 (Se puede ver en los logs del arranque).

APIs en ElasticSearch

En ES hay APIs de “documentos”, de “búsquedas”, de “índices”, “cat” y “clúster”. Dentro de alguna de ellas, podemos encontrar otras APIs, por ejemplo en la API de documentos tenemos CRUD API y Rest API. En esta guía no se verán todas las API, para ello está la guía oficial.

CRUD Api: “Create”, “read”, “update” y “delete”.

Single document APIs

Multi-document APIs

REST APi: Como todas las APIs Rest, tiene una interfaz web simple que utiliza XML y HTTP, por lo tanto la API CRUD es considerada también REST si interactuamos con ElasticSearch mediante HTTP. En la documentación de ES se mezclan muchos conceptos de API entre si que pueden llegar a confundir al lector.

API REST de Elasticsearch

Con la API REST de ElasticSearch podemos gestionar todo mediante http. Índices, cambiar parámetros de la instancia, verificar nodos, estado del clúster, datos del índice, buscar en los datos, recuperar documentos, etc.

Por ahora nos centraremos en el uso de la CRUD (crear, recuperar, actualizar y borrar), como si de una base de datos NoSQL se tratara.

En una arquitectura de REST cada solicitud se dirige a un objeto concreto indicado en la URL. Por ejemplo, si “/libros/” es una referencia a una lista de libros en nuestra biblioteca, “/libros/1” es la referencia al libro con el identificador 1. Estos objetos como es de suponer se pueden anidar, /libros/1/capítulo/6 referencia al sexto capítulo del primer libro de la biblioteca. El protocolo HTTP nos da una larga lista de tipos que se pueden utilizar como acciones en las llamadas a la API.

Sintaxis

curl -X<VERB> '<PROTOCOL>://<HOST>/<PATH>?<QUERY_STRING>' -d '<BODY>'
# No especificar GET en curl significa utilizarlo por defecto.
curl http://192.168.178.79:9200

{
  "status" : 200,
  "name" : "Diablo",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "1.5.1",
    "build_hash" : "5e38401bc4e4388537a615569ac60925788e1cf4",
    "build_timestamp" : "2015-04-09T13:41:35Z",
    "build_snapshot" : false,
    "lucene_version" : "4.10.4"
  },
  "tagline" : "You Know, for Search"
}

Comprobar configuración del clúster, sin utilizar “pretty” no se muestran saltos de linea.

curl -XGET http://192.168.178.79:9200/_cluster/health?pretty
{
  "cluster_name" : "escluster",
  "status" : "yellow",
  "timed_out" : false,
  "number_of_nodes" : 1,
  "number_of_data_nodes" : 1,
  "active_primary_shards" : 1,
  "active_shards" : 1,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 1,
  "number_of_pending_tasks" : 0
}

NOTA:Si se quiere conocer cuantos shards contiene cada nodo y el espacio que ocupan en disco.

curl -qs "http://dominios:9200/_cat/allocation?v"

shards disk.used disk.avail disk.total disk.percent    host         ip          node

     0    18.2gb     66.9gb     85.1gb           21    dominio      10.0.201.23 elastic1
  9963     563gb   1550.6gb       11tb           50    dominio      10.0.202.12 elastic2
     0     7.6gb     77.5gb     85.1gb            8    dominio      10.0.203.21 elastic3
     0        0b                                       dominio      10.0.200.36 kibana
  9962   509.2gb   1187.4gb    7696.7gb           73   dominio      10.0.204.71 elastic4
     0     8.9gb    115.4gb     124.3gb            7   dominio      10.0.205.70 elastic5
  9963   528.3gb   1563.7gb         9tb           48   dominio      10.0.206.15 elastic6

Revisar la configuración de los nodos del clúster, útil para saber en qué directorios del sistema se encuentran los directorios de ES.

 curl "localhost:9200/_nodes/settings?pretty=true"
{
  "cluster_name" : "es",
  "nodes" : {
    "09dBkfM6SzC0k7g_q6sLwg" : {
      "name" : "es1",
      "transport_address" : "inet[/192.168.178.115:9300]",
      "host" : "es1",
      "ip" : "127.0.0.1",
      "version" : "1.7.1",
      "build" : "b88f43f",
      "http_address" : "inet[/192.168.178.115:9200]",
      "attributes" : {
        "master" : "false"
      },
      "settings" : {
        "pidfile" : "/var/run/elasticsearch/elasticsearch.pid",
        "path" : {
          "conf" : "/etc/elasticsearch",
          "data" : "/var/lib/elasticsearch",
          "logs" : "/var/log/elasticsearch",
          "work" : "/tmp/elasticsearch",
          "home" : "/usr/share/elasticsearch",
          "repo" : "/mnt/es_backups"
        },
        "cluster" : {
          "name" : "es"
        },
        "node" : {
          "name" : "es1",
          "data" : "true",
          "master" : "false"
        },
        "name" : "es1",
        "index" : {
          "number_of_shards" : "3"
        },
        "client" : {
          "type" : "node"
        },
        "bootstrap" : {
          "mlockall" : "true"
        },
        "config" : {
          "ignore_system_properties" : "true"
        }
      }
    },
    ...

Apagar / parar Elasticsearch.

# Cluster.
curl -XPOST 'http://localhost:9200/_cluster/nodes/_shutdown'
# Consultar IDs de nodos.
curl -XGET 'http://localhost:9200/_cluster/nodes/'
# Parar nodos por IDs.
curl –XPOST 'http://localhost:9200/_cluster/nodes/XXXX/_shutdown'
# Consultar el número de documentos
curl -XGET 'http://localhost:9200/_count?pretty'
# Consultar el número de documentos, como la anterior pero con body (Tieenen el mismo efecto).
curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
    "query": { "match_all": {} }
}'

Crear / Indexar / Consultar / Actualizar un nuevo Documento

Crear documento para Elasticsearch. Por ejemplo, imaginemos que estamos creando el sistema de categorías de nuestro blog. Una de las entidades (índice) en este blog es artículos y es la que vamos a agregar.

{
"id": "1",
"title": "New version of Elasticsearch released!",
"content": "Version 1.0 released today!",
"priority": 10,
"tags": ["announce", "elasticsearch", "release"]
}

Hay tipos de datos numéricos (priority, el id no cuenta como tal), texto (title) y arrays (tags).

Indexar / Agregar al índice el documento.

Agregar al Índice el documento. Índice:blog, Tipo:artículo, Identificador:1

curl -XPUT http://192.168.178.79:9200/blog/article/1 -d '{"title": "New version of Elasticsearch released!", "content": "Version 1.0 released today!", "tags": ["announce", "elasticsearch", "release"] }'
 
{"_index":"blog","_type":"article","_id":"1","_version":1,"created":true}

Si no se especifica un ID, Elasticsearch generará uno único.

curl -XPOST http://192.168.178.79:9200/blog/article/ -d '{"title": "New version of Elasticsearch released!", "content": "Version 1.0 released today!", "tags": ["announce", "elasticsearch", "release"] }'
 
{"_index":"blog","_type":"article","_id":"AUy_Oh16FzMRuRcZNvt3","_version":1,"created":true}

Si se están realizando scripts y se desea por algún motivo crear un documento inicializando un valor que luego irá cambiando por ejemplo con un contador, se puede utilizar “upsert” como muestra el siguiente ejemplo. La primera vez que se ejecute creará / indexará el documento inicializando counter a 0, cada vez que se repita ese mismo comando, counter irá sumando +1 a su valor. El uso de upsert y sus valores son ejecutados solo en la creación del documento, después no son utilizados.

curl -XPOST http://192.168.178.79:9200/blog/article/3/_update -d '{
"script": "ctx._source.counter += 1",
"upsert": {
"counter" : 0
}
}'

Automáticamente nos ha creado el índice blog, pero si solo queremos crear índice “blog” se debe hacer lo siguiente.

curl -XPUT http://localhost:9200/blog/
{"acknowledged":true}

Consultar y Borrar un documento. (Utilizar “?pretty” para una salida tabulada)

Por cada actualización la versión aumentará en +1. Cuando el valor de “exists” es false quiere decir que no se ha encontrado el documento y no mostrará por tanto ningún campo “_source”.

curl -XGET http://192.168.178.79:9200/blog/article/1?pretty
# Existe.
{
  "_index" : "blog",
  "_type" : "article",
  "_id" : "1",
  "_version" : 3,
  "found" : true,
  "_source":{"title": "New version of Elasticsearch released!", "content": "Version 1.0 released today!", "tags": ["announce", "elasticsearch", "release"] }
}
 
# Borrar documento con ID: AUy_Oh16FzMRuRcZNvt3
curl -XDELETE http://192.168.178.79:9200/blog/article/AUy_Oh16FzMRuRcZNvt3
{"found":true,"_index":"blog","_type":"article","_id":"AUy_Oh16FzMRuRcZNvt3","_version":2}
 
curl -XGET http://192.168.178.79:9200/blog/article/AUy_Oh16FzMRuRcZNvt3?pretty
# No existe.
{
  "_index" : "blog",
  "_type" : "article",
  "_id" : "AUy_Oh16FzMRuRcZNvt3",
  "found" : false
}

Actualizar documentos en el índice.

Al actualizar documentos en el índice ElasticSearch debe primero buscar internamente el documento, tomar sus datos del campo “_source”, retirar el viejo documento, aplicar los cambios en el campo “_source”, y luego darlo de alta en el índice como un nuevo documento. La causa es que no se puede actualizar la información una vez que se almacena en un índice invertido Lucene.

# Modificaremos el campo content y editaremos la versión 1.0 a 10.2.
curl -XPOST http://192.168.178.79:9200/blog/article/1/_update -d '{"script": "ctx._source.content = \"Version 10.2 released today\""}'
 
{"_index":"blog","_type":"article","_id":"1","_version":4}
 
# Consultamos el tipo article del documento blog con el id 1.
curl -XGET http://192.168.178.79:9200/blog/article/1/?pretty
{
  "_index" : "blog",
  "_type" : "article",
  "_id" : "1",
  "_version" : 4,
  "found" : true,
  "_source":{"title":"New version of Elasticsearch released!","content":"Version 10.2 released today","tags":["announce","elasticsearch","release"]}
}
 
# Agregar un nuevo parámetro si no existe 
curl -XPOST http://192.168.178.79:9200/blog/article/3/_update -d '{"script": "ctx._source.Nuevocampo = \"AAAAAAAAAAAA\""}'

Por motivos de seguridad que se explicarán posteriormente, si no se tiene la directiva “script.disable_dynamic: false” se obtendrá un mensaje como el siguiente.

{"error":"ElasticsearchIllegalArgumentException[failed to execute script]; nested: ScriptException[dynamic scripting for [groovy] disabled]; ","status":400}

El campo “version” en Elasticsearch.

Elasticsearch incrementa la versión del documento cuando este ha sido creado, cambiado o borrado. Puede ser útil para implementar control de concurrencia (leer) en el proyecto que se tenga en mente si así se desea / necesita y evitar problemas al hacer actualizaciones en paralelo sobre el mismo documento.

Para ello se puede utilizar el campo versión en la solicitud.

curl -XGET "http://192.168.178.79:9200/blog/article/1/?version=1&pretty"
# La versión actual es 17, pero se intenta borrar dada la versión 1 mostrando el siguiente error.
{
  "error" : "VersionConflictEngineException[[blog][2] [article][1]: version conflict, current [17], provided [1]]",
  "status" : 409
}

ElasticSearch también se puede basar en el número de versión proporcionado por nosotros, para ello debe ser utilizado “version” y “version_type” como en el siguiente ejemplo.

curl -XPOST "http://192.168.178.79:9200/blog/article/8/_update?version=123454&version_type=external" -d '{...}

Generar datos para comenzar a practicar con ElasticSearch (stream2es) + Plugins

Enlace: https://gist.github.com/clintongormley/8579281 (necesario para seguir los ejemplos de la guía oficial).
Enlace: stream2es

Plugins recomendados para empezar a trabajar con ElasticSearch: Kopf / Marvel (Consola Sense).

./bin/plugin -i lmenezes/elasticsearch-kopf/{version}

URL Kopf: http://localhost:9200/_plugin/kopf
URL Marvel: http://localhost:9200/_plugin/marvel/

Búsquedas en Elasticsearch (Querys / Filters)

Se pueden buscar y consultar índices, tipos y documentos.

Es posible especificar múltiples índices y tipos (Multi-index, Multitype).

# Buscar documentos en el  índice blog.
curl -XGET "http://192.168.178.79:9200/blog/_search"
 
# Consultar dos índices (blog,blog2).
curl -XGET "http://192.168.178.79:9200/blog,blog2/_search"
 
# Consultar todos los índices blog y blog2 para mostrar su tipo de documento "artículo".
curl -XGET "http://192.168.178.79:9200/blog*/artículo/_search"
 
# Consultar todos los índices y filtrar por el tipo de documento "artículo" en todos los índices.
curl -XGET "http://192.168.178.79:9200/_all/artículo/_search"
 
# Consultar toda la información sobre el cluster de Elasticsearch.
 
curl -XGET "http://192.168.178.79:9200/blog/_search?pretty&q=title:elasticsearch'

Interpretar campos en respuestas Elasticsearch / Filtrar por el valor de campos de documento.

Filtrando por el campo título valor “elastic” + Cualquier otro texto a continuación (*).

curl -XGET "http://192.168.178.79:9200/blog/_search?pretty&q=title:elasticsear*"
{
  "took" : 2,             # Tiempo en obtener la respuesta en milisegundos.
  "timed_out" : false,    # Si se ha alcanzado el timeout de la consulta. Se puede definir con "timeout"
  "_shards" : {           
    "total" : 5,          # Fragmentos requeridos para la resolver la consulta.
    "successful" : 5,     # Fragmentos de los que se obtuvo una respuesta exitosa.
    "failed" : 0          # Fragmentos que fallaron en la búsqueda (ej. nodos no accesibles).
  },
  "hits" : {
    "total" : 1,               # Total de documentos obtenidos.
    "max_score" : 1.0,         # Puntuación máxima calculada.
    "hits" : [ {           
      "_index" : "blog",       # Índice del documento.
      "_type" : "article",     # Tipo de documento.
      "_id" : "1",             # Identificador.
      "_score" : 1.0,          # Puntuación y a continuación en _source, normalmente el documento Json.
      "_source":{"title":"New version of Elasticsearch released!","content":"Version 10.2 released today","tags":["announce","elasticsearch","release"],"campo":"XXXXXXXX","campo2":"XXXXXXXX","counter":"1","campo3":"XXXXXXXX"}
    } ]
  }
}

Durante la indexación, la biblioteca Lucene subyacente analiza los documentos y los índices de los datos de acuerdo a la configuración Elasticsearch. Por defecto ES indicará a Lucene que indexe y analice tanto los datos basadas en cadenas como en números.

Query DSL (Solicitudes DSL)

Se basa en la interfaz JSON para comunicarse con ES de una forma más flexible, fácil, y precisa que con la URL. Aunque nos referimos en el título como consulta DSL, en realidad hay dos DSL: consulta DSL y filtro DSL, son similares en naturaleza, pero tienen diferentes propósitos.

Un filtro a diferencia de las querys permite hacer una consulta de sí o no en todos los documentos y se utiliza para campos que contienen valores exactos. El objetivo de filtros es el de reducir el número de documentos que tienen que ser examinado por la consulta.

Los filtros se encargarían por ejemplo de preguntar si el campo “created” tiene una fecha entre 2013 - 2014, si el campo “status” contienen el término “published”, etc.

Lógicamente los filtros son más rápidos ya que las consultas tienen que encontrar no sólo documentos coincidentes, sino también calcular la relevancia de cada documento y mostrar su salida ordenada.

Como regla general, se debe utilizar las cláusulas de las querys para la búsqueda de texto completo o para cualquier condición que debe afectar a la puntuación de relevancia. El uso de las cláusulas de filtro se debe utilizar para todo lo demás.

Estructura de una Query DSL.

{
    QUERY_NAME: {
        FIELD_NAME: {
            ARGUMENT: VALUE,
            ARGUMENT: VALUE,...
        }
    }
}

Preguntando por una frase (literal) en Elasticsearch en el índice wiki.

POST /wiki/_search
{
    "query": {
        "match_phrase": {
            "text": "idioma castellano"
        }
    }
}

Consultas / filtros más importantes y habituales

Enlace: Querys y filtros en profundidad

Filtros.

term y terms: Filtra por valores exactos (números, fechas, booleanos o no analizados)

{ "term": { "age":    26           }}
{ "term": { "date":   "2014-09-01" }}
{ "term": { "public": true         }}
{ "term": { "tag":    "full_text"  }}

con terms podemos especificar multiples valores.

{ "terms": { "tag": [ "search", "full_text", "nosql" ] }}

range: Permite filtrar por rangos de fechas y números. gt (>), gte (= >), lt (<), lte(= <).

{
    "range": {
        "age": {
            "gte":  20,
            "lt":   30
        }
    }
}

exists y missing: Si se encuentra o no en el documento un determinado campo.

{
    "exists":   {
        "field":    "title"
    }
}

bool: Permite unir varios filtros con sus parámetros must (and), must_not (not) y should (or).

{
    "bool": {
        "must":     { "term": { "folder": "inbox" }},
        "must_not": { "term": { "tag":    "spam"  }},
        "should": [
                    { "term": { "starred": true   }},
                    { "term": { "unread":  true   }}
        ]
    }
}

Querys.

match_all: Busca en todos los documentos, la predeterminada si no se especifica otra. Por norma se usa junto a filtros. Estas dos consultas serían iguales. (match_all es el equivalente a “{}”)

POST /_search
{
    "query": {
        "match_all": {}
    }
}
GET /_search{}
POST /_search{}

match: Busca un fulltext o exact value (para ello siempre mejor un filtro) en al menos un campo.

{ "match": { "tweet": "About Search" }}

Si el campo contiene un valor exacto, tal como un número, una fecha, un booleano, o un campo de cadena not_analyzed, se buscará dicho que el valor exacto.

{ "match": { "age":    26           }}
{ "match": { "date":   "2014-09-01" }}
{ "match": { "public": true         }}
{ "match": { "tag":    "full_text"  }}

multi_match: Permite hacer la misma query en múltiples campos.

{
    "multi_match": {
        "query":    "full text search",
        "fields":   [ "title", "body" ]
    }
}

bool: Al igual que el filtro bool, permite combinar varias cláusulas de consulta.

{
    "bool": {
        "must":     { "match": { "title": "how to make millions" }},
        "must_not": { "match": { "tag":   "spam" }},
        "should": [
            { "match": { "tag": "starred" }},
            { "range": { "date": { "gte": "2014-01-01" }}}
        ]
    }
}

Combinar filtros y querys.

POST /wiki/_search
{
  "query": {
    "filtered": {
      "query": {
        "match_phrase": {
          "title": "The Hound of Heaven"
        }
      },
      "filter": {
        "term": {
          "special": false,
          "redirect": false
        }
      }
    }
  }
}

Consultar mútiples campos en una query DSL (También aplicable a los filtros).

POST /wiki/_search
{
  "query": {
    "bool": {
      "must": [
        { "match_phrase": { "text": "In paradise, fast by the tree of life" }},
        { "match_phrase": { "title": "Amaranth" }},
        { "match": { "text": "Oscar synonym" }}
      ]
    }
  }
}

Explicación: Debe incluir las cadenas “In paradise, fast by the tree of life” y “Amaranth” en sus respectivos campos y luego, en el campo text debe encontrarse también o bien “Oscar” o bien “synonym”. Recordemos que must se comporta como un “AND” y should como “OR”.

Clausulas y sus tipos (leaf / compound).

Analizar las solicitudes.

Documentación: http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-analyze.html

En este ejemplo se puede ver como Elasticsearch divide el texto en dos términos.

 curl -XGET "http://192.168.178.79:9200/blog/_analyze?field=title&pretty" -d 'Elasticsearch Server'
 
{
  "tokens" : [ {
    "token" : "elasticsearch",
    "start_offset" : 0,
    "end_offset" : 13,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "server",
    "start_offset" : 14,
    "end_offset" : 20,
    "type" : "<ALPHANUM>",
    "position" : 2
  } ]
}

Buscar dentro de campos fulltext cadenas de texto literales: ej. “caca de vaca

Parámetros utilizados en las consultas con Elasticsearch

Enumeración y explicación básica de algunos parámetros utilizados en las consultas, no preocuparse si algo no se entiende ya que se verá más tarde en profundidad.

curl -XGET 'localhost:9200/books/_search?pretty&q=published:2013&df=title&explain=true&default_operator=AND'

NOTA: Es importante utilizar comillas al especificar con curl parámetros en la URL, así se evita que la shell interprete el carácter “&”.

Por defecto Elasticsearch devuelve los resultados ordenados por puntuación de forma descendiente. De forma predeterminada, para una consulta dada se devuelven siempre los siguientes parámetros.

  1. Nombres del índice.
  2. Identificador del documento.
  3. Puntuación.
  4. Campo “_source”.

Para inferir en los resultados devueltos por Elasticsearch se pueden usar los siguientes parámetros.

Search Lite / Sintaxis de búsqueda Lucene

Hay dos formas de usar la api “search”: Lite y DSL

  1. Lite: Todos los parámetros son pasados en la URL.
  2. DSL: Tiene su lenguaje propio y espera siempre una solicitud json.

La sintaxis de búsqueda Lucene es utilizada para el parámetro “q” (query) de ElasticSearch.

Documentación completa sobre Sintaxis a utilizar en las querys: query-string-syntax y Lucene syntax.

A grandes rasgos y como introducción al anterior enlace propuesto, las búsquedas están divididas en términos (individuales y frases) y operadores booleanos (+ / -).

Una consulta que indique más de un termino y alguno carezca de “+” o “-” indica que el termino puede estar o no en el documento. Si se desea encontrar un documento con el termino “libro” pero no “gato” se puede especificar lo siguiente “+title:libro -description:gato”.

Para dar más relevancia a un determinado término se usa “^”: title:book^4, esto aumentará la puntuación.

Paréntesis para especificar múltiples términos de búsqueda en cualquier orden y somo si fuera un OR: “title:(crime punishment)”

Wildcards (No al principio de la consulta): “*” (Cualquier carácter/s) y “?” Un carácter. Si se quieren utilizar al principio de la consulta se puede utilizar: allow_leading_wildcard false (no recomendable por temas de rendimiento).

c\*po:(blanco negro) Se pueden especificar wildcards en el nombre del campo si se escapan con “\” (ej. campo).

_missing_:Nuevocampo” Muestra los documentos que carezcan de un determinado campo o bien esté vacío “_exists_:title” Muestra documentos con campo “title” no nulo (con contenido)

Se pueden utilizar patrones con expresiones regulares si van dentro de “/”, ej. “name:/joh?n(ath[oa]n)/” (Leer)

Fuzziness: Buscar palabras parecidas utilizando “~”, se puede nivelas incluyendo un numero “~3”, por defecto es 2 (Leer)

Ejemplos como pruebas de concepto.

curl -XGET "http://dominio:9200/blog/_search?pretty&q=+Nuevocampo:(AAAAAAA+BBBB)+counter:(8+3)&default_operator=OR"
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.41285858,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "article",
      "_id" : "3",
      "_score" : 0.41285858,
      "_source":{"counter":3,"cadeba":"CCCCCCCC","Nuevocampo":"AAAAAAA"}
    }, {
      "_index" : "blog",
      "_type" : "article",
      "_id" : "4",
      "_score" : 0.03978186,
      "_source":{"counter":50,"Nuevocampo":"AAAAAAA BBBB"}
    } ]
  }
}
 
# Para dar más puntuación al segundo resultado, se podría utilizar "^".
curl -XGET "http://dominio:9200/blog/_search?pretty&q=Nuevocampo:(AAAAAAA+BBBB^9)&default_operator=OR"
{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.2117889,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "article",
      "_id" : "4",
      "_score" : 0.2117889,
      "_source":{"counter":50,"Nuevocampo":"AAAAAAA BBBB"}
    }, {
      "_index" : "blog",
      "_type" : "article",
      "_id" : "3",
      "_score" : 0.03274158,
      "_source":{"counter":3,"cadeba":"CCCCCCCC","Nuevocampo":"AAAAAAA"}
    } ]
  }
}
 
# Si se usa el operador AND no se mostraría ningún resultado.
curl -XGET "http://dominio:9200/blog/_search?pretty&q=+Nuevocampo:(AAAAAAA+BBBB)+counter:(8+3)&default_operator=AND"
 
# Si se especifica que no muestre ningún documento con el valor de counter 50 o 3 tampoco motraría nada.
curl -XGET "http://dominio:9200/blog/_search?pretty&q=+Nuevocampo:(AAAAAAA+BBBB)-counter:(50+3)&default_operator=OR"
 
# Esta consulta devolvería un resultado si se evita solo un valor de counter y se utiliza AND.
curl -XGET "http://dominio:9200/blog/_search?pretty&q=+Nuevocampo:(AAAAAAA+BBBB)-counter:(3)&default_operator=AND"
{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.2712221,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "article",
      "_id" : "4",
      "_score" : 0.2712221,
      "_source":{"counter":50,"Nuevocampo":"AAAAAAA BBBB"}
    } ]
  }
 
# Muestra el documento que tenga esos dos términos y en ese mismo orden.
curl -XGET "http://dominio:9200/blog/_search?pretty&q=Nuevocampo:\"AAAAAAA/**/BBBB\""
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.38356602,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "article",
      "_id" : "4",
      "_score" : 0.38356602,
      "_source":{"counter":50,"Nuevocampo":"AAAAAAA BBBB"}
    } ]
  }
}
 
# No aparece ningún resultado con el siguiente comando ya que no es el orden correcto
curl -XGET "http://dominio:9200/blog/_search?pretty&q=Nuevocampo:\"BBBB/**/AAAAAAA\""
 
 
# Si se usan los paréntesis se mostrará resultados ya que por defecto usa OR y si no encuentra un termino encontrará el otro, igual el orden.
 
curl -XGET "http://dominio:9200/blog/_search?pretty&q=Nuevocampo:(BBBB/**/AAAAAAA)"
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.15109822,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "article",
      "_id" : "3",
      "_score" : 0.15109822,
      "_source":{"counter":3,"cadeba":"CCCCCCCC","Nuevocampo":"AAAAAAA"}
    }, {
      "_index" : "blog",
      "_type" : "article",
      "_id" : "4",
      "_score" : 0.013555458,
      "_source":{"counter":50,"Nuevocampo":"AAAAAAA BBBB"}
    } ]
  }
}
 
# Utilizando un comodín el el nombre del campo
curl -XGET "http://dominio:9200/blog/_search?pretty&q=Nuevocampo:\"AAAAAAA/**/BBBB\""
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.38356602,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "article",
      "_id" : "4",
      "_score" : 0.38356602,
      "_source":{"counter":50,"Nuevocampo":"AAAAAAA BBBB"}
    } ]
  }
}
 
# Muestra los documentos que carezcan de un determinado campo o bien esté vacío, en este caso "Nuevocampo".
curl -XGET "http://dominio:9200/blog/_search?pretty&q=_missing_:Nuevocampo"
 
# Muestra documentos con campo "title" no nulo (con contenido).
curl -XGET "http://dominio:9200/blog/_search?pretty&q=_exists_:title"
 
# Buscará palabras que se parezcan a Elasticserach (por ejemplo, elasticsearch).
curl -XGET "http://dominio:9200/blog/_search?pretty&q=title:Elasticserach~"

Un cliente vía web muy simple que no requiere ningún tipo de instalación es Elasticsearch query client, permite buscar, editar y eliminar indexaciones. para que pueda conectar al clúster se debe agregar el siguiente parámetro al fichero de configuración elasticsearch.yml

http.cors.enabled: true

Creación de Índices

La creación de índices de manera automática puede ser un problema si se comete un simple error tipográfico en un envío masivo de datos, lo normal es deshabilitar la opción de creación automática de índices en elasticsearch.yml.

action.auto_create_index: false

la siguiente definición permite la creación automática únicamente de índices que comienzan con “a”, pero no “an”.

action.auto_create_index: -an*,+a*,-*

El orden juega un papel importante ya que ejecuta la primera coincidencia, de poner +a delante se anularía la restricción “-an”.

La creación manual de un índice es necesaria para especificar la cantidad de fragmentos y sus réplicas, para el ejemplo se crearán tres índices lucene (el primario y dos copias).

curl -XPUT http://localhost:9200/blog/ -d '{
"settings" : {
"number_of_shards" : 1,
"number_of_replicas" : 2
}
}'

Mostrar todos los índices.

curl 'localhost:9200/_cat/indices?v'
health status index              pri rep docs.count docs.deleted store.size pri.store.size 
green  open   .marvel-2015.05.13   1   1        596            0      3.1mb          1.5mb 
green  open   .marvel-2015.04.24   1   1       2771            0      8.6mb          4.3mb 
green  open   .marvel-2015.05.14   1   1        634            0      3.2mb          1.6mb 
green  open   blog                 5   1          8            0     45.5kb         22.8kb 

Mapping

Aunque Elasticsearch es un motor de búsqueda sin esquemas, se puede averiguar la estructura de datos sobre la marcha, pero se recomienda especificar una estructura al momento de crear el índice. Mapping = schema mapping = Definir estructura del índice.

A veces los valores numéricos se proporcionan dentro de un campo de cadena, para que reciban el tratamiento numérico que merece se puede utilizar la opción “numeric_detection” : true al crear la estructura del índice (mapping).

curl -XPUT http://localhost:9200/blog/?pretty -d '{
"mappings" : {
"article": {
"numeric_detection" : true
}
}
}'

Otro tipo de dato que puede causar problemas es el de fecha. Elasticsearch intenta adivinar fechas dadas como marcas de tiempo o cadenas que coinciden con el formato de fecha. Podemos definir la lista de formatos de fecha reconocidas utilizando la propiedad “dynamic_date_formats” para especificar uno o varios formatos a utilizar. Este comando crea un índice con un único tipo: article.

curl -XPUT 'http://localhost:9200/blog/' -d '{
"mappings" : {
"article" : {
"dynamic_date_formats" : ["yyyy-MM-dd hh:mm"]
}
}
}'

Formatos de fecha usados en Elasticsearch: http://joda-time.sourceforge.net/api-release/org/joda/time/format/DateTimeFormat.html

Para desactivar la agregación de campos de forma automática se debe establecer la propiedad “dynamic” a false. Cualquier intento de agregación de campos diferentes a los definidos (id, content y author) en el tipo “article” será ignorado.

curl -XPUT 'http://localhost:9200/blog/' -d '{
"mappings" : {
"article" : {
"dynamic" : "false",
"properties" : {
"id" : { "type" : "string" },
"content" : { "type" : "string" },
"author" : { "type" : "string" }
}
}
}
}'

Supongamos que queremos crear un índice con las siguiente estructura:

Creamos el fichero “posts.json” para no utilizar siempre la linea de comandos al crear el index.

curl -XPOST 'http://localhost:9200/posts' -d @posts.json
{
"mappings": {
"post": {
"properties": {
"id": {"type":"long", "store":"yes","precision_step":"1" },
"name": {"type":"string", "store":"yes","index":"analyzed" },
"published": {"type":"date", "store":"yes","precision_step":"1" },
"contents": {"type":"string", "store":"no","index":"analyzed" }
}
}
}
}

Tipos de Datos

Tipos de datos principales (Core Type).

Core fields: Atributos para todos los tipos de datos (No para el tipo de dato Binary)

String: Para las búsquedas de texto, además de tener los atributos principales, tiene otros específicos que no explicaremos aquí.

Number: Estos son los tipos de datos numéricos.

Ejemplo de definición un campo “number”.

"price" : { "type" : "float", "store" : "yes", "precision_step" : "4"}

Atributos específicos de los tipos numéricos.

precision_step: Número de términos generado para cada valor en un campo, por defecto 4. Cuanto menor sea el valor, mayor es el número de términos generado. A mayor número de términos por valor mayor velocidad en las consultas a costa de aumentar el tamaño del incide.

ignore_malformed: Este atributo puede tomar el valor verdadero o falso (por defecto). Necesita true para no permitir valores con formato erróneo.

Boolean

Puede ser true y false.

Binary

Se almacenan los ficheros (mp3, pdf, png, exe,…) en Base64. Este campo no permite ser buscado, se debe utilizar index_name.

"image" : { "type" : "binary" }

Date

Se utilizan para indexar fechas.

"published" : { "type" : "date", "store" : "yes", "format" :"YYYY-mm-dd" }

format : Este atributo especifica el formato de la fecha, por defecto “dateOptionalTime” ej. 2012-07-14T12:30:00. Listado de formatos.

precision_step :Número de términos generados por cada valor del campo. Por defecto 4.

ignore_malformed : true / false (por defecto).

NOTA: Un término se refiere a un valor exacto, no es lo mismo Caca, CACA y CaCa.

Multicampos

Se utilizan para tener los mismos valores en varios campos core_types), por ejemplo, uno para la búsqueda y otro para clasificación.

{
    "tweet" : {
        "properties" : {
            "name" : {
                "type" : "multi_field",
                "fields" : {
                    "name" : {"type" : "string", "index" : "analyzed"},
                    "untouched" : {"type" : "string", "index" : "not_analyzed"}
                }
            }
        }
    }
}

La definición anterior creará dos campos: nos referiremos a la primera como el nombre de “name” y el segundo “name.facet”. No se tiene que especificar dos campos separados durante la indexación, con “name” es suficiente.

Direcciones IP

Tiene el atributo precision_step a valor de 4, se recomienda aumentar para búsquedas por rangos.

"address" : { "type" : "ip", "store" : "yes" }

Tipo token_count

Almacena el token en vez de la cadena en si, se usa con analizadores. Lo mejor para el rendimiento es no usar tokens con analizadores.

Campos de Valores exactos VS Campos Full text

Exact value: Fechas, IDs,… El valor exacto “2014” no es lo mismo que el valor 2014-02-11. Son fáciles de buscar y operar.

Full text Este tipo de campos usa índices invertidos, para crearlos, trocea el texto en palabras (llamadas “terms” o “Tokens”) y crea una lista ordenada con los términos únicos y en qué documentos se encuentran. Así el documento que más palabras de la búsqueda contenga será el elegido. También reduce a minúsculas las términos, conoce sinónimos, reduce las palabras a su raíz (elimina conjugaciones, plurales, etc), etc.

Análisis y Analizadores

El proceso de análisis consiste en tokenizar (dividir) un texto en términos individuales para ser usados en un índice invertido y después los normaliza.

Dicho proceso lo realiza el analizador, encontramos algunos de ellos ya de serie en ES, estos combinan estas tres funciones.

Por estas mismas tres fases deben pasar las búsquedas sobre ese campo full-text para que sea efectivo.

GET /_search?q=2014              # 12 results Se pregunta al campo _all (full text).
GET /_search?q=2014-09-15        # 12 results Se vuelve a preguntar al campo all, si encuentra el valor 2014 o 09 o 15, por lo que mínimo devuelve 12 resultados.
GET /_search?q=date:2014-09-15   # 1  result Ahora se ha preguntado al campo date, el cual tiene un valor exacto y el resultado es uno porque solo hay un valor 2014-09-15.
GET /_search?q=date:2014         # 0  results Si la anterior petición con 2014-09-15 devolvió un resultado, esta lógicamente debe devolver ninguno.

Por ejemplo, cuando nos dividimos las cadenas de texto palabras con los espacios en blanco y caracteres en minúsculas, no tenemos que preocuparnos por los usuarios que envían palabras en minúsculas o mayúsculas. Los analizadores son muy usados para mostrar sugerencias en campos de búsqueda según se teclea la palabra deseada. Leer

Elasticsearch nos permite utilizar diferentes analizadores en el momento de la indexación y diferentes analizadores en el momento de la consulta. Podemos elegir cómo queremos que nuestros datos sean procesados en cada etapa del proceso de búsqueda. Para utilizar uno de los analizadores, sólo tenemos que especificar su nombre a la propiedad correcta del campo.

Los analizadores en Elasticsearch se componen de:

Elasticsearch proporciona una serie de Analyzers, aunque nos permite customizarlos en caso de que no haya alguno que se ajuste a nuestras necesidades. Esta customización consiste en seleccionar la combinación de Tokenizer y TokenFilters.

Analizadores Out-of-the-box.

NOTA: El Stemming es le proceso de reducir palabras a su forma más básica, como es “coches” - “coche”, permitiendo que una búsqueda encuentre “coches” simplemente buscando “coche”.

Valor de los documentos

Define donde se va a encontrar un valor, si en disco o en memoria RAM.

{
  "mappings" : {
     "post" : {
        "properties" : {
          "id" : { "type" : "long", "store" : "yes", "precision_step" : "0" },
          "name" : { "type" : "string", "store" : "yes", "index" : "analyzed" },
          "contents" : { "type" : "string", "store" : "no", "index" : "analyzed" },
          "votes" : { "type" : "integer","doc_values_format" : "memory" }
        }
      }  
  }
}

default: Este es un formato de valores doc que se utiliza cuando no se especifica ningún formato. Ofrece un buen rendimiento con bajo uso de memoria.

disk: Almacena los datos en el disco sin requerir prácticamente memoria. Sin embargo, existe una degradación de rendimiento al utilizar esta estructura de datos para operaciones de parseo o clasificación.

memory: Se trata de un formato de valores doc que almacena los datos en la memoria y ofrece el mayor rendimiento. En el ejemplo el campo votes es un buen candidato debido a que se puede utilizar regularmente para clasificaciones.

Indexación por lotes

En vez de indexar documentos uno por uno se puede realizar por lotes. ElasticSearch permite fusionar muchas peticiones en un solo lote.

"error" : "DocumentAlreadyExistsException[[direccion][1] [contact][5]: document already exists]"

Los ficheros por lotes se deben definir sin tabulaciones, todo en una linea y no más de 100Mb por fichero de lotes, pero se puede adaptar con la directiva http.max_content_length.

Cada linea de un fichero por lotes es un objeto JSON con una descripción de la operación a realizar + el objeto JSON en si. Solo cuando se quiere borrar un documento se debe especificar únicamente la operación.

{ "index": { "_index": "addr", "_type": "contact", "_id": 1 }}
{ "name": "Fyodor Dostoevsky", "country": "RU" }
{ "create": { "_index": "addr", "_type": "contact", "_id": 2 }}
{ "name": "Erich Maria Remarque", "country": "DE" }
{ "create": { "_index": "addr", "_type": "contact", "_id": 2 }}
{ "name": "Joseph Heller", "country": "US" }
{ "delete": { "_index": "addr", "_type": "contact", "_id": 4 }}
{ "delete": { "_index": "addr", "_type": "contact", "_id": 1 }}
curl -XPOST 'localhost:9200/_bulk?pretty' --data-binary @test.json

NOTA: En el indexado por lotes, si se necesita más velocidad, se puede usar User Datagram Protocol (UDP), pero solo en hambientes donde la velocidad prima sobre la pérdida de datos.

Campos importantes

En ES hay dos tipos de identificadores internos para los documentos: “_uid” y “_id”.

  1. _uid: Único identificador del documento compuesto de un identificador (_id) y un tipo. Documentos de diferente tipo que están en el mismo índice pueden tener el mismo identificador para que ElasticSearch pueda distinguirlos.
  2. _id: Guarda el identificador durante el tiempo de indexado, se pueden definir sus propiedades en el mapping, por ejemplo si solo queremos que se indexe pero no analice y no se almacene en el índice (Cuidado: en este caso los documentos requerirán un _uid único).
{
"book" : {
  "_id" : {
         "index": "not_analyzed",
         "store" : "no"
        },
  "properties" : {
.
.
.
}

Si queremos usar un “_id” igual a otro campo del documento indexado es también posible.

{
"book" : {
"_id" : {
"path": "book_id"
},
"properties" : {
.
.
.
}
}
}

Campo _type.

Como dijimos antes, cada documento en ES es descrito por su identificador (_id) y tipo. Por defecto los tipos son indexados pero no analizados ni almacenado (No pueden ser buscados ni operar con ellos utilizando expresiones regulares).

Buscar por tipo “contact” en todos los índices.

curl -XGET 'localhost:9200/_all/contact/_search?q=name:Dav*+country:ES&default_operator=AND&pretty'
 
# Como el tipo "contact" no es guardado ni analizado esta consulta con expresión regular "conta*" no funcionará.
curl -XGET 'localhost:9200/_all/conta*/_search?q=name:Dav*+country:ES&default_operator=AND&pretty'

Campo _all

Contiene los datos de todos los otros campos para facilitar así las búsquedas. Esto permite hacer búsquedas cuando no se quieren especificar campos y se quiere buscar en todos los índices, por eso esta consulta muestra el mismo resultado que la anterior (no especifica el tipo “contact”).

curl -XGET 'localhost:9200/_all/_search?q=name:Dav*+country:ES&default_operator=AND&pretty'

Está siempre habilitado y hace que los índices sean algo más grandes pero no en todos los casos será necesario. Para decidir si un campo se debe o no guardar en _all se puede utilizar include_in_all

Si es habilitado tiene las siguientes propiedades configurables: store, term_vector, analyzer, index_analyzer y search_analyzer.

Campo _source.

Permite guardar el documento JSON enviado durante la indexación y si no se define otro campo, puede ser utilizado también para funcionalidades de resaltado (highlighting). como los demás también puede ser deshabilitado al definir el mapa.

Campo _index.

Guarda información sobre le índice a los cuales los documentos son indexados, por defecto _index no se indexa, para ello se debe especificar.

Campo _size

Por defecto no habilitado, si se habilita especifica el tamaño de _source y es almacenado en el documento. Para activarlo

{
"book" : {
"_size" : {
"enabled": true,
"store" : "yes"
},
"properties" : {
.
.
.
}
}
}

Campo _timestamp.

Desactivado por defecto. Especifica cuando el documento fue indexado, es solo indexado pero no guardado ni analizado.

Campo _ttl.

EL tiempo de vida de un documento, por defecto viene desactivado. Si se activa es indexado y guardado, pero no analizado.

{
"book" : {
"_ttl" : {
"enabled" : true,
"default" : "30d"
},
"properties" : {
.
.
.
}
}
}

Validar consultas

Las consultas se pueden validar sintácticamente para averiguar si es correcta una consulta o por qué motivo falla.

Sintaxis

/_validate/query
/_validate/query?explain
POST /wiki/page/_validate/query?explain
{
  "query": {
    "bool": {
      "must": [
        { "match_phrase": { "text": "He served as alderman and mayor" }},
        { "match_phrase": { "title": "Andrew Johnson" }},
        { "match": { "text": "Ohios John Sherman" }}
      ]
    }
  }
}

Repuesta (Validación): (Valida la query y la explica)

{
   "valid": true,
   "_shards": {
      "total": 1,
      "successful": 1,
      "failed": 0
   },
   "explanations": [
      {
         "index": "wiki",
         "valid": true,
         "explanation": "filtered(+text:\"he served as alderman and mayor\" +title:\"andrew johnson\" +(text:ohios text:john text:sherman))->cache(_type:page)"
      }
   ]
}

Ordenar respuestas (Sort)

Cuando se ordena una lista de documentos, no se comprueban las puntuaciones, internamente se ponen todas a 1, los JSON resultantes tienen los campos “max_score” y “_score” a null. Si pese a ordenarlos se quiere mostrar también puntuación, se debe utilizar “track_scores”.

Los resultados devuelven también un nuevo campo “sort” que contiene el valor por el que fue ordenado, en campos de fechas aparecerá el valor en milisegundos desde epoch.

NOTA: Unix time = POSIX time = Epoch time (erróneamente nombrado).

Si no se indica orden, solo el campo, son ordenados ascendentemente. El _score por defecto es siempre descendente.

"sort": { "body": { "order": "asc" }} = "sort":  "body"

También es posible ordenar de manera multinivel, cuando son ordenados por el primer criterio y uno o dos tienen el mismo valor sort, se puede utilizar otro nivel para reoordenar la salida, por ejemplo por puntuación / relevancia “score”.

GET /blog/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "body": "should nonummy" }},
        { "match": { "title": "search lorem"  }}
      ]
    }
  }
  ,
    "sort": 
    [
    { "body": { "order": "desc" }},
    { "_score": { "order": "asc" }}
  ]
}

Cuando se ordena por campos que tienen varios valores, no hay ningún orden establecido, pero para números y fechas se puede utilizar “min”, “max”,“avg” o “sum”.

"sort": {
    "dates": {
        "order": "asc",
        "mode":  "min"
    }
}

Cuando se ordena por cadenas, estas deben tener un solo término y se puede usar “min” (por defecto) y “max”. Los campos string normalmente son analized y por lo tanto son divididos en varios términos. Para solucionar esto, se puede tener el mismo campo con dos propiedades diferentes, una para ser analizado y otra para ser ordenado. Si se quiere poder tener dentro del mismo documento el mismo campo con dos mappings diferentes se debe usar mapping multifield:

"tweet": { 
    "type":     "string",
    "analyzer": "english",
    "fields": {
        "raw": { 
            "type":  "string",
            "index": "not_analyzed"
        }
    }
}

Ejemplo de uso.

GET /_search
{
    "query": {
        "match": {
            "tweet": "elasticsearch"
        }
    },
    "sort": "tweet.raw"
}

Puntuación / Relevancia de los documentos

La puntuación es representada por un número de coma flotante, cuanto más alto, más puntuación, más relevante. La puntuación es aplicada tanto a filtros como a consultas. Para calcular la puntuación se tiene en cuenta lo siguiente.

Para entender como se ha calculado la puntuación para cada resultado devuelto y para depuración, se utiliza: “/_search?explain” o “_search?explain&format=yaml” (más legible) donde se pueden ver la suma de puntuaciones, la frecuencia de los términos en el campo, en los documentos y las dimensiones del campo.

Para saber por qué un documento es o no devuelto por una query, se puede usar “explain”. Por ejemplo, veamos la siguiente consulta directamente el campo “_id”.

GET /us/tweet/12
{
   "_index": "us",
   "_type": "tweet",
   "_id": "12",
   "_version": 5,
   "found": true,
   "_source": {
      "date": "2014-09-22",
      "name": "John Smith",
      "tweet": "Elasticsearch and I have left the honeymoon stage, and I still love her.",
      "user_id": 1
   }
}
GET /us/tweet/12/_explain
{
   "query" : {
      "filtered" : {
         "filter" : { "term" :  { "user_id" : 4           }},
         "query" :  { "match" : { "tweet" :   "honeyXXXmoon" }}
      }
   }
}

Devuelve la siguiente explicación. (No se encontró ni el user_id 4 ni la cadena honeyXXXmoon)

{
   "_index": "us",
   "_type": "tweet",
   "_id": "12",
   "matched": false,
   "explanation": {
      "value": 0,
      "description": "failure to match filter: cache(user_id:[4 TO 4])",
      "details": [
         {
            "value": 0,
            "description": "no matching term"
         }
      ]
   }
}

Resaltado de búsquedas (highlight)

Resaltará mediante la etiqueta HTML “<em>” la cadena “honeymoon” si se devuelve algún resultado tras la query.

POST /us/tweet/_search
{
  "query": {
    "match": {
      "tweet": "honeymoon"
    }
  },
  "highlight": {
    "fields": {
      "tweet": {}
    }
  }
}

Agrupaciones y medias

Muestra al final cuantos empleados hay en cada campo interest y su media de edad.

GET /megacorp/employee/_search
{
    "aggs" : {
        "all_interests" : {
            "terms" : { "field" : "interests" },
            "aggs" : {
                "avg_age" : {
                    "avg" : { "field" : "age" }
                }
            }
        }
    }
}

Estadísticas / Información sobre clúster, índices y nodos

Todos los plugins y herramientas de terceros que muestran gráficas o valores de monitorización obtienen los datos de estas fuentes de información.

Documentación oficial de ElasticSearch.

Cluster health API: Muestra a grandes rasgos el estado de un índice o del clúster mostrando el nombre del clúster, su estado, número de nodos, de shards (asignados y sin asignar), tareas pendientes, etc.

# Consultar estado del cluster = Consultar estado de todos los indices separados por comas.
curl -XGET 'http://localhost:9200/_cluster/health?pretty'
 
# Consultar estado de los índices logstash-2015.08.23 y logstash-2015.08.28.
curl -XGET 'http://localhost:9200/_cluster/health/logstash-2015.08.23,logstash-2015.08.28?pretty'

Cluster Stats API: Muestra métricas básicas en los índices (número de fragmentos, espacio que ocupan, uso de memoria) e información sobre los nodos actuales que forman el clúster (número y tipo de nodos, sistema operativo, JVM, memoria RAM, CPU y plugins instalados).

curl -XGET 'http://localhost:9200/_cluster/stats?human&pretty'

Nodes Stats API: Estadísticas por nodo. retorna los valores de número de indices, sistema operativo (CPU, memoria RAM, swap, etc), procesos, JVM, transporte, conexiones http, sistema de ficheros, breaker y la pila de hilos.

# Estadísticas de todos los nodos.
curl -XGET 'http://localhost:9200/_nodes/stats?pretty'
 
# Especificar nodos en concreto.
curl -XGET 'http://localhost:9200/_nodes/nodeId1,nodeId2/stats?pretty'
 
# Filtrar información mostrando solo lo relativo a las conexiones http y JVM del nodo "es1".
curl -XGET 'http://localhost:9200/_nodes/es1/stats/http,jvm/?pretty'

Estadísticas de índices: Proporciona estadísticas sobre diferentes operaciones realizadas sobre índices.

# Estadísticas de todos los índices del clúster.
curl -XGET 'http://localhost:9200/_stats?pretty'
 
# Estadísticas de determinados índices.
curl http://localhost:9200/index1,index2/_stats

API cat Utilizando esta API se muestra una versión compacta, estadística y amigable de los valores mostrados por las anteriores APIs. Es la API ideal a la hora de querer obtener información desde la terminal.

 curl -qs http://dominio:9200/_cat
=^.^=
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes
/_cat/indices
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/plugins
/_cat/fielddata
/_cat/fielddata/{fields}

### Ejemplo


curl -qs "http://localhost:9200/_cat/plugins?v"
name          component version        type url              
elastic1      marvel    1.3.1          j/s  /_plugin/marvel/ 
elastic1      kopf      1.5.7-SNAPSHOT s    /_plugin/kopf/   
elastic2      marvel    1.3.1          j/s  /_plugin/marvel/ 


curl -qs "http://localhost:9200/_cat/shards/logstash-2014.11.13/?v"
index               shard prirep state      docs   store ip            node     
logstash-2014.11.13 4     p      STARTED 1366319   618mb 101.50.201.24 elastic3 
logstash-2014.11.13 2     r      STARTED 1366319   618mb 102.50.201.25 elastic1
logstash-2014.11.13 0     r      STARTED 1365753 613.7mb 103.50.201.26 elastic9 

Administrar Índices con Curator

Cuando se tienen muchos índices o índices indeseados como pueden ser los del plugin marvel, es posible que se necesiten políticas de borrado que se puedan implementar de una forma simple y programada. Para manejar los índices se puede usar la herramienta curator.

Para instalar curator en CentOS es necesario instalar como dependencia python-pbr

yum install python-pbr

Instalar / actualizar curator (Usar Python2).

pip2 install elasticsearch-curator
pip install -U elasticsearch-curator

Error el instalar Curator.

error: invalid command 'egg_info'

Solución: dos posibilidades.

pip2 install --upgrade setuptools # Opción 1.
easy_install -U setuptools # Opción 2.

Sintaxis de Curator.

curator --help
curator COMMAND --help
curator COMMAND SUBCOMMAND --help

Las opciones “show” y “dry-run” son únicamente informativas y no tienen riesgo.

Borrar todos los índices que tengan más de 30 días y tengan el patrón '%Y.%m.%d' en el nombre.

curator --host localhost delete indices --older-than 30 --time-unit days --timestring '%Y.%m.%d'

NOTA: Los índices Kibana son omitidos por defecto.

Borrar todos los índices “.marvel” con un tiempo de vida superior a un día.

curator --host localhost delete indices --older-than 1 --time-unit days --prefix .marvel --timestring %Y.%m.%d

Mostrar todos los índices con el patrón '%Y.%m.%d'

curator --host 10.0.0.2 show indices --timestring '%Y.%m.%d'

Cerrar / Abrir un índice cerrado.

curator --host localhost close indices --index "logstash-2015.06.08"
curator --host localhost open indices --index "logstash-2015.06.08"

Error de conexión.

ERROR: Connection failure.

Soluciones posibles:

Error.

Error: Got unexpected extra arguments (...)

Solución: Si se usan wildcards, estas deben estar entrecomilladas.

# Mal
curator delete indices --regex .marvel* --older-than 21 --time-unit days --timestring \%Y.\%m.\%d
 
# Bien
curator delete indices --regex ".marvel*" --older-than 21 --time-unit days --timestring \%Y.\%m.\%d
 
# Bien (Mismo efecto que el anterior pero sin usar expresiones regulares, solo indicando el prefijo).
curator  --host localhost delete indices  --prefix ".marvel" --older-than 30 --time-unit days --timestring %Y.%m.%d