Użytkownicy aplikacji „sprzedaż samochodów” zaczęli coraz częściej używać zapytań ze znakami wildcard. Zmusiło nas to do zastanowienia się nad optymalizacją takich zapytań, a na pomoc przyszedł filtr solr.ReversedWildcardFilter.
solr.ReversedWildcardFilter
Filtr solr.ReversedWildcardFilter dostarcza nam do indeksu nowe tokeny, które są niejako odwróceniem tokenów w danym polu. Tokeny takie są wykorzystywane podczas wyszukiwania, w celu przyspieszenia zadawania zapytań, w których „dzikie karty” są na początku wyrażenia. Na poziomie konfiguracji filtr dostarcza nam następujące argumenty:
- withOriginal – jeżeli „true”, to generuj na tej samej pozycji zarówno tokeny oryginalne, jak i odwrócone. Jeżeli „false”, to generuj jedynie tokeny odwrócone.
- maxPosAsterisk – maksymalna pozycja „dzikiej karty” „*”, która uaktywnia korzystanie przez filtr z tokenów odwróconych. Jeżeli „*” pojawi się w zapytaniu na pozycji wyższej niż skonfigurowana, wtedy filtr nie będzie korzystał z tokenów odwróconych.
- maxPosQuestion – maksymalna pozycja „dzikiej karty” „?”, która uaktywnia korzystanie przez filtr z tokenów odwróconych.
- maxFractionAsterisk – dodatkowy parametr, który uaktywnia korzystanie z tokenów odwróconych, jeżeli pozycja „*” jest mniejsza niż skonfigurowana w tym parametrze wartość ułamkowa długości tokena zapytania.
- minTrailing – minimalna liczba znaków występujących po ostatniej „dzikiej karcie” w zapytaniu, która uaktywni nam korzystanie z tokenów odwróconych. W celach wydajnościowych zaleca się, aby wartość ta była większa niż 1.
Zmiany w schema.xml
Nowy filtr dodajemy do definicji typu „text” w następujący sposób:
<fieldType name="text" class="solr.TextField" positionIncrementGap="100"> <analyzer type="index"> <tokenizer class="solr.WhitespaceTokenizerFactory" /> <filter class="solr.PatternReplaceFilterFactory" pattern="'" replacement="" replace="all" /> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" stemEnglishPossessive="0" /> <filter class="solr.LowerCaseFilterFactory" /> <filter class="solr.ReversedWildcardFilterFactory" /> </analyzer> <analyzer type="query"> <tokenizer class="solr.WhitespaceTokenizerFactory" /> <filter class="solr.PatternReplaceFilterFactory" pattern="'" replacement="" replace="all" /> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" stemEnglishPossessive="0" /> <filter class="solr.LowerCaseFilterFactory" /> </analyzer> </fieldType>
Filtr solr.ReversedWildcardFilter wykorzystujemy tylko na etapie indeksacji. Nie definiujemy żadnych argumentów w filtrze, ponieważ chcemy skorzystać ze standardowej konfiguracji, czyli wartości argumentów tego filtra domyślnie będą wyglądać tak:
- withOriginal – „true”, chcemy również mieć dostępne tokeny oryginalne, aby móc dalej korzystać z wyszukiwania bez „dzikich kart” po polach tego typu
- maxPosAsterisk – 2
- maxPosQuestion – 1
- maxPosQuestion – 0.0f (czyli funkcjonalność wyłączona)
- maxPosQuestion – 2
Przykładowe dane
Zaindeksujmy przykładowe dane do analizy:
<add> <doc> <field name="id">1</field> <field name="make">Lancia</field> <field name="model">Delta</field> ... </doc> <doc> <field name="id">2</field> <field name="make">Land Rover</field> <field name="model">Defender</field> ... </doc> <doc> <field name="id">3</field> <field name="make">Acura</field> <field name="model">MDX</field> ... </doc> <doc> <field name="id">4</field> <field name="make">Acura</field> <field name="model">RDX</field> ... </doc> <doc> <field name="id">5</field> <field name="make">Acura</field> <field name="model">RSX</field> ... </doc> </add>
Tworzymy zapytania
Przypomnijmy, że domyślne wyszukiwanie odbywa się po polu „content”, w skład którego wchodzą między innymi pola „make” oraz „model”. W celach analizy wyników i działania filtra solr.ReversedWildcardFilter, ustawimy atrybut „stored” pola „content” na „true”. Dodamy również do zapytania argument debugQuery, który umożliwi nam obserwacje, z którego tokena (oryginalnego, czy odwróconego) korzysta filtr.
- ?q=lan*&fl=id,content&debugQuery=on
<result name="response" numFound="2" start="0"> <doc> <arr name="content"> <str>Lancia</str> <str>Delta</str> <str>2002</str> </arr> <str name="id">1</str> </doc> <doc> <arr name="content"> <str>Land Rover</str> <str>Defender</str> <str>2002</str> </arr> <str name="id">2</str> </doc> </result> <lst name="debug"> <str name="rawquerystring">lan*</str> <str name="querystring">lan*</str> <str name="parsedquery">content:lan*</str> <str name="parsedquery_toString">content:lan*</str> ... </lst>
Użyliśmy „dzikiej karty” „*” na końcu zapytania (pozycja = 4), zatem filtr do wyszukiwania użył tokenów oryginalnych:
<str name="parsedquery">content:lan*</str>
- ?q=*dx&fl=id,content&debugQuery=on
<result name="response" numFound="2" start="0"> <doc> <arr name="content"> <str>Acura</str> <str>MDX</str> <str>2002</str> </arr> <str name="id">3</str> </doc> <doc> <arr name="content"> <str>Acura</str> <str>RDX</str> <str>2003</str> </arr> <str name="id">4</str> </doc> </result> <lst name="debug"> <str name="rawquerystring">*dx</str> <str name="querystring">*dx</str> <str name="parsedquery">content:#1;xd*</str> <str name="parsedquery_toString">content:#1;xd*</str> ... </lst>
Użyliśmy „dzikiej karty” „*” na początku zapytania (pozycja = 1) i dodatkowo mamy jeszcze dwa znaki po ostatniej „dzikiej karcie”. Filtr użył zatem tokenów odwróconych:
<str name="parsedquery">content:#1;xd*</str>
Jak widzimy, tokeny odwrócone są w indeksie poprzedzone specjalnym prefixem, aby nie doszło do wyszukania nieprawidłowych dokumentów.
- ?q=r?x&fl=id,content&debugQuery=on
<result name="response" numFound="2" start="0"> <doc> <arr name="content"> <str>Acura</str> <str>RDX</str> <str>2003</str> </arr> <str name="id">4</str> </doc> <doc> <arr name="content"> <str>Acura</str> <str>RSX</str> <str>2006</str> </arr> <str name="id">5</str> </doc> </result> <lst name="debug"> <str name="rawquerystring">r?x</str> <str name="querystring">r?x</str> <str name="parsedquery">content:r?x</str> <str name="parsedquery_toString">content:r?x</str> ... </lst>
Użyliśmy „dzikiej karty” „?” na pozycji 2 oraz dodatkowo mamy tylko jeden znak występujący po ostatniej „dzikiej karcie”. Zatem filtr do wyszukiwania użył tokenów oryginalnych:
<str name="parsedquery">content:r?x<</str>
Podsumowanie
Dzięki filtrowi solr.ReversedWildcardFilter zoptymalizowaliśmy zapytania z „dzikimi kartami”, zatem nasi użytkownicy mogą teraz efektywnie z takich zapytań korzystać 🙂