Quelques notes sur Elasticsearch version 2.4 (en production sur ssodnet.imcce.fr
). A vérifier si elles sont toujours d'actualité en version 5.
term
n'est pas analysée. Elle est donc sensible à la casse ref.
_all
lors de la configuration d'un index.
name
et aliases
.
_all
augmente la flexibilité et la précision du calcul du score ref.
index: no
).
index: not_analyzed
) sont traîtés comme des champs keywords
ref.
multi_match
ref.
Si un champ n'a pas de mapping associé alors c'est celui déterminé par ES qui est utilisé (dynamic mapping). Donc si on ne veut pas indexer ou analyser un champ, il faut le dire explicitement.
Un analyzer est défini par un tokenizer et 0 ou plusieurs token filters. L'objectif du tokenizer est de découper une chaîne en plusieurs tokens. Les token filters traîtent (transforment) chaque token obtenu.
Les analyzers sont utilisés à deux niveaux : lors de l'indexation et lors de la recherche. Ils peuvent être différents mais il faut faire attention à ce qu'ils soient cohérents.
Pour définir un analyzer on doit se poser ce genre de questions :
Pour avoir des sorties cURL plus lisibles on peut utiliser l'outil python mjson ou ajouter le paramètre pretty=true
à l'URI.
curl curl_parameters | python -mjson.tool
Cette étape est nécesaire pour s'assurer que les champs ont bien le mapping défini.
http://ssodnet.imcce.fr:9200/ssodnet/_mapping
curl 'http://ssodnet.imcce.fr:9200/ssodnet/_analyze?analyzer={ANALYZER}&pretty=true' -d "string"
Exemple :
curl 'http://ssodnet.imcce.fr:9200/ssodnet/_analyze?analyzer=search_is_analyzer&pretty=true' -d "P/2000 Haléo"
curl 'http://ssodnet.imcce.fr:9200/ssodnet/_analyze?tokenizer={TOKENIZER}&pretty=true' -d "string"
Exemple :
curl 'http://ssodnet.imcce.fr:9200/ssodnet/_analyze?tokenizer=standard&pretty=true' -d "P/2000 Haléo"
curl 'http://ssodnet.imcce.fr:9200/ssodnet/_analyze?tokenizer={TOKENIZER}&filters={TF1},{TF2}&pretty=true' -d "string"
Exemple :
curl 'http://ssodnet.imcce.fr:9200/ssodnet/_analyze?tokenizer=standard&filters=lowercase,asciifolding&pretty=true' -d "P/2000 Haléo"
Cela permet de vérifier que le configuration du mapping est correcte.
curl 'http://ssodnet.imcce.fr:9200/ssodnet/_analyze?field={FIELD}&pretty=true' -d "string"
Exemples :
curl 'http://ssodnet.imcce.fr:9200/ssodnet/_analyze?field=name.raw&pretty=true' -d "P/2000 Haléo" curl 'http://ssodnet.imcce.fr:9200/ssodnet/_analyze?field=name.sayt&pretty=true' -d "P/2000 Haléo"
Si le champ est inconnu c'est l'analyzer standard
qui sera utilisé. Pour les champs analysés de plusieurs façons différentes il faut bien indiquer le suffixe sinon l'analyzer standard
sera utilisé.
Permet de voir comment la recherche est effectuée.
curl 'http://ssodnet.imcce.fr:9200/ssodnet/sso/_validate/query?rewrite=true&pretty=true' -d ' {"query": { "multi_match": { "query": "1950 TT2", "type": "best_fields", "fields": ["name.raw^2", "name", "aliases.raw^2", "aliases"], "operator": "and" } }}'
curl 'http://ssodnet.imcce.fr:9200/ssodnet/sso/_validate/query?rewrite=true&pretty=true' -d ' {"query": { "query_string": { "query": "1950 TT2", "fields" : ["name.raw", "name", "aliases.raw", "aliases"] } }}'
curl 'http://ssodnet.imcce.fr:9200/ssodnet/sso/_search?pretty=true' -d ' {"query": { "multi_match": { "query": "2001 L", "fields": ["name.sayt"] }}, "highlight": { "fields": { "name.sayt": {} } }}'
curl 'http://ssodnet.imcce.fr:9200/ssodnet/sso/_search?pretty=true' -d ' {"query": { "multi_match": { "query": "C/2003", "fields": ["name.raw^2", "name.sayt", "aliases.raw^2", "aliases.sayt"] }}, "highlight": { "fields": { "name.raw": {}, "name.sayt": {}, "aliases.raw": {}, "aliases.sayt": {} } }}'
Quand on highlight sur un champ multi-valué le highlight est sur l'intégralité de la chaîne (semble être le cas tout le temps). Dans l'exemple précédent les champs aliases.*
sont multi-valués alors que name.*
non.
Dans ce cas on recherche exactement un ou plusieurs tokens. C'est à dire que tous les tokens doivent apparaître dans les champs demandés (ici toutes les déclinaisons de name
et aliases
avec un boost si la chaîne recherchée a la même valeur que celle du champ). La recherche présentée dans l'exemple n'est pas sensible à la casse (cf. mapping ssodnet).
curl 'http://ssodnet.imcce.fr:9200/ssodnet/sso/_search?pretty=true' -d ' {"query": { "multi_match": { "query": "1950 TT2", "type": "best_fields", "fields": ["name.raw^2", "name", "aliases.raw^2", "aliases"], "operator": "and" } }}'
La recherche de type multi_match
est analysée avant d'être executée :
name.raw
et aliases.raw
donnent un unique token "1950 tt2"
name
et aliases
donnent deux tokens "1950" et "tt2"
On peut aussi ajouter un filtre pour affiner cette recherche (ici sur le champ type
). Ce filtre est effectué au moyen d'une recherche de type term
. La chaîne n'est donc pas analysée avant d'être exécutée. Elle est donc sensible à la casse.
curl 'http://ssodnet.imcce.fr:9200/ssodnet/sso/_search?pretty=true' -d ' {"query": { "bool": { "must": [ {"multi_match": { "query": "io", "fields": ["name.raw^2", "name", "aliases.raw^2", "aliases"], "operator": "and" }} ], "filter": [ {"term": {"type": "Asteroid"}} ] } }}'
Pour filtrer les données selon le type
(dans cet exemple), on se place dans un context filter car on ne veut pas obtenir de résultats qui ne correspondent pas au type
demandé. Si on avait utilisé une clause match
, la requête aurait aussi retournée des ressources du mauvais type mais avec un score faible (donc en fin de liste).
Dans ce cas on peut exprimer sa propre requête et non une simple liste de tokens. La syntaxe est documentée ici. On peut aussi utiliser une version simplifiée qui n'est pas sensible aux erreurs dans la saisie de la requête.
_all
n'est pas défini dans l'index, on doit préciser les champs utilisés en l'absence de préfix (attribut fields
).
OR
est l'opérateur par défaut. On doit donc indiquer AND
entre les clauses si besoin.
curl 'http://ssodnet.imcce.fr:9200/ssodnet/sso/_search?pretty=true' -d ' {"query": { "query_string": { "query": "1950 tt2", "fields": ["name.raw", "name", "aliases.raw", "aliases"] } }}'
curl 'http://ssodnet.imcce.fr:9200/ssodnet/sso/_search?pretty=true' -d ' {"query": { "query_string": { "query": "\"1950 tt2\"", "fields": ["name.raw", "name", "aliases.raw", "aliases"] } }}'
L'utilisation de l'API _validate montre pourquoi ces deux exemples ne retournent pas le même résultat.
curl 'http://ssodnet.imcce.fr:9200/ssodnet/sso/_validate/query?rewrite=true&pretty=true' -d ' {"query": { "query_string": { "query": "1950 tt2", "fields": ["name.raw", "name", "aliases.raw", "aliases"] } }}'
explanation : +((name.raw:1950 | name:1950 | aliases.raw:1950 | aliases:1950) (name.raw:tt2 | name:tt2 | aliases.raw:tt2 | aliases:tt2))
curl 'http://ssodnet.imcce.fr:9200/ssodnet/sso/_validate/query?rewrite=true&pretty=true' -d ' {"query": { "query_string": { "query": "\"1950 tt2\"", "fields": ["name.raw", "name", "aliases.raw", "aliases"] } }}'
explanation : +(name.raw:1950 tt2 | name:\"1950 tt2\" | aliases.raw:1950 tt2 | aliases:\"1950 tt2\")
dfs_query_then_fetch
Si le classement des résultats retournés semble étrange (c-à-d que le calcul du score ne correspond pas à ce que la logique voudrait), c'est peut être dû au sharding.
Cf. https://www.elastic.co/blog/understanding-query-then-fetch-vs-dfs-query-then-fetch
Les champs présents dans les ressources sont principalement de type string
:
string
)
array(string)
) => champs multi-valués dans Elasticsearch
Les différents désignations pour un objet se trouvent dans les champs name
et aliases
.
term
car elle est sensible à la casse.
multi_match
car on recherche sur plusieurs champs.
On propose 3 façons de faire des recherches dans SsODNet :
/sso
)
/sso/search
)
/sso/instant-search
)
Cas d'utilisation :
Pour répondre aux cas d'utilisation du service, on défini plusieurs analyzers :
Cf. définition du service.
Dans ce cas d'utilisation l'utilisateur entre une liste de tokens et s'attend à obtenir comme résultat les sso où tous les tokens sont trouvés dans les champs name
ou aliases
.
Comme le champ aliases
est multi-valué on peut obtenir des sso dont un des alias contient un token et un autre alias contient un autre token. Par exemple une recherche de 2000 JW8 retourne un sso avec un alias 2000 AB174 et un alias 1997 JW8.
Comme on recherche dans plusieurs champs, on utilise une recherche de type multi_match
et on attribue un poids supplémentaire.
Dans cette recherche le même analyzer est utilisé pour l'indexation et la recherche.
Cf Recherche exacte - query
Dans ce cas d'utilisation une recherche est possible sur aucun ou des champs donnés. Les contraintes sont alors exprimées sous forme de clé:valeur. Si la clé n'est pas indiquée alors les tokens sont recherchés dans les attributs name
et aliases
(car le _all
est désactivé).
Cas d'une recherche sur corot-1 :
Le résultat obtenu pour une recherche sans préciser d'attribut est dû au fait que les champs utilisés dans ce cas sont name
, aliases
, name.raw
et aliases.raw
. Or l'analyzer utilisé pour name
indexe ce champ comme (corot-1, corot, 1) et c'est le même analyzer utilisé pour la recherche.
Cf. Recherche avancée - query_string
TBD utiliser un analyzer qui ne fait rien au moment de la query pour chercher exactement le token entré ?
Dans ce cas on veut pouvoir faire une saisie avec autocomplétion à partir du début de la chaîne ou d'un de ses tokens.
L'analyzer utilisé lors de le recherche est différent de celui de utilisé lors de l'indexation.
L'analyzer à l'indexation doit :
L'analyzer à la recherche doit uniquement :
Deux approches sont possibles : match_phrase_prefix
ou edgengram
. Comme c'est plus performant de découper les tokens lors de l'indexation on utilise un token filter de type edgeNGram
(cf. 1 et 2).
Choix de la taille du edgeNGram
:
maxgram
pas trop court pour contenir l'intégralité d'un nom ;
mingram
de 2 permet de faire de l'autocomplétion sur 1 caractère car le nom entier est aussi indexé. Par exemple Cérès (alias 1).