Bij een van mijn werkgevers kwam ik veel in contact met Apache Lucene een open source project over het indexeren en professioneel vinden van informatie. Omdat wij destijds een applicatie moesten ontwikkelen welke informatie over een bedrijf verzamelde en zelf op basis van strenge criteria ontsluit heb ik er enorm veel over geleerd.
Een van de belangrijkste redenen om te kiezen voor Apache Lucene was destijds omdat alle functionaliteit die werd aangeboden door dit platform een vereiste was om hiermee aan de slag te gaan. Wij gebruiken hiervoor een ander software pakket wat veel geld vroeg voor dezelfde functionaliteit (zo’n bedrag met een paar nullen
).
Inhoudsopgave
Zend_Search_Lucene V.S. MySQL Fulltext search?
MySQL full-text search is in de basis een mooie oplossing voor niet te complexe zoekvragen en als men geen hoge eisen stelt aan de structuur waarop MySQL informatie opslaat en vindbaar maakt.
Wat mij zelf dwong om met Zend Lucene aan de slag te gaan was omdat ik van de MySQL MyIsam engine overgestapt was op InnoDB in verband met integriteit van je database, row level locking en transacties. Deze functionaliteit wou ik niet opofferen voor het fulltext indexeren van mijn data. Overigens kun je in PostGreSQL deze dingen wel combineren met PostGreSQL in combinatie met TSearch2.
Maargoed, ondanks de voordelen van de InnoDB engine was er wel een aanzienkelijke behoefte van het beter vindbaar maken van informatie. Toen kwam Zend_Search_Lucene om de hoek kijken en wekte dit direct mijn grote interesse. Zend_Search_Lucene is namelijk een PHP implementatie geschreven door Zend (uiteraard) van Apache Lucene. Daarnaast is het een onderdeel van het Zend Framework.
Naast wat je ook met MySQL fulltext search zou kunnen doen biedt Zend Lucene ook een aantal extra interessante specialiteiten:
- Similarity (relevantie)
- Hoge kwaliteit ranking
- Zoeken op velden
- Enorm veel query-typen (phrase queries, boolean queries, wildcard queries, proximity queries, range queries, etc)
- Highlighting van je zoekresultaat
En zul je tijdens het werken met Zend Lucene nog wel meer voordelen tegen komen! Enige nadeel van het gebruiken van een aparte index naast je database is dat je elke keer als er een bepaald record (informatie) toegevoegd is in de database je ook je index moet bijwerken.
Praktijk voorbeeld
Een voorbeeld zou kunnen zijn dat je een tabel met producten bijhoudt voor je webshop. Data waarop je graag terug gevonden word is o.a. product titel, fabrikant, omschrijving van het product, categorie, etc. In ons geval was het ook tags, eigenschappen, etc.
Je zou natuurlijk een database-query kunnen doen met een LIKE phrase alleen dan worden de zoekresultaten niet gesorteerd op basis van relevantie en kun je alleen zoeken op de (of een gedeelte van de) naam van het product bijvoorbeeld. Stel je hebt een site met televisies en een van je producten heeft de titel “philips televisie” dan word je product niet gevonden als men zoekt op “televisie philips” en andersom wel. Met Zend_Search_Lucene was het product in beide gevallen gevonden. Omdat Zend Lucene kijkt naar de woorden waarop gezocht word hoevaak en wanneer deze voorkomen in de velden waar men op zoekt (standaard alle velden).
Omdat onze webshop maar ging om niet meer dan 100 producten hebben wij ervoor gekozen om elke nacht de hele index te wissen en opnieuw op te bouwen. Uit mijn ervaring weet ik dat een index niet helemaal lekker werkt als er veel updates zijn geweest. Ondanks het optimizen, etc. Praat je over duizenden documenten is het toch raadzamer om je index te updaten.
Enkele tips bij het gebruiken van Zend_Search_Lucene
- Sla je index op een veilige plaats op buiten de webroot of beveilig de index directory
- Denk goed na over over de manier waarop je velden opslaat
- Afhankelijk van het aantal documenten wat je gaat opslaan is het belangrijk je index op tijd te optimizen (voor performance) en wellicht ook installeren zoals de mergefactor aan te passen?
Merge factor
Een belangrijke instelling is je mergefactor. Wanneer Zend Lucene documenten aan indexeert creeërt deze segmenten welke ieder een X aantal documenten bevat. Afhankelijk van de mergefactor zal Zend Lucene deze segmenten samenvoegen wanneer X aantal documenten is bereikt. Standaard is de Mergefactor 10. Hoe kleiner deze factor des te sneller je niet geoptimaliseerde zoekresultaten zijn maar des te langzamer het indexeren gaat en word er niet veel geheugen gebruikt. Zet je deze waarde hoger dan zal het indexeren sneller gaan van je documenten maar worden deze minder snel terug gevonden in een niet geoptimaliseerde index en neemt het geheugen gebruik ook toe (let ook op je memory_limit instelling in je PHP.ini).
Stel dat je dus elke nacht alle documenten opnieuw gaat indexeren is het raadzaam om deze waarde wat hoger te zetten, op 50 bijvoorbeeld, of misschien wel 100. Het is dan wel raadzaam om de optimaliseer functionaliteit te gebruiken wanneer je volledige index herbouwt is.
Mergefactor instellen:
|
1 2 3 4 5 6 |
<?php $index = Zend_Search_Lucene::open('/path/to/my/index'); $index->setMergeFactor(50); //indexeer hier je documenten.. ?> |
Optimaliseren van je index
Het is raadzaam na het opbouwen van je index of wanneer je index flink gewijzigd is deze te optimaliseren. Dit zal ervoor zorgen dat documenten veel sneller gevonden word. Zend Lucene zorgt ervoor dat de slecht geoptimaliseerde index verdeeld in meerdere bestanden in 1 bestand word gezet wat voor performance winst zorgt. Het optimaliseren van je index is erg simpel:
|
1 2 3 4 |
<?php $index = Zend_Search_Lucene::open('/path/to/my/index'); $index->optimize(); ?> |
Het beste is om bovenstaande code uit te voeren in de commandline via bijvoorbeeld een cronjob. Ook is het belangrijk dat je de maximale executie tijd van je script voldoende is omdat het optimaliseren van een index aardig wat tijd kan kosten.
Veld typen
Als je een document indexeert met Zend Lucene sla je in een document velden op. Deze velden kunnen bijvoorbeeld ‘title’, ‘description’ of ‘category’ zijn. Informatie die belangrijk is bij het vindbaar maken van je informatie. De manier waarop deze velden opgeslagen zijn kan best belangrijk zijn. Zie hieronder een belangrijke tabel met mogelijke veld typen:
| Field Type | Stored | Indexed | Tokenized | Binary |
| Keyword | yes | yes | no | no |
| UnIndexed | yes | no | no | no |
| Binary | yes | no | no | yes |
| Text | yes | yes | yes | no |
| UnStored | no | yes | yes | no |
Keyword
Een keyword veld word wel opgeslagen in de index maar word niet ‘tokenized’ heel simpel gezegd betekend dit dat Lucene de inhoud van dit veld niet uitgebreid gaat analyseren om er zo voor te zorgen dat het op verschillende manier gevonden kan worden. Dit veld type kan je sterk aanraden om te gebruiken om bijvoorbeeld ID’s in op te slaan. Zoals een ‘productid’ zodat je altijd vanuit de gevonden resultaten terug kan corresponderen met het juiste product in je database.
UnIndexed
Een ‘UnIndexed’ veld is een veld waarbij de inhoud van het veld wel opgeslagen word maar deze niet vindbaar word gemaakt. Heeft dus vrij sterke overeenkomsten met ‘keyword’ behalve dat een ‘keyword’ veld nog wel te vinden is. Het is makkelijk als je bijvoorbeeld bepaalde informatie toegang wilt bieden vanuit je zoekresultaten maar welke niet belangrijk zijn voor het vindbaar maken van je informatie. Omdat ik veelal Zend Lucene combineer met een MySQL tabel is dit bij mij in de meeste gevallen overbodig en sla ik met name de velden die vindbaar moeten worden op in mijn index.
Binary
Een ‘binary’ veld is zo goed als hetzelfde als een ‘UnIndexed’ veld, dit veld is niet vindbaar maar word wel opgeslagen zodat het gebruikt kan worden in de zoekresultaten. Dit veld kan bijvoorbeeld gebruikt worden om de binaire data op te slaan van een afbeelding o.i.d. Zelf gebruik ik dit veld ook nauwelijks.
Text
Tekst velden of ‘text fields’ zijn velden die worden opgeslagen in de index en ‘tokenized’ ofwel; vindbaar worden. Dit veldtype word vaak gebruikt voor bijvoorbeeld de omschrijving of de titel van een document (of in ons geval een product). Let er wel op, dat dit type veld het meeste ruimte in beslag neemt. Heb je de data niet nodig in je zoekresultaten kun je beter het veld type ‘unstored’ gebruiken.
Unstored
Een ‘unstored’ veld word de waarde van het veld niet opgeslagen maar wel vindbaar gemaakt (‘tokenized’). Dit type gebruik ik zelf ook vaak voor mijn velden. Omdat veel velden toch al in de database opgeslagen worden en zou het redundant zijn om ze ook in mijn Zend Lucene index op te slaan.
Tot slot
In het artikel worden de belangrijkste dingen uitgelegd over de voordelen van Zend Lucene en waar je rekening mee dient te houden. Ik wil in een andere blogpost later graag een keer dieper in gaan op de techniek en samen met je een index opbouwen. Dat is nog iets teveel voor 1 artikel. Dit artikel is vooral om je te overtuigen in de kracht van Zend Lucene.
Je zou er bijna Google mee kunnen nabouwen
Maar biedt natuurlijk een hele mooie uitkomst bij de indexatie van bijvoorbeeld je nieuwsartikelen, of de producten in je webshop of berichten op een forum etc!
Wil je hulp bij de integratie van Zend Lucene jouw bedrijf / website? Neem dan contact met mij op.
Een id moet toch niet geïndexeerd worden? Dus moet het als unindexed opgeslagen worden.
Hallo Pim,
Bedankt voor je reactie. Ik zou een dergelijk veld niet als unIndexed opslaan om 2 belangrijke redenen:
Reden 1: Stel dat je een index met producten opbouwt en je dus een veld productid nodig hebt. Dan zou je zeggen dat je productid alleen nodig hebt om de juiste MySQL gegevens uit de tabel producten te halen maar stel dat het product gewijzigd word? Dan zul je het document in de index ook moeten updaten.
Om een document te updaten in (Zend) Lucene zul je het document eerst moeten verwijderen en daarna opnieuw moeten toevoegen (Lucene ondersteund geen document updates). Om het goede document te verwijderen zul je het wel moeten terug vinden en dat zou heel lastig worden als je productid als een unIndexed veld op zou slaan. Als je het als keyword veld opslaat kun je het volgende doen:
Stel dat je zoals in mijn geval elke nacht je hele index opbouw zou opbouwen dan is dat uiteraard (zoals jij aangeeft) niet perse nodig.
Reden 2: Stel dat je naast productid ook categoryid als veld wilt indexeren en je stelt je gebruiker in staat om in een vooraf gedefinieerde categorie te zoeken dan zou ik me de volgende query kunnen voorstellen:
Met
Zend_Search_Lucene_Index_Termkun je een keyword veld gebruiken, hierbij vertel ik Zend Lucene dat het keyword ‘categoryid’ met de waarde 10 moet (dit door het argumenttruemee te geven) voorkomen in het gevonden document (naast de gebruikers criteria) . Hiermee sluit je dus andere categorieën uit!Ik hoop dat het een beetje duidelijk is waarom ik adviseer om het niet te doen.
@Kees: 10000 x keer bedankt! You made my day.
@Peter, graag gedaan hoor!