Elasticsearch et comment être pertinent

Créé par Baptiste Donaux

Baptiste Donaux

Elasticsearch, qu'est-ce que c'est ?

En bref…

  • Une base de données NoSQL
  • Doté d'une API RESTful
  • Basé sur le moteur d'indexation Lucene
  • Écrit en Java
  • Développé par la société Elastic
  • Dont le fondateur est Shay Banon

Objectif

Indexer du contenu dans le but de resortir des informations par pertinence selon des cas métiers.

Concurrence

Elasticsearch est un concurrent plus « Monolithique » que SolR.

  • Pas de Tomcat
  • De nombreuses features intégrées de base comme le Clustering, Backup & Restore,
  • « Plug and Play » Une configuration par défaut très bonne. Tout est « overridable ».
  • Possibilité d'avoir du support, des produits de gestions avancées, et même du SaaS.

Qui l'utilise ?

  • Wikipédia : recherche full-text avec highlight, suggestions...
  • StackOverflow : recherche full-text, « more like this » pour les questions similaires
  • GitHub : indexation des codes sources
  • Goldman Sachs : 5To de logs par jour
  • Dailymotion : millions de vidéos indéxées

Terminologies

  • Un Cluster est un ensemble de nodes.
  • Un Node est une instance d'elasticsearch
  • Un Index est namespace logique (comme un table en BDD)
  • Un Shard est une instance de Lucene pour stocker des documents. Il y a n shards dans un index. On ne deal jamais avec les shards.
    Un shard peut être primary ou replica.
    Un document est contenu par un unique shard.
  • Un Type est un sous-ensemble d'un Index.
  • Un Document est une entrée.

Cluster

  • Un cluster possède toujours un unique node master.
  • Master élu automatiquement.
  • Le Master est responsable des opérations (écriture, ajout/suppression d'index, gestion des nodes).
  • Mais tous les nodes peuvent répondre pour de la lecture.

API

Deux API

  • API RESTful sur le port 9200
  • API JAVA sur le port 9300

RESTful - Quels langages ?

Pour les libs officielles

  • Java
  • JavaScript
  • Groovy
  • .NET
  • PHP
  • Perl
  • Python
  • Ruby
  • et d'autres non officielles

RESTful - Quelles libs ?

Liste des clients

Quelle solution pour du PHP ?

  • elasticsearch, client officiel
  • Elastica sur lequel est basé FOSElasticaBundle, une version proposant une gestion à base d'objets.

Quelle solution pour du PHP ?

FOSElasticaBundle

  • Faible activité sur GitHub
  • Des demandes en attentes depuis longtemps (#638 ouverte et taggée dans la prochaine Milestone depuis Juin 2014 n'est toujours pas terminée)
  • Pas adapté dans la plupart des cas, car les documents sont indexés des schémas de BDD.

Quelle solution pour du PHP ?

Elastica

  • Toujours en cours de développement
  • Stable, recommandé
  • Utilisé pour de la veille sur un projet chez Wanadev et abandonné car manque de flexibilité sur le push de configuration (index/type/settings/analyser/et autres)

Quelle solution pour du PHP ?

elasticsearch

  • Client officiel
  • Stable, simple, plug-and-play
  • Utilisé chez Wanadev sur un projet
  • Gestion des multiples hosts

Quelle solution pour du PHP ?

From Scratch

Solution pertinente car gestion des requêtes extrêmement simples

Pratiquons

Installation (Docker package)

$ docker run --rm elasticsearch

Installation de Sense

Pratique pour développer

$ kibana plugin --install elastic/sense

Example

Fichiers d'exemples disponibles.

Request

  • GET pour lire un/des résultats
  • PUT/POST pour ajouter/modifier un document
  • DELETE pour supprimer un document

Hello world !

GET /
{
  "name": "Zero-G",
  "cluster_name": "elasticsearch",
  "version": {
    "number": "2.2.0",
    "build_hash": "8ff36d139e16f8720f2947ef62c8167a888992fe",
    "build_timestamp": "2016-01-27T13:32:39Z",
    "build_snapshot": false,
    "lucene_version": "5.4.1"
  },
  "tagline": "You Know, for Search"
}

Indexer un document

PUT /club/member/1
{
    "firstname": "John",
    "lastname": "Smith"
}
{
  "_index": "club",
  "_type": "member",
  "_id": "1",
  "_version": 1,
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

Indexer un document

{
  "_index": "club",
  "_type": "member",
  "_id": "1",
  "_version": 1,
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}
  • On a créé un index (_index: club) car celui-ci n’existait pas
  • On a créé un type (_type: member) car celui-ci n'existait pas
  • On a indexé un document (_id: 1)
  • Le document est dans sa version 1 (cela permet de gérer la synchronisation entre les shards d'un cluster
  • Le document a tout juste été créé. Il ne s'agissait pas d'une édition

Indexer un document

Par défaut les types et les index sont créés automatiquement

action.auto_create_index: false // pour désactiver

Récupérer un document

GET /club/member/1
{
  "_index": "club",
  "_type": "member",
  "_id": "1",
  "_version": 1,
  "found": true,
  "_source": {
    "firstname": "John",
    "lastname": "Smith"
  }
}

Supprimer un document

DELETE /club/member/1
{
  "found": true,
  "_index": "club",
  "_type": "member",
  "_id": "1",
  "_version": 2,
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  }
}

Faire une recherche

GET /club/member/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "_index": {
              "value": "club"
            }
          }
        }
      ],
      "should": [
        {
          "term": {
            "nickname": {
              "value": "nickname_to_boost",
              "boost": 2
            }
          }
        }
      ]
    }
  }
}

Faire une recherche

Liste des requêtes dans la section Query DSL

Des petits plus et puis s'en vont

À savoir

  • Par défaut les index et les types sont créés automatiquement. Je conseille de désactiver cette feature pour pousser les applications à posséder un mapping précis du type des champs à avoir.
  • Tous les champs peuvent être/ou ne pas être renseignés.
  • Tous les champs sont de types Array. Vous pouvez donc renseigné autant de valeurs que vous voulez.
  • Vous pouvez faire des recherches multiples index et multiples types.

Plugins

Performance

Elasticsearch met en cache le résultat des requêtes sur chacun des résultats retournés.

Performance

Deux types de requêtes : query et filter. Les filter sont catégoriques et donc rapides.
Les query sont modérés et influent donc sur le score et la pertinence du résultat.

Les filters sont rapides, il faut donc trier ses filters par ordre d'efficacité pour limiter le stockage des résultats d'un filtre à l'autre.

Performance

Indexer des documents unitairement n'est pas efficace. Mieux vaut utiliser des Bulk et indexer des documents en masse.

Insertion de 1000 documents

  • Un par un : 95 secondes
  • Bulk : 1 seconde

Mapping

Définir un mapping comme on veut.

Analyzers/Tokenizers/Filters

Définir des analyzers/tokenizers selon nos besoins.

Subfilters et pertinences

On peut indexer une version d'une valeur mais également indexer une valeur sous plusieurs formes.

Exemple :

On peut très bien imaginer l'indexation d'un document dans le champs content avec un analyseur enlevant les stop words (de, la, une).
On aurait un subfield (content.raw) pour indexer ce même contenu sans analyseur et pouvoir ressortir le contenu sans l’altérer.

Merci