Solr 4.7 – wydajne stronicowanie

Dawno, dawno temu, opisywaliśmy problem tzw. głębokiego stronicowania. W skrócie – im dalszą stronę wyników chcemy pobrać, tym wolniejsze będzie zapytanie. Dzieje się tak ze względu na konieczność przygotowania danych w kolejce. Niestety, nie było w Solr dobrego rozwiązania tego problemu – aż do premiery Solr 4.7. Wraz z niedawno wypuszczoną wersją dostaliśmy możliwość skorzystania z kursora i znaczącego poprawienia wydajności głębokiego stronicowania.

Problem

Problem głębokiego stronicowania jest całkiem prosty do zdefiniowania. Aby zwrócić wyniki wyszukiwania, Solr musi przygotować odpowiednią strukturę w pamięci i zwrócić jej część. Sytuacja jest prosta w przypadku pierwszej strony wyszukiwania, jednak komplikuje się dalej. Aby zwrócić stronę numer 10.000 (gdzie mamy 20 wyników na stronę) Solr musi przygotować strukturę zawierającą minimum 200.000 dokumentów (10.000 * 20). Można sobie wyobrazić, iż nie tylko zabiera to czas, ale także znaczne ilości pamięci.

Na szczęście, wraz z premierą Solr 4.7 sytuacja się zmieniła – wprowadzono pojęcie kursora. Kursor jest logiczną strukturą, która nie wymaga przechowywania stanu po stronie serwera. Kursor zawiera informacje na temat sortowania i ostatniego dokumentu w zwróconych wynikach wyszukiwania, dzięki temu Solr nie musi za każdym razem budować całej struktury od początku. Skutkuje to bardzo dużym wzrostem wydajności zapytań korzystających z kursora i głębokiego stronicowania.

Działanie

Zasada działania jest dość prosta. Aby Solr mógł skorzystać z nowej funkcjonalności, w pierwszym zapytaniu przekazujemy parametr cursorMark=*. W odpowiedzi, oprócz wyników wyszukiwania zostanie zwrócony identyfikator kursora, który przekazujemy w ramach następnych zapytań. Spójrzmy na przykład.

Zapytanie

Na początek zadajemy proste zapytanie:

curl 'localhost:8983/solr/select?q=*:*&rows=1&sort=score+desc,id+asc&cursorMark=*'

Warto spojrzeć na cztery rzeczy. Po pierwsze – albo pomijamy parametr start, albo ustawiamy parametr start na 0. Parametr rows może przyjmować wartości jakich potrzebujemy. Oczywiście przekazaliśmy parametr cursorMark=*, aby skorzystać z kursora. Ostatnią rzeczą jaką zrobiliśmy, to zdefiniowanie sortowania. Aby uniknąć problemów z sortowaniem, nasze sortowanie musi zawierać sortowanie po identyfikatorze – z tego względu musieliśmy nadpisać domyślne sortowanie przekazując do Solr parametr sort=score+desc,id+asc.

Wyniki wyszukiwania

Nasze zapytanie zwraca następujący wynik wyszukiwania:

<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">33</int>
  <lst name="params">
   <str name="sort">score desc,id asc</str>
   <str name="start">0</str>
   <str name="q">*:*</str>
   <str name="cursorMark">*</str>
   <str name="rows">1</str>
  </lst>
 </lst>
<result name="response" numFound="32" start="0">
 <doc>
  <str name="id">0579B002</str>
  <str name="name">Canon PIXMA MP500 All-In-One Photo Printer</str>
  <str name="manu">Canon Inc.</str>
  <str name="manu_id_s">canon</str>
  <arr name="cat">
   <str>electronics</str>
   <str>multifunction printer</str>
   <str>printer</str>
   <str>scanner</str>
   <str>copier</str>
  </arr>
  <arr name="features">
   <str>Multifunction ink-jet color photo printer</str>
   <str>Flatbed scanner, optical scan resolution of 1,200 x 2,400 dpi</str>
   <str>2.5" color LCD preview screen</str>
   <str>Duplex Copying</str>
   <str>Printing speed up to 29ppm black, 19ppm color</str>
   <str>Hi-Speed USB</str>
   <str>memory card: CompactFlash, Micro Drive, SmartMedia, Memory Stick, Memory Stick Pro, SD Card, and MultiMediaCard</str>
  </arr>
  <float name="weight">352.0</float>
  <float name="price">179.99</float>
  <str name="price_c">179.99,USD</str>
  <int name="popularity">6</int>
  <bool name="inStock">true</bool>
  <str name="store">45.19214,-93.89941</str>
  <long name="_version_">1461375031699308544</long></doc>
 </result>
 <str name="nextCursorMark">AoIIP4AAACgwNTc5QjAwMg==</str>
</response>

Jak widzimy, oprócz standardowych informacji, Solr zwrócił także identyfikator kursora – nextCursorMark. Teraz, aby pobrać kolejne wyniki wyszukiwania musimy uwzględnić go w naszym zapytaniu korzystając z cursorMark.

Kolejne zapytanie

Zatem nasze kolejne zapytanie wyglądać będzie następująco:

curl 'localhost:8983/solr/select?q=*:*&rows=1&sort=score+desc,id+asc&cursorMark=AoIIP4AAACgwNTc5QjAwMg=='

Wynik tego zapytania będzie następujący:

<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">2</int>
  <lst name="params">
   <str name="sort">score desc,id asc</str>
   <str name="indent">true</str>
   <str name="q">*:*</str>
   <str name="cursorMark">AoIIP4AAACgwNTc5QjAwMg==</str>
   <str name="rows">1</str>
  </lst>
 </lst>
<result name="response" numFound="32" start="0">
 <doc>
  <str name="id">100-435805</str>
  <str name="name">ATI Radeon X1900 XTX 512 MB PCIE Video Card</str>
  <str name="manu">ATI Technologies</str>
  <str name="manu_id_s">ati</str>
  <arr name="cat">
   <str>electronics</str>
   <str>graphics card</str>
  </arr>
  <arr name="features">
   <str>ATI RADEON X1900 GPU/VPU clocked at 650MHz</str>
   <str>512MB GDDR3 SDRAM clocked at 1.55GHz</str>
   <str>PCI Express x16</str>
   <str>dual DVI, HDTV, svideo, composite out</str>
   <str>OpenGL 2.0, DirectX 9.0</str>
  </arr>
  <float name="weight">48.0</float>
  <float name="price">649.99</float>
  <str name="price_c">649.99,USD</str>
  <int name="popularity">7</int>
  <bool name="inStock">false</bool>
  <date name="manufacturedate_dt">2006-02-13T00:00:00Z</date>
  <str name="store">40.7143,-74.006</str>
  <long name="_version_">1461375031846109184</long></doc>
 </result>
 <str name="nextCursorMark">AoIIP4AAACoxMDAtNDM1ODA1</str>
</response>

Jak widzimy Solr znów zwrócił informacje o kursorze – tym razem innym, niż poprzednio.

Wszystkie kolejne zapytania

Dalsza logika jest prosta, korzystamy z parametru cursorMark używając wartości zwróconej przez Solr wraz z poprzednim wynikiem wyszukiwania. Zatem nasze kolejne zapytanie wyglądałoby następująco:

curl 'localhost:8983/solr/select?q=*:*&rows=1&sort=score+desc,id+asc&cursorMark=AoIIP4AAACoxMDAtNDM1ODA1'

Podsumowanie

Proste API, ogromny zysk wydajności w przypadku głębokiego stronicowania, brak konieczności zadawania zapytania do tej samej instancji Solr. Tak w skrócie można byłoby podsumować nowe możliwości stronicowania wprowadzone w Solr 4.7. Aby ponownie nie wynajdować koła, stwierdziłem, że daruje sobie testy wydajnościowe, szczególnie, że dość dokładne testy przeprowadził Chris Hostetter we swoim wpisie dostępnym pod adresem: http://searchhub.org/2013/12/12/coming-soon-to-solr-efficient-cursor-based-iteration-of-large-result-sets/.

This entry was posted on poniedziałek, Marzec 10th, 2014 at 09:19 and is filed under Solr. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

2 komentarze to “Solr 4.7 – wydajne stronicowanie”

  1. Marcin Rzewucki Says:

    Bardzo fajny artykuł dotyczący problemu występującego w każdym systemie wyszukiwania (np. Google nie wyświetla więcej niż 1000 stron wyników). Warto wspomnieć, że dzięki temu, że struktura wyników nie jest przebudowywana od początku unikamy wielu problemów związanych z modyfikacją indeksu, tzn. tylko wartość po której sortujemy decyduje czy dokumenty będą pominięte lub powtórzone.
    W ostatnim linku zamiast parametru „nextCursorMark” powinien być parametr „cursorMark”.

  2. gr0 Says:

    Dzięki 😉