58. Sökmotorerna Elasticsearch och Solr
Innan jag började jobba med sökteknologi hade jag aldrig befattat mig med sökmotorerna Elasticsearch och Solr tidigare. Numera vet jag att de används i såväl små som stora, offentliga som privata organisationer och att de är bra att känna till även om man inte primärt jobbar med sök!
Inverterat index
Centralt för både Elasticsearch (ES) och Solr samt de flesta typiska sökmotorer (inklusive Google) är det inverterade indexet. Det är en datastruktur som likt indexet i en bok mappar nyckelord till ett eller flera platser (dokument) där det förekommer. Detta möjliggör snabbare och smidigare sökning i stora textmängder där dokumenten kan innehålla delar av eller hela söksträngen. Själva dokumenten har ofta en JSON-struktur med olika fält motsvarande delar av texten (t.ex. titel, författare, beskrivning) och annan metadata (t.ex. tillkomstdatum, ändringsdatum, URL, språk, nyckelord, m.m.). Varje textfält kan i sin tur leda till ett eget inverterat index X.
Lucene
Både Elasticsearch och Solr använder sig i grunden av det Java-baserade sökbiblioteket Lucene. Det skrevs ursprungligen av Doug Cutting 1999 och togs upp av den icke-vinstdrivande organisationen Apache Software Foundation 2001. Där blev det ett fristående projekt med öppen källkod år 2005. Mycket av den funktionalitet jag trodde var specifik för ES eller Solr är i grund och botten funktionalitet som redan finns inbyggd i Lucene, exempelvis fuzzy search och highlighting i sökresultaten. I Lucene är indexen uppbyggda i s.k. segment som var och en kan betraktas som egna index. Ett index växer fram genom att nya segment skapas när nya dokument läggs till (eller mergeas för att optimera indexet).
Indexering i Lucene
För att förstå hur dokument indexeras i Lucene måste vi bekanta oss med dess IndexWriter
-klass. IndexWritern har mandat att skapa upp ett nytt index eller att modifiera ett existerande. Den har tillstånd att skriva till indexet men inte att läsa av det eller att söka i det. Den använder sig av en annan central (och abstrakt) klass som heter Directory
och som bestämmer hur indexet ska lagras: på disk (via FSDirectory
-subklassen) eller i minnet (via RAMDirectory
eller det nyare MMapDirectory
).
När ett nytt dokument läggs till analyseras det först via Analyzer
-klassen och indexeras sedan till ett segment genom att IndexWritern commitar dokumenten den har i sitt arbetsminne till Directory
-klassen. I samband med detta skapas en ny segments_N
-fil som innehåller referenser till alla segment (via deras namn) och tillhörande detaljer om kodek samt dokumentradering. Det är genom segments_N
-filen (var N
är en siffra som ökas på för varje indexuppdatering) som Lucenes IndexReader
vet vilka segment den ska öppna.
NTR-sökning
Att commita nya dokument till disk (något som görs genom fsync
-metoden i Directory
) är en relativt tung operation. Trots det har både Elasticsearch och Solr en feature som kallas NTR-sökning, eller near real-time search, och som innebär att ett dokument är sökbart inom en sekund från att det skickats till indexering. Detta är tack vare ett mellanliggande filsystems-cache i Lucene som där det är möjligt att spara det nya segmentet med en soft commit
innan det skrivs till disk (hard commit
).
Vektorrumsmodeller, TF-IDF och BM25
Även relevansrankningen av dokument görs direkt i Lucene genom en kombination av vektorrumsmodeller och den s.k.bag-of-words-hypotesen, enligt vilken man kan uppskatta relevansen hos ett dokument vis-à-vis en söksträng genom att betrakta dem båda som en “ordpåse” utan egentlig ordning och räkna ordfrekvenserna. För att belöna dokument som innehåller mer ovanliga ord i söksträngen använder man TF-IDF och mer specifikt en variant av TF-IDF, BM25, som tar den genomsnittliga dokumentlängden i beaktning.
Elasticsearch
Elasticsearch är en relativt ung sökmotor (släppt första gången 2010, ursprungligen skapad av Shay Bannon) som blivit en av de allra populäraste för företagssök. Den kännetecknas av bl.a. multitenans (att flera användare/intressenter ska kunna använda samma Elastic-instans utan att märka av varandra), hög skalbarhet (att kunna lägga till nya servrar och få datan automatiskt ombalanserad i takt med att klustret växer) och flexibel JSON-mappning (dokument kan skickas in i sökmotorn utan att specificera deras fälttyper på förhand). Den hade från början helt öppen källkod under Apache 2.0-licens men licensieras sedan 2021 under andra former. ES är en del av ELK-stacken där även loggnings- och visualiseringsverktygen Logstash och Kibana ingår.
Solr
Solr är en lite äldre sökomotor som ursprungligen skrevs av Yonik Seeley vid det amerikanska mediaföretaget CNET Networks och sedan donerades till Apache Foundation 2006. Vad gäller funktionalitet är min uppfattning att ES och Solr är ganska likvärdiga. Det som brukar nämnas som den primära skillnaden är att Solrs källkod fortfarande är helt öppen under Apache 2.0-licens. En annan skillnad är hur kluster-koorderingen (hanteringen av indexets beståndsdelar och backup) sköts. Medan man i ES har koordineringen inbyggd använder man i Solr en annan Apache-komponent, Zookeeper, för att styra detta. En annan nämnvärd skillnad är hur sökfrågans och klusterkonfigureringens DSL (domain-specific language) ser ut. I ES brukar man sköta det mesta via JSON-kommandon medan man i Solr främst har använt XML-filer (t.ex. schema.xml
) för konfigurering av indexfält och ett ganska kompakt URI-anpassat språk för själva sökfrågorna.