Agregaciones y búsquedas en elasticsearch

Elasticsearch

Sabemos que todas las agregaciones se ejecutan en el contexto de una búsqueda y además si queremos podemos utilizar la misma query de agregaciones para devolver una muestra de documentos utilizados para calcular dicha agregación.

Este comportamiento lo podemos modificar. Vamos a ver como.

Devolver los documentos de una query y sobre ellos aplicar una serie de agregaciones.

Esto devuelve una respuesta con:

  • documentos que cumplen la query
  • las agregaciones aplicadas a los documentos resultado de la query

Por ejemplo

GET .../_search
{
    "size" : 10,
    "query" : {
        ...
    },
    "aggs" : {
        "nombre_agg": {
            ...
        }
    }
}

Devolver los documentos de una query y aplicar un filtrado a los documentos antes de aplicar las agregaciones

Esto devuelve una respuesta con:

  • documentos que cumplen la query
  • las agregaciones aplicadas a los documentos filtrados sobre el resultado de la query

Por ejemplo

GET .../_search
{
   "size" : 10,
   "query":{
      ...
   },
   "aggs":{
      "filtro": {
         "filter": { 
            ...
         },
         "aggs": {
            "nombre_agg":{
               ...
            }
         }
      }
   }
}

Devolver los documentos de una query y aplicar a esos documentos un filtro, pero generar las agregaciones sobre los documentos devueltos por la query

Esto devuelve una respuesta con:

  • documentos filtrados que han cumplido la query
  • las agregaciones aplicadas sobre los documentos devueltos por la query

Por ejemplo

GET .../_search
{
    "size" : 10,
    "query": {
        ...
    },
    "post_filter": {    
        "term" : {
        ...
        }
    },
    "aggs" : {
        "nombre_agg": {
            ...
        }
    }
}
Anuncios

Geolocalización con elasticsearch, el tipo geo_point

Elasticsearch

ES tiene dos formas de representar geolocalizaciones. Para ello dispone de los siguientes dos tipos de representación de datos.

  • geo_point type. Utilizado para representar puntos, utilizando coordenadas latitud y longitud.

    Con este tipo de representación podemos buscar todos los puntos que existen cerca de uno dado, calcular distancias entre puntos, ordenar por distancias, encontrar todos los puntos dentro de un circulo o polígono etc

    El orden de las coordenadas depende del formato elegido al insertar el documento.

    Si es un string con coordenadas el orden es lat,lon
    Si es un array con coordenadas el orden es [lon,lat]. Éste último orden es el elegido en el estandar de GeoJSON.

  • geo_shape type. Utilizado para representar formas o polígonos definidos utilizando GeoJSON.

    Con este tipo de representación podemos saber si dos polígonos se superponen, si un contiene al otro, etc.

geo_point type

Como hemos visto representan coordenadas geográficas. Por lo tanto tendremos dos puntos, la latitud y la longitud.

mapping

Debemos indicar el mapping de un tipo geo_point. El mapping dinámico no funciona con este tipo de dato.

Ejemplo

# Arrancamos ES (vamos a utilizar docker)
$ docker run -d --name es-geo-server -p 9200:9200 -p 9300:9300 elasticsearch:2.1.1 -Des.node.name="Puntos de Interes"

# Verificamos su correcto funcionamiento
$ curl localhost:9200?pretty
{
  "name" : "Puntos de Interes",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.1.1",
    "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
    "build_timestamp" : "2015-12-15T13:05:55Z",
    "build_snapshot" : false,
    "lucene_version" : "5.3.1"
  },
  "tagline" : "You Know, for Search"
}

# Creamos el indice y el mapping
curl -XPUT http://localhost:9200/mis_puntos_de_interes?pretty -d '{
    "mappings": {
        "cine": {
            "properties": {
                "nombre": {"type": "string"},
                "direccion": {"type": "string"},
                "coordenadas": {"type": "geo_point"}
            }
        }
    }
}'

# Insertar o actualizar puntos de interes
curl -XPUT http://localhost:9200/mis_puntos_de_interes/cine/1 -d '{
    "nombre" : "Universo",
    "direccion" : "Calle del universo, madrid, 28400"
    "coordenadas" :  "40.394609,-3.7740232"
}'

curl -XPUT http://localhost:9200/mis_puntos_de_interes/cine/2 -d '{
    "nombre" : "Los estrenos",
    "direccion" : "Calle del estreno, madrid, 28400"
    "coordenadas" :  "40.435469, -3.6871448"
}'

Tipos de búsquedas o filtros que podemos hacer con geo_point

Por defecto ningún resultado devuelto por estos filtros es cacheado.

geo_bounding_box

Es el filtro más simple y con el que obtendremos mejores resultados (en tiempo de respuesta). Devuelve todos los documentos que se encuentran dentro de un rectángulo, definido por sus coordenadas geográficas.

curl -XGET localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '{
    "query": {
        "filtered": {
            "filter": {
                "geo_bounding_box": {
                    "coordenadas": {
                        "top_left": {
                            "lat": 41,
                            "lon": -3.8
                        },
                        "bottom_right": {
                            "lat": 40.40,
                            "lon": -3.65
                        }
                    }
                }
            }
        }
    }
}'

y deberíamos obtener solamente 1 documento

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "mis_puntos_de_interes",
      "_type" : "cine",
      "_id" : "2",
      "_score" : 1.0,
      "_source":{
                "nombre" : "Los estrenos",
                "direccion" : "Calle del estreno, madrid, 28400",
                "coordenadas" :  "40.435469, -3.6871448"
        }
    } ]
  }
}

Podemos mejorar aún más los resultados (hablando de tiempo de respuesta) obtenidos al aplicar este tipo de filtro geo_bounding_box (tambíen para geo_distance). Todo lo que tenemos que hacer es cambiar el mapping del campo donde están las coordenadas para decirle a elasticsearch que indexe las coordenadas en campos separados.

Ahora tendremos 2 nuevos campos con la latitud y la longitud

  • coordenadas_v2.lat
  • coordenadas_v2.lon.

De esta forma estos dos nuevos campos los tenemos también en el índice invertido.

Finalmente cuando ejecutemos el filtro le indicaremos que tiene que utilizar los nuevos dos campos indexados. Entonces el filtro se aplica como una simple operación de rangos. En estos casos y cuando trabajemos con grandes volúmenes de datos mejoraremos sustancialmente el rendimiento obtenido.

El mapping en este caso cambiaría y sería

# Creamos el indice y el mapping
curl -XPUT http://localhost:9200/mis_puntos_de_interes?pretty -d '{
    "mappings": {
        "cine": {
            "properties": {
                "nombre": {"type": "string"},
                "direccion": {"type": "string"},
                "coordenadas": {"type": "geo_point"}
                "coordenadas_v2": {
                    "type": "geo_point",
                    "lat_lon": true
                }
            }
        }
    }
}'

NOTA. No es necesario mantener los dos campos de coordenadas. Es suficiente con mantener solo uno de ellos. El más útil es el mapeado con lat_lon: true.

Ahora tendremos 2 tipos de búsquedas.

  • Una de ellas es la búsqueda por defecto que se haría en memoria (type: memory, en este caso no sería necesario indicar el tipo) , y
  • la otra sería la búsqueda por el índice invertido (type: indexed)
# En memoria
#
curl -XGET localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '{
    "query": {
        "filtered": {
            "filter": {
                "geo_bounding_box": {
                    "type": "memory",
                    "coordenadas_v2": {
                        "top_left": {
                            "lat": 41,
                            "lon": -3.8
                        },
                        "bottom_right": {
                            "lat": 40.40,
                            "lon": -3.65
                        }
                    }
                }
            }
        }
    }
}'

# En el índice invertido (mejor rendimiento)
#
curl -XGET localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '{
    "query": {
        "filtered": {
            "filter": {
                "geo_bounding_box": {
                    "type": "indexed",
                    "coordenadas_v2": {
                        "top_left": {
                            "lat": 41,
                            "lon": -3.8
                        },
                        "bottom_right": {
                            "lat": 40.40,
                            "lon": -3.65
                        }
                    }
                }
            }
        }
    }
}'

geo_distance

A partir de una coordenada y una distancia encuentra todos los documentos que se encuentran dentro de ese radio.

Para optimizar y mejorar los tiempos de respuesta, elasticsearch primero localiza todos los documentos que se encuentran dentro del rectángulo que engloba el círculo de búsqueda, ya que este tipo de filtros es el menos costoso (geo_bounding_box). Una vez tiene los documentos candidatos, aplica el filtro según el radio proporcionado.

curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '
{
  "query": {
    "filtered": {
      "query": {"match_all": {}},
      "filter": {
        "geo_distance": {
          "distance": "8643m",
          "coordenadas_v2": {
            "lat": 40.394609,
            "lon": -3.7740232
          }
        }
      }
    }
  }
}'

Este filtro puede devolver los documentos encontrados ordenados por distancia. El calculo de la distancia entre dos puntos se puede personalizar cuando aplicamos el filtro.

Este calculo de distancia también se utiliza para saber qué documentos se encuentran dentro del radio de búsqueda.

Tenemos 3 tipos de algoritmos disponibles, configurados mediante la propiedad distance_type.

  • distance_type: sloppy_arc. Es el utilizado por defecto. Es más rápido que el arc pero algo menos preciso.

  • distance_type: plane. Es bastante rápido pero menos preciso. La precisión empeora para los puntos más cercanos a los polos.

  • distance_type: arc. El más lento y el más preciso.

Por tanto, para aumentar la precisión

curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '
{
  "query": {
    "filtered": {
      "query": {"match_all": {}},
      "filter": {
        "geo_distance": {
          "distance": "8643m",
          "distance_type": "arc", 
          "coordenadas_v2": {
            "lat": 40.394609,
            "lon": -3.7740232
          }
        }
      }
    }
  }
}

geo_distance_range

Basado en una coordenada y a dos distancias, encuentra los documentos que se encuentran dentro del rango o donut indicado.

Sigue los mismos principios del filtro geo_distance.

Por ejemplo, dada una coordenada busca todos los documentos que se encuentran dentro del rango de 8642 metros hasta 8643 metros.

curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search?pretty -d '
{
  "query": {
    "filtered": {
      "query": {"match_all": {}},
      "filter": {
        "geo_distance_range": {
          "gte": "8642m",
          "lt": "8643m",
          "coordenadas_v2": {
            "lat": 40.394609,
            "lon": -3.7740232
          }
        }
      }
    }
  }
}
'

geo_polygon

Es el filtro más costoso. Busca todos los puntos que se encuentran dentro de un poligono.

Si necesitamos utilizar este tipo de filtros es mejor que utilicemos el tipo geo_shape.

Ordenar por distancia

La ordenación siempre se realizará respecto a un punto.

curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search
{
  "query": {
    "filtered": {
      "filter": {
        "geo_bounding_box": {
          "type": "indexed",
          "coordenadas_v2": {
            "top_left": {
              "lat": 50,
              "lon": -5
            },
            "bottom_right": {
              "lat": 0,
              "lon": 0
            }
          }
        }
      }
    }
  },
  "sort": [
    {
      "_geo_distance": {
        "coordenadas_v2": {
          "lat": 45,
          "lon": -3
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}

# 
curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search -d '
{
  "query": {
    "match_all": {}
  }, 
  "sort": [
    {
      "_geo_distance": {
        "coordenadas_v2": {
          "lat": 45,
          "lon": -3
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}'

# 
curl -XPOST localhost:9200/mis_puntos_de_interes/cine/_search -d '
{
  "sort": [
    {
      "_geo_distance": {
        "coordenadas_v2": {
          "lat": 45,
          "lon": -3
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}'

Utilizar docker compose para gestionar y administrar varios contenedores

Docker Logo

En el anterior artículo Preparar un entorno de desarrollo basado docker con elasticseach, kibana y sense vimos como preparar dos contenedores docker, uno basado en elasticsearch y otro basado en kibana.

Lo que vamos a ver en este pequeño artículo es una guía de como preparar los mismos contenedores pero utilizando docker compose. Como sabemos docker compose es una herramienta de docker con la que nos permiten manejar varios contenedores con un solo comando.

Utilizando docker compose nos evitamos la tediosa tarea de tener que crear, arrancar, parar y borrar cada uno de los contenedores por separado utilizados en nuestro proyecto.

docker-compose.yml

Es el fichero donde le decimos a docker compose los contenedores que tiene que gestionar y administrar.

Vamos a crear nuestro entorno de desarrollo basado en un contenedor de elasticsearch y otro de kibana.

El contenedor de Kibana

  • se encontrará lincado al contenedor de elasticsearch
  • dejaremos su puerto disponible en el host, en el primer puerto libre que tenga la máquina

El contenedor de elasticsearch

  • dejaremos sus puertos disponible en el host, en el primer puerto libre que tenga la máquina, por si fuera necesario acceder directamente por el API Rest a elasticsearch.

El primer paso será crear un directorio y dentro de él creamos el fichero docker-compose.yml.

$ mkdir test-elasticsearch-kibana
$ cd test-elasticsearch-kibana
$ touch docker-compose.yml

Y editamos el fichero docker-compose-yml

elasticsearch:
  image: elasticsearch:2.1.1
  container_name: test-elasticsearch-2.1.1
  ports:
    - "9200"
    - "9300"
  expose:
    - "9200"
    - "9300"

kibana:
  image: kibana:4.3.1
  container_name: test-kibana-4.3.1
  links:
    - elasticsearch
  ports:
    - "5601"

donde,

  • image. Indicamos la imagen a partir de la cual queremos crear el contenedor.
  • container_name: El nombre del contenedor. Es opcional y si no le ponemos uno docker compose le asignará un nombre por defecto.
  • ports: Los puertos que queremos tener disponible en la maquina host.
  • expose: Los puertos que deben ser expuestos para otros contenedores (links).
  • links: Creamos un link con el contenedor de elasticsearch desde el contenedor de kibana.

Operaciones principales

Una vez tenemos el fichero creado, procederemos como siempre.

Arrancamos los dos contenedores

$ docker-compose up -d

Paramos los dos contenedores

$ docker-compose stop

Borramos los dos contenedores

$ docker-compose rm

Ver los puertos mapeados de uno de los contenedores

$ docker-compose port elasticsearch 9200

Instalar el plugin sense en Kibana

docker exec test-kibana-4.3.1 /opt/kibana/bin/kibana plugin --install elastic/sense

etc

Preparar un entorno de desarrollo basado en docker con elasticseach, kibana y sense

Docker Logo

Vamos a crear 2 contenedores de Docker, uno con una instancia de elasticsearch, y el otro con una instancia de Kibana. En este segundo contenedor instalaremos el plugin Sense, y además lo lincaremos con el contenedor de elasticsearch.

Un paso más que no veremos en este artículo sería utilizar docker compose para gestionar el ciclo de vida de ambos contenedores como si fuera uno solo. Este paso ya lo veremos en otro artículo.

Por tanto lo que vamos a ver en este articulo es

  • crear un contenedor docker basado en elasticsearch, mapeando los puertos del contenedor a la máquina host, para tener acceso desde nuestra maquina a los servicios de elasticseach
  • utilizar docker logs para ver el correcto arranque de elasticsearch, kibana y la instalación de sense
  • crear un contenedor basado en kibana, mapeando los puertos del contenedor a la máquina host, para tener acceso desde nuestra máquina a los servicios de kibana
  • crear un link desde el contenedor basado en kibana hacia el contenedor basado en elasticsearch, para facilizar la comunicación de red entre kibana y elasticsearch.

Preparar un contenedor docker con elasticsearch

Arrancamos el contenedor docker con elasticsearch

$ docker run -d --name es-server-test -p 9200:9200 -p 9300:9300 elasticsearch:2.1.1
251554d157a73a177940093ec79cb342cd9927dba5b6b25c5f67bf105884fa59
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
251554d157a7 elasticsearch:2.1.1 "/docker-entrypoint.s" 5 seconds ago Up 4 seconds 0.0.0.0:32772->9200/tcp, 0.0.0.0:32771->9300/tcp es-server-test

Comprobamos el funcionamiento de elasticsearch

docker@default:~$ curl localhost:9200
{
 "name" : "Starstreak",
 "cluster_name" : "elasticsearch",
 "version" : {
 "number" : "2.1.1",
 "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
 "build_timestamp" : "2015-12-15T13:05:55Z",
 "build_snapshot" : false,
 "lucene_version" : "5.3.1"
 },
 "tagline" : "You Know, for Search"
}
$ docker logs es-server-test
[2016-01-14 14:38:25,583][INFO ][env ] [Starstreak] using [1] data paths, mounts [[/usr/share/elasticsearch/data (/dev/sda1)]], net usable_space [14.4gb], net total_space [18.1gb], spins? [possibly], types [ext4]
[2016-01-14 14:38:29,240][INFO ][node ] [Starstreak] initialized
[2016-01-14 14:38:29,247][INFO ][node ] [Starstreak] starting ...
[2016-01-14 14:38:29,433][WARN ][common.network ] [Starstreak] publish address: {0.0.0.0} is a wildcard address, falling back to first non-loopback: {172.17.0.2}
[2016-01-14 14:38:29,433][INFO ][transport ] [Starstreak] publish_address {172.17.0.2:9300}, bound_addresses {[::]:9300}
[2016-01-14 14:38:29,453][INFO ][discovery ] [Starstreak] elasticsearch/Y6CTY4f7RWyGy4621sxcFw
[2016-01-14 14:38:32,499][INFO ][cluster.service ] [Starstreak] new_master {Starstreak}{Y6CTY4f7RWyGy4621sxcFw}{172.17.0.2}{172.17.0.2:9300}, reason: zen-disco-join(elected_as_master, [0] joins received)
[2016-01-14 14:38:32,508][WARN ][common.network ] [Starstreak] publish address: {0.0.0.0} is a wildcard address, falling back to first non-loopback: {172.17.0.2}
[2016-01-14 14:38:32,509][INFO ][http ] [Starstreak] publish_address {172.17.0.2:9200}, bound_addresses {[::]:9200}
[2016-01-14 14:38:32,509][INFO ][node ] [Starstreak] started
[2016-01-14 14:38:32,587][INFO ][gateway ] [Starstreak] recovered [0] indices into cluster_state

Preparar un contenedor con kibana y el plugin de sense

Arrancamos kibana

$ docker run --link es-server-test:elasticsearch -d --name kibana-es-server-test -p 5601:5601 kibana:4.3.1
1bc9c3a03cd6b97d6b507b5cffa8ec323cee3237ced8661d45ef727ed954635c
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1bc9c3a03cd6 kibana:4.3.1 "/docker-entrypoint.s" 3 seconds ago Up 3 seconds 0.0.0.0:32776->5601/tcp kibana-es-server-test        
251554d157a7 elasticsearch:2.1.1 "/docker-entrypoint.s" 23 minutes ago Up 23 minutes 0.0.0.0:32772->9200/tcp, 0.0.0.0:32771->9300/tcp es-server-test

Instalamos el plugin sense en kibana

$ docker exec kibana-es-server-test kibana plugin --install elastic/sense

Miramos los logs verificando la instalación correcta de sense

$ docker logs -f kibana-es-server-test
Installing sense
Attempting to extract from https://download.elastic.co/elastic/sense/sense-latest.tar.gz
Downloading 318236 bytes....................
Extraction complete
Optimizing and caching browser bundles...
Plugin installation complete

Reiniciamos el contenedor de kibana

$ docker restart kibana-es-server-test

Finalmente accedemos desde un navegador a kibana y sense

Kibana localhost:5601

Kibana

Sense localhost:5601/app/sense

Sense

Tipo de Hardware utilizado para formar un cluster de nodos de Elasticsearch

Elasticsearch

Este artículo es un resumen de la documentación oficial de Elasticsearch. Lo que pretendemos reflejar en este artículo es una guía rápida de requisitos Hardware que debemos considerar cuando creamos un Cluster de nodos de elasticsearch.

Memoria RAM.

  • 64GB es ideal
  • 32GB y 16GB suele ser bastante común
  • Menos de 8GB o más de 64GB no es aconsejable

Memoria cache del sistema de ficheros

Los indices de Elasticsearch son indices de Lucene. Lo que significa que si aportamos más memoria al SO destinada a la cache del sistema de ficheros lograremos mejorar el rendimiento de elasticsearch.

  • Destinar memoria cache al sistema de ficheros

CPU Cores

Es recomendable un nodo con más Cores frente a menos Cores pero más rápidas. Es decir más Cores es mejor, ya que aumentamos la capacidad de concurrencia del nodo.

  • Entre 2 a 8 cores es lo más habitual.

Disco

  • Se desaconseja el uso de NAS (network-attached storage), es decir el uso de un sistema de ficheros compartido entre varios equipos.
  • Mejor discos sólidos o SSD frente a los tradicionales discos duros o HDD. Dan mejor rendimiento en escrituras y lecturas.
  • Raid 0 mejora la velocidad de acceso del disco.

Red

  • Ancho de banda de 1Gb a 10 Gb es suficiente para la mayoría de los Clusters.
  • Evitar redes con gran latencia ya que terminan ocasionando problemas.
  • Los nodos de un Cluster deben estar próximos y en el mismo Data Center

Copias de seguridad en elasticsearch, el API de snapshot y restore

Elasticsearch

El API de snapshot y restore de elasticsearch nos permite hacer copias de seguridad de los datos y el estado de

  • todo el cluster o
  • algunos indices

sobre un repositorio compartido, que puede ser

  • Amazon S3
  • Sistema de ficheros compartido
  • HDFS
  • Azure
  • etc

En cualquiera de los casos, independientemente del repositorio elegido, éste tiene que ser accesible desde cualquier nodo del cluster.

Elasticsearch soporta multitud de repositorios compartidos, todos ellos a través de la instalación de un plugin.

Nosotros vamos a ver como crear un snapshot y restore en un sistema de ficheros compartido.

El proceso de snapshot es incremental. Eso significa que el primer snapshot se realiza completo, mientras que el resto de snapshot solo tiene en cuenta la diferencia entre el último snapshot y los nuevos datos del cluster añadidos o modificados.

Registrar el path del sistema de ficheros

Antes de registrar el repositorio compartido, el cual hemos elegido en nuestro caso el sistema de ficheros, debemos registrar su ubicación. Recordemos que esta ubicación debe ser visible y accesible por todos los nodos del cluster.

Esta configuración debe ser añadida igualmente en todos los nodos del cluster.

Para ello editamos el fichero de configuración

$ES_HOME/config/elasticsearch.yml

y añadimos la configuración

path.repo: ["/mnt/fs-shared/backups/"]

Registrar el repositorio compartido

Una vez tenemos configurado la ubicación en todos los nodos del cluster, procedemos a registrar el repositorio compartido

# Petición
curl -XPUT 'http://localhost:9200/_snapshot/backups' -d '{
    "type": "fs",
        "settings": {
            "location": "/mnt/fs-shared/backups/"
    }
}'

# Respuesta
{
   "acknowledged": true
}

Donde podemos definir las siguientes características

  • location, puede ser un path relativo, en cuyo caso es relativo al path especificado en la configuración path.repo
  • compress, true o false. true por defecto. Indica si comprimir los ficheros de metadatos, es decir, los mappings, settings etc. Los ficheros de datos sin embargo no se comprimen
  • chunk_size, para dividir los ficheros grandes en otros más pequeños

Obtener información del repositorio compartido

# Petición
curl -XGET http://localhost:9200/_snapshot/backups

# Respuesta
{
   "backups": {
      "type": "fs",
      "settings": {
         "location": "/mnt/fs-shared/backups/"
      }
   }
}

Obtener información de todos los repositorios compartidos

curl -XGET http://localhost:9200/_snapshot

curl -XGET http://localhost:9200/_snapshot/_all

Verificación del registro de un repositorio

Cuando registramos un repositorio compartido, automáticamente se verifica su configuración en todos los nodos de cluster. Este comportamiento lo podemos modificar mediante un parámetro durante el registro del repositorio, evitando que este chequeo se realice. En esos casos, podemos lanzar posteriormente el proceso de verificación mediante la llamada al API:

# Petición
curl -XGET http://localhost:9200/_snapshot/backups/_verify

# Respuesta
{
   "nodes": {
      "NhD89B4NR1eI1kgl56TwQQ": {
         "name": "Architect"
      }
   }
}

Y como respuesta obtendremos la lista todos los nodos que han podido verificar el repositorio.

Creación de un snapshot de todo el cluster

Por defecto el snapshot se realiza del cluster completo. Exactamente de todos los índices abiertos.

# Petición
curl -XPUT http://localhost:9200/_snapshot/backups/backup_1?wait_for_completion=false

# Respuesta
{
   "accepted": true
}

Donde,

  • wait_for_completion, indica que no esperemos a que el snapshot termine. Aun así si el cluster es muy grande, esta operación puede tarde segundos o minutos, ya que antes de relizar el snapshot tiene que cargar en memoria la información del mismo.

Obtener información de un snapshot

# Petición
curl -XGET http://localhost:9200//_snapshot/backups/backup_1

# Respuesta
{
   "snapshots": [
      {
         "snapshot": "backup_1",
         "version_id": 1070199,
         "version": "1.7.1",
         "indices": [
            ".marvel-2015.09.24",
            "cars",
            ".marvel-2015.09.29",
            ".kibana_2",
            "pruebas",
            "usuarios",
            ".marvel-kibana",
            ".marvel-2015.10.16",
            ".kibana"
         ],
         "state": "SUCCESS",
         "start_time": "2015-11-11T16:59:43.224Z",
         "start_time_in_millis": 1447261183224,
         "end_time": "2015-11-11T17:13:31.158Z",
         "end_time_in_millis": 1447262011158,
         "duration_in_millis": 827934,
         "failures": [],
         "shards": {
            "total": 44,
            "failed": 0,
            "successful": 44
         }
      }
   ]
}

Como vemos el snapshot ha terminado con correctamente state=SUCCESS, y tenemos todos los índices del cluster. Además al estar utilizando kibana y marvel tenemos los índices que estos plugins crea automáticamente.

Creación de un snapshot pero solo de determinados índices

# Petición
curl -XPUT 'http://localhost:9200/_snapshot/backups/usuarios_y_cars_1' -d '{
  "indices": "usuarios,cars"
}'

# Respuesta
{
   "accepted": true
}

En este caso podemos indicarle las siguiente propiedades
– ignore_unavailable, si un índice no esta disponible el snapshot falle o no
– include_global_state, para no incluir o ignorar la información del estado del cluster

Igualmente podemos obtener la información del snapshot utilizando el mismo API Rest, pero en este caso, veremos solamente los indices seleccionados en el snapshot

# Petición
curl -XGET http://localhost:9200/_snapshot/backups/usuarios_y_cars_1

# Respuesta
{
   "snapshots": [
      {
         "snapshot": "usuarios_y_cars_1",
         "version_id": 1070199,
         "version": "1.7.1",
         "indices": [
            "usuarios",
            "cars"
         ],
         "state": "SUCCESS",
         "start_time": "2015-11-11T17:24:04.625Z",
         "start_time_in_millis": 1447262644625,
         "end_time": "2015-11-11T17:24:04.781Z",
         "end_time_in_millis": 1447262644781,
         "duration_in_millis": 156,
         "failures": [],
         "shards": {
            "total": 10,
            "failed": 0,
            "successful": 10
         }
      }
   ]
}

Información de todos los snapshot

curl -XGET http://localhost:9200/_snapshot/backups/_all

Borrado de un snapshot o parada de un snapshot

Esta operación se puede lanzar tanto para borrar un snapshot, como para parar uno que se encuenta en ejecución.

# Petición
curl -XDELETE http://localhost:9200/_snapshot/backups/backup_1

# Respuesta
{
   "acknowledged": true
}

Restaurar o recuperar un snapshot

Es la operación de restore. Debemos antes cerrar el índice o índices para poder realizar el restore. Además debemos asegurarnos que el número de shards no ha cambiado.

curl -XPOST http://localhost:9200/cars/_close
curl -XPOST http://localhost:9200/usuarios/_close
curl -XPOST http://localhost:9200/_snapshot/backups/usuarios_y_cars_1/_restore

# Respuesta
{
   "accepted": true
}

Una vez se ha restaurado el snapshot, los índices son abiertos automáticamente.

Por defecto se restauran todos los índices junto con el estado del cluster. Pero también se puede seleccionar del snapshot que índices queremos restaurar.

Por ejemplo, podemos restaurar solo el índice usuarios.

curl -XPOST http://localhost:9200/usuarios/_close
curl -XPOST 'http://localhost:9200/_snapshot/backups/usuarios_y_cars_1/_restore' -d '{
    "indices": "usuarios"
}'

# Respuesta
{
   "accepted": true
}

Restore nos permite otras funcionalidades, como la de poder hacer el restore en un cluster diferente, renombrar los índices etc

Determinar el estado de un snapshot

Tanto durante la ejecución de un snapshot como cuando ya ha termiando, podemos saber su estado.

La operación como siempre se realiza con el API Rest, y en la respuesta tenemos toda la información que necesitamos. Entre ellas nos encontraremos con la propiedad

  • state

cuyos valores no dicen el estado actual del snapshot, que pueden ser STARTED, SUCCESS, etc

# Lanzamos el snapshot
curl -XPUT http://localhost:9200/_snapshot/backups/all_cluster/

# Pedimos su estaso
curl -XGET http://localhost:9200/_snapshot/backups/all_cluster/_status

# Respuesta
{
   "snapshots": [
      {
         "snapshot": "all_cluster",
         "repository": "backups",
         "state": "STARTED",
         "shards_stats": {
            "initializing": 18,
            "started": 2,
            "finalizing": 0,
            "done": 25,
            "failed": 0,
            "total": 45
         },
         "stats": {
            ...
         },
         "indices": {
            ...
      }
   ]
}

Recordemos que un snapshot en ejecución o en proceso puede ser parado con el API Rest de borrado de snapshot.

Si te ha gustado este artículo o lo has encontrado interesante no olvides compartirlo en tus redes sociales. Esto nos ayuda a seguir mejorando.

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 .