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/.

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