Accueil Le blog ebiznext
ElasticSearch : Architecture et Développement

Introduction

ElasticSearch est un moteur de recherche Open Source (Apache 2). Il est basé sur la librairie Apache Lucene et masque la complexité de celle-ci. Les données sont indexées sous forme de documents. Il possède des avantages indéniables dont :

  • Simplicité : Sa mise en place est très simple.
  • Rapidité : Les recherches sont traitées en quasi temps réel grâce à la parallélisation des traitements.
  • Scalablilité : Le rajout de nouveau nœud permet d’augmenter la capacité de traitement et d’être en haute disponibilité.
  • Sauvegarde : Les données sont automatiquement sauvegardées et répliquées.
  • Accessibilité : API REST

Distribution

  • bin : scripts de lancement elasticsearch.bat (windows) et elasticsearch (linux)
  • config : contient les fichierselasticsearch.yml et logging.yml
  • data : répertoire par défaut des données indexées
  • lib : contient les librairies
  • logs : fichiers logs

Cet article s’appuie sur la version 1.1.0

http://www.elasticsearch.org/overview/elkdownloads/

Démarrage

$./bin/elasticsearch
# mode daemon
$./bin/elasticsearch -d

Variables d’environnements:

  • EZ_HEAP_SIZE : JMV Heap Size
  • EZ_JAVA_OPTS : options de la JVM

Configuration du fichier elasticsearch.yml:

cluster.name: mycluster
http.port: 9200
transport.port: 9300
network.host: localhost
discovery.zen.ping.multicast.enabled: false
discovery.zen.ping.unicast.hosts: ["host1", "host2:port"]

Fonctionnement cluster

  • Nœud : Correspond à une instance ElasticSearch
  • Cluster : Il est composé d’un à plusieurs noeuds. Un nœud maître est choisi, il sera remplacé en cas de défaillance. Par défaut, un nœud utilisera le multicast pour trouver un cluster du même nom. Mais il est préférable d’utiliser l’unicast dans des réseaux sécurisés.
  • Index : Espace logique de stockage de documents de même type, découpé sur un à plusieurs PrimaryShards et peut être répliqué sur zéro ou plusieurs SecondaryShards.
  • Shard : Correspond à une instance Lucène.
  • PrimaryShard : Par défaut, l’index est découpé en 5 ShardsPrimary. Il n’est pas possible de changer le nombre de partitions après sa création.
  • SecondaryShard : Il s’agit des partitions répliquées. Il peut en avoir zéro à plusieurs par PrimaryShard.
  • Routage : Permet de sélectionner un PrimaryShard pour indexer le document. Il est choisi en hachant la valeur de l’ID du document ou de l’ID du document parent pour s’assurer que les documents parents et enfants soient stockés sur le même PrimaryShard.

elasticsearch-cluster
API REST
EllasticSearch offre une API REST permettant d’effectuer tous types d’opération. Il supporte les méthodes HTTP (GET, PUT, POST et DELETE).

curl -XPUT 'http://localhost:9200/[index]/[type]/[id]/[action]'
  • Index : Nom de l’index
  • Type : Nom du type du document
  • Id : ID du document
  • Action : Action à effectuer

Méta data d’un document

ES-1
Type de données:

  • string, integer, long, float, double, boolean, datetime, binary (base64)
  • Array, Object
  • geo_point, geo_shape, ip

Indexation de document

Ajout ou modification d’un document :
Le document est créé si l’ID n’existe pas. Si l’ID existe le document sera

curl -XPUT 'http://localhost:9200/biblio/livres/1' -d '{
  "titre": "Shining",
  "auteur": "Stephen King",
  "genre": "Fantastique",
  "date_parution": "27/01/1977"
}'

 

Création:

curl -XPUT 'http://localhost:9200/biblio/livres/1/_create' -d '{… }'

 

 

Avec création de l’ID automatiquement :

curl -XPOST 'http://localhost:9200/biblio/livres' -d '{ … }'

 

Réponse :

  • HTTP 200 : Document modifié
  • HTTP 201 : Document créé
  • HTTP 409 : Document existe déjà dans le cas d’une opération de création uniquement (_create)
{
  "ok": "true",
  "_index": "biblio",
  "_type": "livres",
  "_id": "1",
  "_version": "1"
}

 

Obtenir un document

curl -XGET 'http://localhost:9200/biblio/livres/1'

Réponse :

  • HTTP 200 : OK
  • HTTP 404 : NOT FOUND
{
  "_index": "biblio",
  "_type": "livres",
  "_id": "1",
  "_version": 1,
  "found" : true,
  "_source" :  {
    "titre": "Shining",
    "auteur": "Stephen King",
    "genre": "Fantastique",
    "date_parution": "28/01/1977"
  }
}

 

Modification et suppression d’un document

Modification partiel d’un document :

Si le document existe, il est fusionné.

curl -XPOST 'http://localhost:9200/biblio/livres/1/_update ' -d ' {
  "doc" :  {
    "date_parution": "28/01/1977"
  }
}

 

Suppression d’un document :

Cette opération ne supprime pas vraiment le document. Elle marque le document comme supprimé pour ne plus être visible lors d’une recherche. Un traitement périodique permet de supprimer réellement les documents.

curl -XDELETE 'http://localhost:9200/biblio/livres/1

 

Version

Unnuméro de version est assigné à la création d’un document. Ce numéro de version est incrémenté pour chaque opération de réindexassions, modifications ou de suppressions. Une opération de modification est validée si le numéro de version de la requête correspond au numéro de version indexé.

Recherche

Les différentes syntaxes possibles :

curl -XPOST 'http://localhost:9200/index1/type1/_search' –d { }
curl -XPOST 'http://localhost:9200/index1/type1,type2/_search' –d { }
curl -XPOST 'http://localhost:9200/index1,index2/type1/_search' –d { }
curl -XPOST 'http://localhost:9200/_all/type1/_search' –d { }

 

Exemple :

curl -XGET 'http://localhost:9200/biblio/_search?q=Shining'

 

Dans la réponse, un tableau de « hits » correspond aux documents trouvés. Le champ total indique le nombre de document.

{
  "took":8,
  "timed_out":false,
  "_shards": {
    "total":5,
    "successful":5,
    "failed":0
  },
  "hits": {
    "total":1,
    "max_score":0.09289607,
    "hits":[
    {
      "_index":"zenika",
      "_type":"formation",
      "_id":"3",
      "_score":0.09289607,
      "_source" : {
        "titre": "Shining",
        "auteur": "Stephen King",
        "genre": "Fantastique",
        "date_parution": "28/01/1977"
      }
    }
    ]
  }
}

Analyse de texte

Le résultat d’une recherche de type string est définie par l’analyseur choisie. Cela signifie que suivant l’analyseur de texte le résultat sera différent. Il est donc très important de définir le bon analyseur suivant notre besoin.

Tokenizer et Filter

Un Analyser est composé d’un Tokenizer et de zéro à plusieurs Filters.

  • Tokenizer : Il permet de scinder un texte en plusieurs éléments.
    (standard, keyword, whitespace, pattern, letter, custom, etc.)
  • Filter : Les filtres peuvent modifier ou supprimer Tokens.
    (lowercase, synonym , asciifolding, nGrams, custom, etc.)

 

Exemple

Prenant un exemple sur un document indexé :

curl -XPUT 'http://localhost:9200/twitter/tweet/1' -d '{
  "user": "david",
  "message": "C'est mon premier message de la journée !",
  "postDate": "2010-03-15T15:23:56",
  "priority":  2 ,
  "rank": 10.2
}'

 

Les deux requêtes ci-dessous ne retournent pas de document, pourquoi ?

curl-XGET 'http://localhost:9200/twitter/tweet/_search?q=est'
curl -XGET 'http://localhost:9200/twitter/tweet/_search?q=journée'

 

Comme on peut le voir ci-dessous, ElasticSearch type automatiquement les champs.

curl -XGET 'http://localhost:9200/twitter/_mapping?pretty=true'
{
  "twitter" : {
    "mappings" : {
      "tweet" : {
        "properties" : {
          "message" : { "type" : "string" },
          "postDate" : { "type" : "date", "format":"dateOptionalTime"},
          "priority" : {"type" : "long"},
          "rank" : {"type" : "double“ },
          "user" : {"type" : "string"}
        }
      }
    }
  }
}

L’API analyse permet de comprendre comment a été analysé un texte:

curl -XGET 'http://localhost:9200/twitter/_analyze?pretty=true' -d "C'est"
{
  "tokens" : [
  {
    "token" : "c'est",
    "start_offset" : 0,
    "end_offset" : 5,
    "type" : "<ALPHANUM>",
    "position" : 1
  }
  ]
}

 

Le texte « c’est » n’a pas été découpé en deux Token. C’est la raison pour laquelle la recherche sur le texte « est » n’a pas été trouvée. En fait EllasticSearch utilise l’Analyseur standard car nous ne l’avons pas définie.

Avec l’utilisation du tokenizer « letter », le texte « c’est » est découpé en deux Tokens.

curl -XGET 'http://localhost:9200/twitter/_analyze?tokenizer=letter&filters=asciifolding,lowercase' -d "c'est"
{
  "tokens" : [
  {
    "token" : "c",
    "start_offset" : 0,
    "end_offset" : 1,
    "type" : "word",
    "position" : 1
  },
  {
    "token" : "est",
    "start_offset" : 2,
    "end_offset" : 5,
    "type" : "word",
    "position" : 2
  }
  ]
}

 

L’utilisation du filtre asciifolding permet de supprimer tous les accents.

curl -XGET 'http://localhost:9200/twitter/_analyze?tokenizer=letter&filters=asciifolding,lowercase' -d "journée"
{
  "tokens" : [
  {
    "token" : "journee",
    "start_offset" : 1,
    "end_offset" : 8,
    "type" : "word",
    "position" : 1
  }
  ]
}

Configuration globale

Cette configuration sera effective à tous l’index. Le filtre « synonym » permet par exemple d’effectuer une recherche sur le mot initial pour trouver tous les documents contenant « initial »  mais également « premier ».

curl -XPUT 'http://localhost:9200/twitter' -d '{
  "settings" : {
    "analyzer" : {
      "myAnalyzer" : {
        "tokenizer" : "letter",
        "filter" : ["asciifolding", "lowercase", "mySynonyms"]
      }
      "filter" : {
        "mySynonyms" : {
          "type" : "synonym",
          "synonyms" : ["premier, initial"]
        }
      }
    }
  }
}'

Mappings

Présentation

Bien qu’Elasticsearch propose d’appliquer un mapping dynamique par défaut lors de l’indexation de nouveaux documents, il est recommandé de définir comment ces documents doivent être manipulés :

  • La façon dont les documents doivent être analysés et indexés.
  • Les types de données des champs du document.
  • Les relations entre les différents types de documents.
  • La gestion des méta-données du document.
  • La définition de la pertinence par champ/document (boosting)

 

La définition d’un mapping permet d’adapter l’analyse et la recherche des données à un domaine métier spécifique (terminologies spécifiques) ou à l’utilisateur (langages) ou simplement parce que certaines fonctionnalités l’exigent (tri, aggrégations, hilighting)

API de mapping

Il existe divers moyens pour configurer le mapping :

  • Lors de la création index
curl -XPOST localhost:9200/test -d '{  
  "settings" : {  
    "number_of_shards" : 1  
  },  
  "mappings" : {  
    "type1" : {  
      "properties" : {  
        "field1" : {"type":"string","index":"not_analyzed"}  
      }  
    }  
  }  
}'

Récupérer le mapping d’un type via l’API GET :

curl -XGET 'http://localhost:9200/test/type1/_mapping'
  • Mise à jour du mapping via l’API Rest
curl -XPUT 'http://localhost:9200/test/_mapping'/type1 -d '
{
  "properties" : {
    "field2" : {"type" : "string", "store" : true }
  }
}'
  • Utilisation des templates de mapping : un template définit le paramétrage et le mapping qui seront automatiquement appliqués aux nouveaux documents qui correspondent au pattern définit.
curl -XPUT localhost:9200/_template/template_1 -d '
{
  "template" : "te*",
  "settings" : {
    "number_of_shards" : 1
  },
  "mappings" : {
    "type1" : {
      "_source" : { "enabled" : false }
    }
  }
}'
  • Configuration au niveau du noeud

Les Mappings peuvent être fournis au niveau du nœud, ce qui signifie que chaque index créé sera automatiquement démarré avec toutes les correspondances définies dans des fichiers de configuration.

 

Les Mappings peuvent être définis dans des fichiers appelés [mapping_name].json et placés sous l’emplacement config/applications/_default ou sous config/applications/[index_name] pour les mappings qui devraient être associées uniquement avec un indice spécifique.

 

 

Mapping des types

 

Le type de chaque champ du document (strings, nombres, objets…) est contrôlé via le mapping. Elasticsearch supporte non seulement les types usuels : string, byte, short, integer, long, float, double, date, boolean mais aussi un ensemble de types complexes tels que : les tableaux, les objets, les types embarqués (nested), les ardresses IP, les types de données géographiques (Geo Point, Geo Shape) et les attachements.

 

Le tableau suivant liste quelques attributs qui pourront être utilisé par type.

ES-2
D’autres attributs communs à touts les types :

ES-3

 

Object fields

Les documents JSON peuvent contenir des objets internes. Elasticsearch comprend parfaitement la nature de ces objets internes et permet de les mapper facilement. De plus il permet requêter leurs champs intérieurs.

Puisque les objets internes peuvent avoir différents champs, le mapping dynamique est activé par défaut pour ces documents.

{
  "tweet" : {
    "properties" : {
      "person" : {
        "type" : "object",
        "properties" : {
          "name" : {
            "properties" : {
              "first_name" : {"type" : "string"},
              "last_name" : {"type" : "string"}
            }
          },
          "sid":{"type":"string","index":"not_analyzed"}
        }
      },
      "message" : {"type" : "string"}
    }
  }
}

Multi fields

Le multi field permet d’indexer le même champ avec plusieurs façons dans un document. Un cas d’utilisation typique est le besoin d’utiliser plusieurs analyseurs pour le même champ comme le montre l’exemple suivant :

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

API de recherche
Tri / Pagination

La pagination du résultat se fait via les mots-clés from et size. Le contexte de tri est déterminé par le mot-clé sort.
On peu trier par pertinence (_score) ou par la valeur des champs.

{
  "from" : 0, "size" : 10,
  "sort" : [
    { "name" : "desc" },
    { "age" : "desc" },
    "_score"
  ],
  "query" : {
    "term" : { "user" : "kimchy" }
  }
}

Fields

Permet de sélectionner les champs qui seront retournée par la requête de recherche.

{
  "fields" : ["user", "postDate"],
  "query" : {
    "term" : { "user" : "kimchy" }
  }
}

Query DSL
Le query DSL (Domain Specific Language) permet d’exprimer des définitions complexes de requêtes.
Il existes deux types de query DSL :

  • Requêtes :

○      Utilisé pour la recherche full text

○      Le résultat dépend d’un score attribué aux documents.

○      Pas d’utilisation de cache, Rapide.

○      Exemples de reqûetes

■      Match Query

{
  "match" : {
    "message" : "this is a test"
  }
}

■      Multi Match Query

{
  "multi_match" : {
    "query":    "this is a test",
    "fields": [ "subject", "message" ]
  }
}

■      BoolQuery

{
  "bool" : {
    "must" : {
      "term" : { "user" : "kimchy" }
    },
    "must_not" : {
      "range" : {
        "age" : { "from" : 10, "to" : 20 }
      }
    },
    "should" : [
    {
      "term" : { "tag" : "wow" }
    },
    {
      "term" : { "tag" : "elasticsearch" }
    }
    ],
    "minimum_should_match" : 1,
    "boost" : 1.0
  }
}

■      TermQuery

{
  "term" : { "user" : { "value" : "kimchy", "boost" : 2.0 } }
}
  • Filtres :

○      Pas de manipulation de scores.

○      Utilisation de cache.

○      Exemples de filtres

■      And Filter

{
  "filtered" : {
    "query" : {
      "term" : { "name.first" : "shay" }
    },
    "filter" : {
      "and" : [
      {
        "range" : {
          "postDate" : {
            "from" : "2010-03-01",
            "to" : "2010-04-01"
          }
        }
      },
      {
        "prefix" : { "name.second" : "ba" }
      }
      ]
    }
  }
}

■      ExistsFilter

{
  "constant_score" : {
    "filter" : {
      "exists" : { "field" : "user" }
    }
  }
}

■      GeoBounding Box Filter

{
  "filtered" : {
    "query" : {
      "match_all" : {}
    },
    "filter" : {
      "geo_bounding_box" : {
        "pin.location" : {
          "top_left" : {
            "lat" : 40.73,
            "lon" : -74.1
          },
          "bottom_right" : {
            "lat" : 40.01,
            "lon" : -71.12
          }
        }
      }
    }
  }
}

Plugins
ElasticSearch dispose de son propre système de plugins qui permet d’enrichir les fonctionnalités de base. Plusieurs plugins sont développés par Elasticsearch ou par la communauté. On distingue :

  • Les analyseurs : Ensemble de filtres et séparateurs pour supporter les langues.
  • Les rivières : Les rivers (rivières) ont pour rôle d’injecter les données provenant de divers sources de données dans ElasticSearch. Il existe 4 rivières standards : CouchDB, RabbitMQ, Twitter et Wikipedia.

Il y a également plusieurs autres rivières développées par la communauté qui permettent d’injecter les données provenant de MongoDB, Neo4J, flux RSS, Git…

  • Support des scripts : Clojure, Groovy, Javascript…

 

Commandes pour gérer les plugins :

bin/plugin **--install** mobz/elasticsearch-head
plugin **--remove**head