JDONREFv4 plugin GettingStarted

De JDONREF Wiki
Révision de 3 novembre 2014 à 23:02 par Julien2512 (discussion | contributions) (Indexation en masse)

L'utilisation du plugin JDONREFv4 nécessite :

  • de disposer d'un cluster elasticsearch !
  • de déployer le fichier de synonymes de JDONREFv4 (voir ici)
  • d'installer le plugin JDONREFv4 (voir ici)
  • puis d'indexer le contenu

Vous êtes ensuite libre d'effectuer les recherches souhaitées ! L'API elasticsearch peut bien sûr être utilisée, mais le plugin jdonrefv4 fourni un moyen de chercher efficacement des adresses (c'est à dire avec les résultats auxquels on devrait s'attendre). Cette page décrit une démarche possible en débutant par l'indexation du contenu.

Création de l'index

Commencez par vous créer un index en respectant la configuration fournie par le fichier jdonrefv4-settings.json de la sorte :

 curl -XPUT 'http://localhost:9200/jdonref/' -d '{
    "index" : {
       "analysis" : {
           "analyzer": {
               "jdonrefv4_index" : {
                   "type" : "custom",
                   "tokenizer" : "whitespace",
                   "filter" : ["delimited_payload_filter", "lowercase", "french_elision", "french_stop", /*/"french_keywords",*/ "french_stemmer","jdonrefv4_synonyme", "jdonrefv4_nGram"]
               },
               "jdonrefv4_codes_index" : {
                   "type" : "custom",
                   "tokenizer" : "standard",
                   "filter" : ["standard", "lowercase"]
               },
               "jdonrefv4_search" : {
                   "type" : "custom",
                   "tokenizer" : "standard",
                   "filter" : ["lowercase", "french_elision", "french_stop",/*/"french_keywords",*/ "french_stemmer"]
               }
           },
           "filter" : {
               "delimited_payload_filter" : {
                   "type": "delimited_payload_filter",
                   "delimiter" : "|",
                   "encoding" : "int"
               },
               "french_elision": {
                   "type":         "elision",
                   "articles": [ "l", "m", "t", "qu", "n", "s",
                                   "j", "d", "c", "jusqu", "quoiqu",
                                   "lorsqu", "puisqu"
                               ]
               },
               "french_stop": {
                   "type":       "stop",
                   "stopwords":  "_french_" 
               },
 //            "french_keywords": {
 //                "type":       "keyword_marker",
 //                "keywords":   [] 
 //            },
               "french_stemmer": {
                   "type":       "stemmer",
                   "language":   "light_french"
               },
 //            "jdonrefv3es_metaphone" : {
 //                "type" : "phonetic",
 //                "encoder" : "metaphone",
 //                "replace" : true
 //            },
               "jdonrefv4_synonyme" : {
                   "type" : "synonym",
                   "synonyms_path": "jdonrefv4_synonym.fr_FR.txt"
               },
               "jdonrefv4_nGram" : {
                   "type" : "jdonrefv4_edgengram",
                   "min_gram": 1,
                   "max_gram": 15,
                   "token_chars": ["digit"],
                   "withPayloads": true
               }
           },
           "similarity": {
               "jdonrefv4" : {
                   "type": "org.apache.lucene.search.similarities.JDONREFv4TermSimilarity"
               }
           }
       }
   }
 }'

A noter que le metaphone n'est plus utilisé pour le moment, car combiné au ngram, il introduit un trop grand nombre de faux positifs. Vous pouvez toutefois le mettre en place à votre guise. Le "french_keywords" peut être rétabli si vous notez quelques mots auxquels l'application du stemmer ne fournis pas de bons résultats.

Création des mappings

Pour que le plugin jdonrefv4 soit efficace, il s'agit de respecter les mappings fournis par jdonref. Cette opération est obligatoire pour que la requête de JDONREF puisse traiter correctement les données indexées.

Par exemple, pour que l'indexation de la géométrie se déroule correctement, les mappings auront du être définis au préalable, car ElasticSearch ne détecte pas automatiquement les géométries.

Vous avez peut-être aussi remarqué l'existence d'un champ calculé "codes". Si vous utilisez les mappings fournis, il se remplit automatiquement à partir du code_postal, code_insee, code_departement, code_arrondissement quelque soit l'objet considéré. Il est cependant vrai que dans le fond, vous pouvez vous en occuper vous-même.

Le champ "fullName" est lui aussi précalculé et contient l'ensemble des termes utilisé par la requête JDONREFv4. Il n'est ainsi pas uniquement la concaténation des champs ligne1, ligne4, et ligne6, mais dispose aussi des champs indexés dans "codes". Il permet d'effectuer un premier niveau de filtrage sur les résultats, très rapidement.

Enfin, le nom des types attribués à chaque mapping doit pour le moment être respecté. Dans le cas contraire, les malus ne pourront pas être appliqués.

Utiliser les synonymes

Le fichier de synonymes a normalement été utilisé à la création de votre index à partir du fichier jdonrev4_synonym.fr.txt.

La recherche pourra ainsi par exemple être effectuée avec le type de voie "BD" plutôt que "BOULEVARD". Attention toutefois, si vous modifiez ce fichier, il faut penser aussi à ré-indexer vos données.

Une autre stratégie consisterait à inverser le fonctionnement du fichier de synonyme pour l'utiliser durant la requête et non pas l'indexation. Le fichier de synonyme contiendrait alors des lignes commme :

 residence, res => residence

et la création de l'analyzer serait par exemple:

           "analyzer": {
               "jdonrefv4_index" : {
                   "type" : "custom",
                   "tokenizer" : "standard",
                   "filter" : ["standard", "lowercase", "jdonrefv4_nGram"]
               },
               "jdonrefv4_search" : {
                   "type" : "custom",
                   "tokenizer" : "standard",
                   "filter" : ["standard", "lowercase", "jdonrefv4_synonyme"]
               }
           },

Indexation du contenu

Il est ensuite possible d'indexer du contenu, comme une commune :

 curl -XPUT 'http://localhost:9200/jdonref/commune/1' -d '{
   "code_insee" : "75056",
   "code_departement" : "75",
   "code_pays" : "1",
   "commune" : "PARIS",
   "code_postal" : "75000",
   "ligne7" : "FRANCE",
   "type" : "commune",
   "t0" : "22/03/2014",
   "geometrie" : {
       "type": "multipolygon",
       "coordinates" : [
          [[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]],
          [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],
          [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]
       ]
   }
 }'

ou une voie :

 curl -XPUT 'http://localhost:9200/jdonref/voie/1' -d '{
   "numero_min": "1",
   "numero_max": "192",
   "type_de_voie": "BOULEVARD",
   "article": "DE",
   "voie": "HOPITAL",
   "code_insee_commune" : "75056",
   "code_postal" : "75013",
   "code_insee": "75113",
   "code_departement" : "75",
   "code_pays" : "FR1",
   "commune" : "PARIS",
   "ligne4" : "BOULEVARD DE L HOPITAL",
   "ligne6" : "75013 PARIS",
   "ligne7" : "FRANCE",
   "type": "voie",
   "t0" : "22/03/2014",
   "geometrie" : {
       "type": "multipolygon",
       "coordinates" : [
          [[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]],
          [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],
          [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]
       ]
   }
 }'
Indexation en masse

A noter qu'il est conseillé d'utiliser l'API [BULK] d'ElasticSearch pour procéder à l'indexation d'un très grand nombre d'adresses.

Attention, pour que la requête du plugin fonctionne correctement, certains champs doivent être correctement renseignés (voir mappings).

Recherches

Une fois le contenu indexé, il vous est possible d'effectuer des recherches à loisir !

Optimisation (une fois que vous vous êtes sortis du reste)

Pour utiliser le paramètre maxSizePerType de la requête JDONREF, il est conseillé (obligatoire) de répartir les types sur de multiples index. D'une manière générale, il est même plus que conseillé de répartir les communes, départements et pays sur un unique shard (avec des réplicats, et potentiellement sur différents index).

A cet effet, et pour simplifier le fonctionnement de l'ensemble, il est possible d'utiliser des alias elasticsearch. Un alias permet de "contenir" différent index. Lorsqu'une requête est faite sur un alias, tous les index de l'alias sont requêtés (et le résultat aggrégé).

Les index de mon cluster sont répartis ainsi (via un batch) :

  1. alias jdonref
  2. index pays, 1 shard + 1 réplica
  3. index departement, 1 shard + 1 réplica
  4. index commune, 1 shard + 1 réplica
  5. index voie, 5 shards + 1 réplica
  6. index adresse, 5 shards + 1 réplica
  7. index poizon, 5 shards + 1 réplica

NB: Je n'indexe pas les troncons, je n'en ai pas l'usage. NB: Notez que vous pourriez aussi choisir de regrouper pays, departement et commune. Il faut savoir que dans un même index, les fréquences des termes sont cumulés. Dans le même index, les communes auront donc une influence sur la fréquence des termes des pays. A vous de voir.


Cela me permet de définir maxSizePerType à 5000 (voir optimisation), pour éviter de polluer certaines requêtes avec des résultats d'adresse farfelus. Bien sûr, il est nécessaire d'adapter les requêtes de création des index, des mappings, et d'ajouter la création d'un alias.

Par exemple pour les index de pays, departement, commune, il s'agit simplement d'ajouter "number_of_shards" et "number_of_replicas". Ici l'exemple incomplet de l'index des pays :

 curl -XPUT 'http://localhost:9200/jdonref_pays/' -d '{
    "index" : {
       "number_of_shards" : 1,
       "number_of_replicas" : 1,
       "analysis" : {
           "analyzer": {
               "jdonrefv4_index" : {
                   "type" : "custom",
                   "tokenizer" : "whitespace",
                   "filter" : ["delimited_payload_filter", "lowercase", "french_elision", "french_stop", /*/"french_keywords",*/ "french_stemmer","jdonrefv4_synonyme", "jdonrefv4_nGram"]
               },
               "jdonrefv4_codes_index" : {
                   "type" : "custom",
                   "tokenizer" : "standard",
                   "filter" : ["standard", "lowercase"]
               },
 ...


Le mapping du pays doit bien sûr être réalisé sur l'index du pays ...

 $ curl -XPUT 'http://localhost:9200/jdonref_pays/pays/_mapping' -d '{
   "pays": {
      "_type": {"store": true},
     "_source": {"excludes": ["geometrie"]},
     "properties" : { 
                          "code_pays" : { "type" : "string" , "term_vector" : "with_positions_offsets", "index_analyzer":"jdonrefv4_codes_index","search_analyzer":"jdonrefv4_search", "similarity":"jdonrefv4"},
                          "pays" : { "type" : "string" , "index": "no"},
                          "t0" : { "type" : "date", "format": "YYYY-MM-dd HH:mm:ss", "index":"not_analyzed"},
                          "t1" : { "type" : "date", "format": "YYYY-MM-dd HH:mm:ss", "index":"not_analyzed"},
                          "ligne7" : { "type" : "string", "term_vector" : "with_positions_offsets", "index_analyzer":"jdonrefv4_index","search_analyzer":"jdonrefv4_search", "similarity":"jdonrefv4"},
                          "pin" : { "properties" : { "centroide" : { "type" : "geo_point" , "fielddata" : { "format" : "compressed" , "precision" : "1cm"}}}},
                          "geometrie" : { "type" : "geo_shape", "precision": "1cm", "tree": "quadtree"},
                          "fullName" : {"type": "string", "term_vector" : "with_positions_offsets_payloads", "index_analyzer":"jdonrefv4_index_token_count","search_analyzer":"jdonrefv4_search", "similarity":"jdonrefv4"}
                     },
     "transform" : {
         "lang" : "groovy",
         "script" : "ctx._source['fullName'] = ; if (ctx._source['ligne7']!=null) { def tokens = ctx._source['ligne7'].split(' '); for(x in tokens) ctx._source['fullName'] += ' ' + x + '|9'; }; if (ctx._source['code_pays']!=null) { def tokens = ctx._source['code_pays'].split(' '); for(x in tokens) ctx._source['fullName'] += ' ' + x + '|10'; };"
     }
 }
}'

et enfin le pays peut être rattaché à l'alias jdonref :

 $ curl -XPOST 'http://localhost:9200/_aliases' -d '{
 {"actions":[{"add":{"index":"jdonref_pays","alias":"jdonref"}}]}'

Idem pour les autres types (n'oubliez pas d'ajouter des shards pour les voies, adresses et poizon).

Vous êtes alors prêt à utiliser une requête avec maxSizePerType comme ici.


Debug (inaccessible pour le moment, en attente du scoring et du mode bulk)

Pour les experts, un mode debug est disponible avec le paramètre debugDoc de la sorte :

 curl -XPOST 'http://localhost:9200/jdonref/_search' -d '{
   "query": {
     "jdonrefv4" : {
       "value" : "24 BOULEVARD DE L HOPITAL 75005 PARIS",
       "debugDoc": 2154
     }
   }
 }'

Cela permet d'afficher au niveau debug (niveau log4j), dans les logs d'elasticsearch, l'exécution détaillée du calcul de la note associée au document d'id "debugDoc". Il ne s'agit toutefois pas de l'id du document correspondant à _id, mais l'id du document relativement au shard considéré. Pour l'identifier, il suffit d'exécuter une requête dont le résultat inclus le document recherché, en mode explain (l'explication fait référence à l'id relatif au shard !).