Solr, deel 1: Introductie tot faceted search nl

Door creator1988 op vrijdag 14 januari 2011 14:48 - Reacties (5)
Categorie: Backend, Views: 7.584

Alle huizen op funda staan netjes opgeslagen in een SQL Server database. Normaal gesproken best praktisch, maar niet flexibel genoeg om al onze wensen eenvoudig te vervullen. Vandaar de overstap naar een zoekplatform dat hier wél voor geoptimaliseerd is. Vandaag deel 1 in een technische serie over de overgang naar Solr.

Bij onze zoektocht naar nieuwe software is het van belang dat deze snel is, want we serveren tijdens een beetje drukte makkelijk meer dan 300 pageviews per seconde. Daar ligt ook meteen het goede nieuws: Solr is blazingly fast! Op één instance haalden we al een performance van 1.000 req/s. En dat is niet alleen zoeken, maar inclusief het converteren van en naar .NET code. En het mooie is; het integreren binnen een bestaande .NET omgeving is helemaal niet zo lastig!

Faceted search
Faceted search is het clusteren van zoekresultaten in relevante categoriën; om snel en intuitief door grote sets data heen te filteren:
http://weblogs.asp.net/blogs/drnetjes/CNET_faceted_search.jpg
Het hele doel van dit implementatietraject is om dit eenvoudiger te laten verlopen, zoals in een eerder artikel al eens uitgezet is.

Onze pre-Solr oplossing
Voordat we met Solr aan de slag gingen gebruikten we veel redundante kolommen op funda, waarbij we voor elk hiervan een COUNT(...) deden:
http://weblogs.asp.net/blogs/drnetjes/wide_table2.png
Wanneer een gebruiker dus zocht naar 'Amsterdam', dan zag een gemiddelde query er uit als:

SQL:
1
2
3
SELECT COUNT(hasGarden), COUNT(yearBuilt1930_1940), COUNT(yearBuilt1941_1950), COUNT(etc...) 
FROM KoopObjecten
WHERE city = 'Amsterdam'


Nadelen van deze oplossing zijn:
  • Toevoegen van nieuwe facetten is arbeidsintensief
  • Performance is niet acceptabel bij grote datasets
En wanneer je dan flink wat meer huizen wil tonen op je site, gaan deze argumenten vrij zwaar wegen.

Welkom bij Solr
Wikipedia:
"Solr is an open source enterprise search server based on the Lucene Java search library, with XML/HTTP and JSON APIs, hit highlighting, faceted search, caching, replication, and a web administration interface."
Je moet Solr dus ook niet als database zien, maar meer als een grote index. Wanneer je data upload naar de server wordt de data geanalyseerd, en wordt er een inverted index van gebakken. Op deze manier kan er razendsnel worden gezocht. Voor meer info over de werking van de indexer kan je op de Solr wiki terecht.

Na installatie is het opvragen van zoekresultaten eenvoudig. Het is namelijk niets anders dan het opvragen van een URL in de browser:

HTTP:
1
http://localhost:8983/solr/select?q=city:Amsterdam


Er wordt nu standaard XML teruggegeven:

XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<response>
     <result name="response" numFound="3" start="0">
         <doc>
            <long name="id">3203</long>
            <str name="city">Amsterdam</str>
            <str name="steet">Keizersgracht</str>
            <bool name="hasGarden">false</bool>
            <int name="yearBuilt">1932</int>
        </doc>
        <doc>
            <long name="id">3205</long>
            <str name="city">Amsterdam</str>
            <str name="steet">Vondelstraat</str>
            <bool name="hasGarden">true</bool>
            <int name="yearBuilt">1938</int>
         </doc>
         <doc>
            <long name="id">4293</long>
            <str name="city">Amsterdam</str>
            <str name="steet">Trompstraat</str>
            <bool name="hasGarden">true</bool>
            <int name="yearBuilt">1949</int>
         </doc>
      </result>
   </response>


Het wordt echter pas interessant, als we aangeven dat we ook de faceted search willen hebben. Dit kan door extra parameters mee te geven:

HTTP:
1
...&facet.field=hasGarden&facet.query=yearBuilt:[1930 TO 1940]&facet.query=yearBuilt:[1941 TO 1950]


Naast de standaard resultaten voegt Solr nu een extra sectie toe aan het xml document, met daarin de facetten voor alle objecten binnen je zoekopdracht vallen (ook degene die je niet ziet door bijv. paging)!

XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<response>
      <result name="response" numFound="3" start="0">
         ...
      </result>
      <lst name="facet_counts">
         <lst name="facet_queries">
            <int name="yearBuilt:[1930 TO 1940]">2</int>
            <int name="yearBuilt:[1941 TO 1950]">1</int>
         </lst>
         <lst name="facet_fields">
            <lst name="hasGarden">
               <int name="true">2</int>
               <int name="false">1</int>
            </lst>
         </lst>
      </lst>
   </response>



Zelf spelen?
No problemo. Zorg dat Java is geïnstalleerd op je machine, en open deze tutorial. Binnen een uur heb je Solr geïnstalleerd, geconfigureerd, data geüpload en je eerste queries gedaan. Solr werkt ook onder Windows zonder verdere problemen.

Tips en best practices voor Solr met .NET
  • In tegenstelling tot wat in de tutorial staat, was het voor ons makkelijker zijn om Solr te hosten binnen Tomcat. Tomcat draait gewoon als Windows service; met alle voordelen van dien. Voor installatie: zie SolrTomcat.
  • Gebruik de 64-bits versie van Tomcat. Bij ons verdubbelde het aantal req/s.
  • Gebruik .NET's XmlReader om de XML van Solr te converteren naar .NET objecten. XPath is te traag.
  • Gebruik filter queries ("fq" ipv "q") als het mogelijk is. Deze worden namelijk gecached, en dat scheelt aanzienlijk in snelheid.
Next up?
In de nabije toekomst wordt deze serie vervolgd met een artikel over het synchroon houden van de data in SQL Server en in de Solr index!

Dit was een gastbijdrage van Dion Olsthoorn, mede-developer bij funda.

Volgende: Rails' respond_to in ASP.NET MVC 01-'11 Rails' respond_to in ASP.NET MVC
Volgende: Javascript, state van snel opvolgende AJAX requests bijhouden (?) 01-'11 Javascript, state van snel opvolgende AJAX requests bijhouden (?)

Reacties


Door Tweakers user krvabo, vrijdag 14 januari 2011 15:01

Interessant wel om even te lezen, maar waarom hadden jullie eerst een count() ? Een dynamisch opgebouwde 'where' lijkt me sneller?

Door Tweakers user creator1988, vrijdag 14 januari 2011 15:03

krvabo schreef op vrijdag 14 januari 2011 @ 15:01:
Interessant wel om even te lezen, maar waarom hadden jullie eerst een count() ? Een dynamisch opgebouwde 'where' lijkt me sneller?
Het is een combinatie. Stel dat we het facet 'oppervlakte' willen tonen, voor de zoekopdracht 'Amsterdam, met tuin'. Dan wordt de query:

SQL:
1
2
3
SELECT COUNT(opp_tot50), COUNT(opp_50_100), COUNT(opp_100_150), etc.
FROM Koop
WHERE plaats = 'Amsterdam' AND indTuin = 1


Dus die count is puur voor de facetten, anders moet je extreem veel queries doen.

Door Tweakers user Xudonax, vrijdag 14 januari 2011 15:14

Solr is een erg mooi stukje software, gebruik het zelf icm Django (Python). Ik zit zelf ook erg te kijken naar de facetted search mogelijkheden en ga dit stukje dan ook zeker volgen. Het moeilijkste om op te zetten is in mijn ervaring Tomcat, en zelfs dat is bijzonder eenvoudig.

En ook eens tijd om te kijken of ik fq kan gaan gebruiken in plaats van q. Niet dat ik zoveel requests heb op mijn site, maar caching is altijd mooi :)

Door Tweakers user dionoid, vrijdag 14 januari 2011 16:10

Jan, dit ziet er strak uit. Dank voor je vertaling en opmaak van code & sql !

Door Tweakers user analog_, vrijdag 14 januari 2011 20:59

Ik heb dat probleem anders opgelost, elke 'facet' een id, gekoppeld met een item (huis in jouw geval) met een value. Ik weet dat dit lichtjes goor is omdat het een database in een database begint te vormen maar t'werkt bijzonder snel. De waardes worden op voorhand berekend tijdens het invoeren/updaten van de item.

Reageren is niet meer mogelijk