Nikdy jsem pořádně nevěděl, co a jak nastavit, aby mi fungovalo dobře vyhledávání v elasticsearch. Píši si to tady, abych to třeba zase za rok pochopil, když budu něco někde na nějakém webu upravovat.
V rámci PHP používám knihovnu ruflin/elastica
Ze všeho nejdříve je třeba trošku pochopit konfiguraci Indexu. Mějme například takovou to konfiguraci:
$index->create(array(
"number_of_shards" => 2,
"number_of_replicas" => 0,
"analysis" => array(
"filter" => array(
"my_ngram" => array(
"type" => "nGram",
"min_gram" => 3,
"max_gram" => 50
)
),
"analyzer" => array(
"analyzer_for_indexing" => array(
"type" => "custom",
"tokenizer" => "standard",
"language" => "czech",
"filter" => (array("lowercase", 'asciifolding', "my_ngram"))
),
"analyzer_for_queries" => array(
"type" => "custom",
"tokenizer" => "standard",
"language" => "czech",
"filter" => (array("asciifolding", 'lowercase'))
)
)
)
), true); // oprions true -- delete the previous index
Tímto se pouze nastavují filtry a analyzery pro konkrétní index. Dále je pak můžeme používat pro vkládání do indexu, a nebo pro pokládání query (vyhledávání).
Pro ukládané dokumenty si nastavím toto mapování:
$elasticaType = $index->getType('product');
$mapping = new Mapping();
$mapping->setType($elasticaType);
$mapping->setProperties(array(
'id' => array('type' => 'integer', 'include_in_all' => false),
'title' => array('type' => 'text', 'analyzer' => 'analyzer_for_indexing', 'include_in_all' => true, 'boost' => 6),
'ean' => array('type' => 'text', 'analyzer' => 'standard', 'include_in_all' => true, 'boost' => 4),
'catalog_number' => array('type' => 'text', 'analyzer' => 'standard', 'include_in_all' => true, 'boost' => 3),
'text' => array('type' => 'text', 'analyzer' => 'analyzer_for_indexing', 'include_in_all' => true),
));
$mapping->send();
Jinými slovy říkám jednotlivým “sloupcům”, že mají používat určitý analyzer, který je nastavený pro tento index.
V rámci ukládání do indexu používám tyto filtry:
- lowercase
- asciifolding – převádí diakritiku na nediakritiku
- ngram – rozděluje jednotlivé slova po skupinách písmen a vytváří tím různé sloučeniny pro lepší vyhledávání
Pro vyhledávání v indexu pak používám toto:
$index = $this->elasticaClient->getIndex($this->indexName);
$boolQuery = new BoolQuery();
$elasticaQueryString = new MultiMatch();
$elasticaQueryString->setQuery($q);
$elasticaQueryString->setType(MultiMatch::TYPE_PHRASE_PREFIX);
$elasticaQueryString->setFields(['title', 'text']);
$elasticaQueryString->setAnalyzer('analyzer_for_queries');
$boolQuery->addShould($elasticaQueryString);
$elasticaQuery = new Query;
$elasticaQuery->setQuery($boolQuery);
$elasticaQuery->setFrom(0);
$elasticaQuery->setSize(50);
$response = $index->search($elasticaQuery);
Někde jsem se dočetl, že MATCH query používá před vložením do hledání analyzér – tedy před konečným hledáním v indexu zadané query změní dle nastaveného analyzeru, a teprve potom s touto změnou začne vyhledávat.
Abych se nemusel starat o to, jak je položen dotaz, a třeba v PHP ho nějak upravovat, použiji analyzer, který využívá tyto filtry:
- asciifolding
- lowercase
Záměrně zde nepoužívám filter NGRAM, protože tak by vyhledával naprosté nesmysly (např. slovo tričko by rozdělil na “tri”, “rič”, “čko”, atd – a tím by vyhledával i hodně nerelevantní věci)
Výborná věc je testovat analyzér, jaké hodnoty vyhazuje. To jde tímto příkazem:
dump($index->analyze('Rukávř', ['analyzer' => 'analyzer_for_queries']));
Tímto můžu v pohodě testovat, jak se zdeformuje dotaz, pokud na něj aplikuji nějaký analyzer.