elasticsearch, analizador spanish con filtro fonético

Pasos a seguir para crear un indice con un analizdor en español con reconocimiento de fonético.

Especialmente util si queremos buscar para un texto como este:

  • “Habia una vez un barquito chiquitito que navegaba ….”

palabras como

  • avia, nabegava

es decir, palabras mal escritas pero que se pronuncian igual.

Pasos previo necesarios

Instalar plugin

En cada nodo debemos instalar el plugin y reiniciar.

sudo bin/plugin install analysis-phonetic

Crear el indice y el mapping

curl -XDELETE http://localhost:9201/indice_fonetico

curl -XPUT http://localhost:9201/indice_fonetico

curl -XPOST http://localhost:9201/indice_fonetico/_close

curl -XPUT http://localhost:9201/indice_fonetico/_settings?pretty -d '{
  "analysis" : {
    "analyzer":{
      "spanish_no_accent": {
          "tokenizer": "standard",
          "filter":  [ "lowercase", "asciifolding", "spanish_stop", "filtro_fonetico", "spanish_stemmer" ]
          }
    },
    "filter" : {
            "spanish_stemmer" : {
                "type" : "stemmer",
                "name" : "spanish"
            },
            "spanish_stop": {
                             "type":        "stop",
                             "stopwords": [ "_spanish_" ]
      },
      "filtro_fonetico": {
          "type": "phonetic",
          "encoder": "beidermorse",
          "replace": false,
          "languageset": ["spanish"]
      }
          }
  }
}'

curl -XPOST http://localhost:9201/indice_fonetico/_open

curl -XPUT http://localhost:9201/indice_fonetico/_mapping/documento?pretty -d '{
"properties":{
  "documento":{
     "type":"string",
     "analyzer":"spanish_no_accent"
  }
 }
}'

Indexar datos y preparar una query

Indexamos un documento

PUT indice_fonetico/documento/1
{
  "documento": "Habia una vez un barquito chiquitito que navegaba ...."
}

Realizamos una búsqueda

  • avia

Query y respuesta

GET indice_fonetico/documento/_search
{
  "query": {
    "match": {
      "documento": {
        "query": "avia"
      }
    }
  }
}
{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1.7499999,
    "hits": [
      {
        "_index": "indice_fonetico",
        "_type": "documento",
        "_id": "4",
        "_score": 1.7499999,
        "_source": {
          "documento": "Habia una vez un barquito chiquitito que navegaba ...."
        }
      }
    ]
  }
}

Términos generados por este analizador

En cualquier momento podemos ver los términos que genera este analizador, para entender mejor las búsquedas que podemos realizar con este analizador y el nuevo filtro fonético.

Invocación del analizador

POST prueba_fonetica/_analyze
{
  "analyzer": "spanish_no_accent",
  "text":     "barquito"
}

Términos generados por el analizador

{
  "tokens": [
    {
      "token": "barkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "borkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "varkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "vorkit",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    }
  ]
}

Para entender mejor el comportamiento de este filtro fonético podemos crear un analizador donde solamente tengamos como filtro el filtro fonético.

curl -XPUT http://localhost:9201/indice_fonetico/_settings?pretty -d '{
  "analysis" : {
    "analyzer":{
      "spanish_no_accent": {
          "tokenizer": "standard",
          "filter":  [ "lowercase", "filtro_fonetico" ]
          }
    },
    "filter" : {
      "filtro_fonetico": {
          "type": "phonetic",
          "encoder": "beidermorse",
          "replace": false,
          "languageset": ["spanish"]
      }
    }
  }
}'
Anuncios

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": {
            ...
        }
    }
}

Como configurar nginx como proxy reverso en un contenedor de docker

Docker Logo

Vamos a configurar un contenedor de docker basado en nginx para que funcione como un proxy reverso contra un contenedor de Kibana y otro de elasticsearch.

Para facilitar la creación y gestión de todos los contenedores vamos a utilizar docker compose.

El acceso a Kibana y a elasticsearch desde el host se realizará siempre desde el contenedor de nginx.

La topología de este sistema es:

  • el host accede a nginx
  • nginx accede a kibana y a elasticseach
  • kibana accede a elasticsearch

Desde nuestro host solo tendremos acceso al contenedor de nginx, ya que el resto de contenedores no van a tener expuestos los puertos de cada servicio.

Sigue leyendo

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

elasticsearch Cookbook, similitud con SQL para las búsquedas IS NULL e IS NOT NULL

Elasticsearch

En elasticsearch también podemos realizar búsquedas preguntando por la existencia de un campo, es decir, podemos preguntarle si un campo tiene algún valor en alguno de sus documentos o si no por el contrario no tiene ningún valor.

Si hablamos en términos de SQL estaríamos hablando de sentencias como

  • Select * from table_name where column_1 IS NOT NULL;
  • Select * from table_name where column_1 IS NULL;

Ejemplo de ambos tipos de búsqueda utilizando el API Rest

La funcionalidad que buscamos la conseguimos aplicando filtros. En el ejemplo siguiente vamos a buscar todos los artículos del índice loquemeinteresadelared donde su título contenga ciertas palabras, y donde el campo autor tenga al menos un valor (IS NOT NULL) y donde el campo colaborador no tenga ningún valor (IS NULL).

curl -XPOST 'http://localhost:9200/loquemeinteresadelared/articulo/_search?pretty' -d '{
    "query" : {
      "filtered" : {
          "query": {
          "match": {
              "titulo": "elasticsearch cookbook"
            }
          }, 
        "filter" : {
          "bool" : {
                    "must" : [
                        {"exists" : { "field" : "autor" }},
                        {"missing" : { "field" : "colaborador" }}
                    ]
                }
          }
    }
  }
}'