Solr 6.0 i wsparcie grafów

Jedną z funkcjonalności, która weszła w skład niedawno wydanego Solr 6.0 to wsparcie dla grafów. Mając zbiór dokumentów źródłowych i relację między nimi w postaci informacji o rodzicu każdego dokumentu możemy użyć prostego zapytania i dostać wiele poziomów zagnieżdżenia w pojedynczym żądaniu. Warto także pamiętać, iż funkcjonalność o której będziemy rozmawiać działa zarówno w przypadku SolrCloud, jak również w przypadku wdrożeń opartych o master – slave.

Do pisania tego wpisu użyliśmy bardzo prostego zbioru danych (możliwe do zaindeksowania pojedynczą komendą) oraz konfiguracji, która dostępna jest na naszym koncie na Github: https://github.com/solrpl/blog.

Tworzenie kolekcji i indeksowanie danych

Pierwszą rzeczą, jaką musimy zrobić jest stworzenie odpowiedniej kolekcji i zaindeksowanie danych. Aby to zrobić potrzebujemy uruchomić Solr. Robimy to następującą komendą:

bin/solr start -c

Spowoduje to uruchomienie Solr w trybie SolrCloud razem z wbudowaną instancją ZooKeepera. Jak tylko Solr zostanie uruchomiony możemy wysłać konfigurację kolekcji do ZooKeepera. Aby to zrobić używamy poniższego polecenia:

bin/solr zk -upconfig -n graph_test_config -z localhost:9983 -d graph/conf

Następnie stworzymy kolekcję o nazwie graph korzystając z następującego polecenia:

curl -XGET 'http://localhost:8983/solr/admin/collections?action=CREATE&name=graph&numShards=2&replicationFactor=1&maxShardsPerNode=2&collection.configName=graph_test_config'

I kiedy mamy już to wszystko, możemy zaindeksować trochę danych korzystając z następującego polecenia:

curl -XPOST -H 'Content-Type: application/json' 'http://localhost:8983/solr/graph/update' --data-binary '{
 "add" : { "doc" : { "id" : "1", "name" : "Root document one" } },
 "add" : { "doc" : { "id" : "2", "name" : "Root document two" } },
 "add" : { "doc" : { "id" : "3", "name" : "Root document three" } },
 "add" : { "doc" : { "id" : "11", "parent_id" : "1", "name" : "First level document 1, child one" } },
 "add" : { "doc" : { "id" : "12", "parent_id" : "1", "name" : "First level document 1, child two" } },
 "add" : { "doc" : { "id" : "13", "parent_id" : "1", "name" : "First level document 1, child three" } },
 "add" : { "doc" : { "id" : "21", "parent_id" : "2", "name" : "First level document 2, child one" } },
 "add" : { "doc" : { "id" : "22", "parent_id" : "2", "name" : "First level document 2, child two" } },
 "add" : { "doc" : { "id" : "121", "parent_id" : "12", "name" : "Second level document 12, child one" } },
 "add" : { "doc" : { "id" : "122", "parent_id" : "12", "name" : "Second level document 12, child two" } },
 "add" : { "doc" : { "id" : "131", "parent_id" : "13", "name" : "Second level document 13, child three" } },
 "commit" : {}
}'

Relacje między zaindeksowanymi dokumentami można zobrazować następująco:

Graph Documents Layout

Możemy rozpoczynać wyszukiwanie.

Podstawowe zapytanie

Aby skorzystać z zapytania obsługującego grafy w podstawowej formie musimy podać kilka informacji. Po pierwsze podajemy zbiór dokumentów, które są będą służyć, jako wierzchołki grafu. Dodatkowo podajemy informację o tym, które pole jest używane jako identyfikator dokumentu oraz które jest używane jako identyfikator rodzica. Na przykład, jeżeli chcielibyśmy znaleźć wierzchołki grafu wraz ze wszystkimi poziomami zagnieżdżenia użylibyśmy następującego zapytania:

http://localhost:8983/solr/graph/select?q=*:*&fq={!graph from=parent_id to=id}name:"root document"

Dokumenty które zostały zwrócony przez Solr wyglądają następująco:

<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  <bool name="zkConnected">true</bool>
  <int name="status">0</int>
  <int name="QTime">8</int>
  <lst name="params">
   <str name="q">*:*</str>
   <str name="fq">{!graph from=parent_id to=id}name:"root document"</str>
  </lst>
 </lst>
 <result name="response" numFound="8" start="0" maxScore="1.0">
 <doc>
  <str name="id">1</str>
  <str name="name">Root document one</str>
  <long name="_version_">1531331026113003520</long></doc>
 <doc>
  <str name="id">11</str>
  <str name="parent_id">1</str>
  <str name="name">First level document 1, child one</str>
  <long name="_version_">1531331026114052096</long></doc>
 <doc>
  <str name="id">12</str>
  <str name="parent_id">1</str>
  <str name="name">First level document 1, child two</str>
  <long name="_version_">1531331026115100672</long></doc>
 <doc>
  <str name="id">13</str>
  <str name="parent_id">1</str>
  <str name="name">First level document 1, child three</str>
  <long name="_version_">1531331026115100673</long></doc>
 <doc>
  <str name="id">122</str>
  <str name="parent_id">12</str>
  <str name="name">Second level document 12, child two</str>
  <long name="_version_">1531331026120343552</long></doc>
 <doc>
  <str name="id">2</str>
  <str name="name">Root document two</str>
  <long name="_version_">1531331026109857792</long></doc>
 <doc>
  <str name="id">3</str>
  <str name="name">Root document three</str>
  <long name="_version_">1531331026110906368</long></doc>
 <doc>
  <str name="id">21</str>
  <str name="parent_id">2</str>
  <str name="name">First level document 2, child one</str>
  <long name="_version_">1531331026111954944</long></doc>
 </result>
</response>

Jak widać, Solr zwrócił zarówno wierzchołki grafu, jak również wszystkie dokumenty, na wszystkich poziomach zagnieżdżenia.

Filtrowanie

Wyniki wyszukiwania mogą być filtrowane korzystając z filtra zdefiniowanego przy pomocy parametru traversalFilter. Pozwala on na zdefiniowanie filtra, który będzie wykorzystany do każdego poziomu zagnieżdżenia oprócz wierzcholków. Na przykład, jeżeli chcielibyśmy dostać tylko dokumenty, które mają term one w polu name moglibyśmy użyć następującego zapytania:

http://localhost:8983/solr/graph/select?q=*:*&fq={!graph from=parent_id to=id traversalFilter=name:one}name:"root document"

Wyniki wyszukiwania zwrócone przez Solr byłyby następujące:

<pre class="brush:xml">
<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  <bool name="zkConnected">true</bool>
  <int name="status">0</int>
  <int name="QTime">7</int>
  <lst name="params">
   <str name="q">*:*</str>
   <str name="fq">{!graph from=parent_id to=id traversalFilter=name:one}name:"root document"</str>
  </lst>
 </lst>
 <result name="response" numFound="5" start="0" maxScore="1.0">
 <doc>
  <str name="id">1</str>
  <str name="name">Root document one</str>
  <long name="_version_">1531331026113003520</long></doc>
 <doc>
  <str name="id">11</str>
  <str name="parent_id">1</str>
  <str name="name">First level document 1, child one</str>
  <long name="_version_">1531331026114052096</long></doc>
 <doc>
  <str name="id">2</str>
  <str name="name">Root document two</str>
  <long name="_version_">1531331026109857792</long></doc>
 <doc>
  <str name="id">3</str>
  <str name="name">Root document three</str>
  <long name="_version_">1531331026110906368</long></doc>
 <doc>
  <str name="id">21</str>
  <str name="parent_id">2</str>
  <str name="name">First level document 2, child one</str>
  <long name="_version_">1531331026111954944</long></doc>
 </result>
</response>

Jak widać, dokumenty zwrócone przez Solr zostały przefiltrowane z użyciem filtra, który zdefiniowaliśmy.

Zwracanie liści, lub wierzchołków

Oprócz filtrowania możemy także powiedzieć Solr, aby zwrócone zostały same liście oraz aby pominięte zostały wierzchołki grafu. Aby pominąć zwracanie wierzchołków grafu powinniśmy skorzystać z parametru returnRoot i ustawić go na false, na przykład::

http://localhost:8983/solr/graph/select?q=*:*&fq={!graph from=parent_id to=id returnRoot=false}name:"root document"

Wyniki takiego zapytania przedstawiają się następująco:

<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  <bool name="zkConnected">true</bool>
  <int name="status">0</int>
  <int name="QTime">10</int>
  <lst name="params">
   <str name="q">*:*</str>
   <str name="fq">{!graph from=parent_id to=id returnRoot=false}name:"root document"</str>
  </lst>
 </lst>
 <result name="response" numFound="5" start="0" maxScore="1.0">
 <doc>
  <str name="id">11</str>
  <str name="parent_id">1</str>
  <str name="name">First level document 1, child one</str>
  <long name="_version_">1531331026114052096</long></doc>
 <doc>
  <str name="id">12</str>
  <str name="parent_id">1</str>
  <str name="name">First level document 1, child two</str>
  <long name="_version_">1531331026115100672</long></doc>
 <doc>
  <str name="id">13</str>
  <str name="parent_id">1</str>
  <str name="name">First level document 1, child three</str>
  <long name="_version_">1531331026115100673</long></doc>
 <doc>
  <str name="id">122</str>
  <str name="parent_id">12</str>
  <str name="name">Second level document 12, child two</str>
  <long name="_version_">1531331026120343552</long></doc>
 <doc>
  <str name="id">21</str>
  <str name="parent_id">2</str>
  <str name="name">First level document 2, child one</str>
  <long name="_version_">1531331026111954944</long></doc>
 </result>
</response>

Jak łatwo zauważyć, wyniki wyszukiwania nie zawierają wierzchołków grafu.

Jeżeli jesteśmy zainteresowani tylko liśćmi, powinniśmy korzystać z parametru returnOnlyLeaf i ustawić go na wartość true (domyślna wartość to false).

Maksymalna ilość zagnieżdżeń

Oprócz wymienionych wcześniej możliwości wpływania na zapytanie, możemy także powiedzieć Solr, jak głęboko w graf chcemy spojrzeć korzystając z parametru maxDepth. Domyślnie wartość parametru ustawiona jest na -1 co oznacza, że Solr postara się wyświetlić wszystkie poziomy zagnieżdżenia grafu. Na przykład, jeżeli jesteśmy zainteresowani tylko pierwszym poziomem zagnieżdżenia skorzystaliśmy z następującego zapytania:

http://localhost:8983/solr/graph/select?q=*:*&fq={!graph from=parent_id to=id maxDepth=1}name:"root document"

Wyniki wyszukiwania zawierać będą jedynie wierzchołki grafu oraz pierwszy poziom zagnieżdżenia:

<?xml version="1.0" encoding="UTF-8"?>
<response>
 <lst name="responseHeader">
  <bool name="zkConnected">true</bool>
  <int name="status">0</int>
  <int name="QTime">10</int>
  <lst name="params">
   <str name="q">*:*</str>
   <str name="fq">{!graph from=parent_id to=id maxDepth=1}name:"root document"</str>
  </lst>
 </lst>
 <result name="response" numFound="7" start="0" maxScore="1.0">
 <doc>
  <str name="id">1</str>
  <str name="name">Root document one</str>
  <long name="_version_">1531331026113003520</long></doc>
 <doc>
  <str name="id">11</str>
  <str name="parent_id">1</str>
  <str name="name">First level document 1, child one</str>
  <long name="_version_">1531331026114052096</long></doc>
 <doc>
  <str name="id">12</str>
  <str name="parent_id">1</str>
  <str name="name">First level document 1, child two</str>
  <long name="_version_">1531331026115100672</long></doc>
 <doc>
  <str name="id">13</str>
  <str name="parent_id">1</str>
  <str name="name">First level document 1, child three</str>
  <long name="_version_">1531331026115100673</long></doc>
 <doc>
  <str name="id">2</str>
  <str name="name">Root document two</str>
  <long name="_version_">1531331026109857792</long></doc>
 <doc>
  <str name="id">3</str>
  <str name="name">Root document three</str>
  <long name="_version_">1531331026110906368</long></doc>
 <doc>
  <str name="id">21</str>
  <str name="parent_id">2</str>
  <str name="name">First level document 2, child one</str>
  <long name="_version_">1531331026111954944</long></doc>
 </result>
</response>

Podsumowanie

Możliwość pracy z grafami jest zdecydowanie kolejną po wsparciu dla SQL oraz replikacji pomiędzy centrami danych w SolrCloud funkcjonalnością na którą warto spojrzeć i o której warto pamiętać. Niestety nie mieliśmy jeszcze okazji testować wydajności opisywanego rodzaju zapytań, ale spróbujemy w kolejnych wpisach przedstawić takie testy.

Aktualizacja

Nie wspomnieliśmy, ale jak widać na przykładzie, nie wszystkie dokumenty z naszego testowego indeksowania zostały uwzględnione w wynikach wyszukiwania. Wiąże się to niestety z SolrCloud i wyszukiwaniem rozproszonym. W chwili obecnej, jeżeli chcemy mieć widoczne wszystkie dokumenty, musimy zadać zapytanie do każdej części kolekcji osobno, bądź skorzystać z kolekcji, która zbudowana jest z pojedynczego sharda.

This entry was posted on poniedziałek, Kwiecień 18th, 2016 at 08:28 and is filed under Lucene, 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.