Aplikacja „sprzedaż samochodów” – solr.ReversedWildcardFilter, czyli optymalizujemy zapytania wildcard (cz. 8)

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.

  1. ?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>
  2. ?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.

  3. ?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ć 🙂

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

We use cookies to personalise content and ads, to provide social media features and to analyse our traffic. We also share information about your use of our site with our social media, advertising and analytics partners. View more
Cookies settings
Accept
Privacy & Cookie policy
Privacy & Cookies policy
Cookie name Active
Save settings
Cookies settings