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 *