Elastic search – nastavení v PHP

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.