Flexible Suche – einfach gemacht

Verteilte Echtzeitsuche mit Elasticsearch
Kommentare

Die verteilte Echtzeitsuch- und -analyse-Engine Elasticsearch kann ohne Programmieranpassungen horizontal skaliert werden und unterstützt dabei High Availability mit automatischer Fehlerbehandlung bei Node-Ausfällen. Auch mehrere Indizes zu verwalten, stellt für Elasticsearch kein Problem dar. Die Kommunikation erfolgt über ein einfaches und sehr gut dokumentiertes RESTful-API. Die Liste der Features ist lang. Ein paar der wichtigsten werden wir in diesem Artikel präsentieren und weiterführende Informationsquellen nennen.

In fast jedem Bereich der Softwareentwicklung gibt es eine Vielzahl an unterschiedlichen Produkten. So ist es auch bei den Search-Engines der Fall. Eine große Zahl der Search-Engines, darunter auch Elasticsearch, wird unter einer Open-Source-Lizenz angeboten und ist ohne große Investitionen in eigenen Produkten einsetzbar. Viele dieser Engines haben sich in unterschiedlichsten Einsatzszenarien bewährt. Zu diesen gehört zweifelsohne Elasticsearch.

Es baut wie einige andere Searchframeworks (SolrIndex Tank, Bobo Search etc.) auf Apache Lucene auf. Ohne Erweiterungen unterstützt Elasticsearch die Aufteilung der Daten auf mehrere Shards, eine Art Datencontainer. In einem Set-up mit mehreren Instanzen von Elasticsearch (also mehreren Nodes), die zu einem Cluster zusammengefasst sind, können die Daten durch Sharding auf mehrere Nodes verteilt werden, was die Verwaltung sehr großer Datenmengen erst möglich macht. Gleichzeitig kann auch die Replikation verwendet werden. In diesem Fall werden die Shards auf einem oder mehreren Nodes dupliziert, was sich positiv auf die Geschwindigkeit und auch Ausfallsicherheit auswirkt. Mehrere Indizes können auch unter einem Alias zusammengefasst werden. Dadurch können auch neue Indizes im Produktivbetrieb hinzugefügt oder bestehende ersetzt werden, ohne die Geschwindigkeit oder die Stabilität der Suche negativ zu beeinflussen.

Wie jede ausgewachsene Search-Engine ist auch Elasticsearch sehr vielfältig einsetzbar. Es gibt bereits fertige Extensions für Onlineshops, CRM- und Content-Management-Systeme sowie gute Bibliotheken, um Elasticsearch in eigenen Webanwendungen zu integrieren. Da die Einbindung mit existierenden Extensions fast immer trivial ist, möchten wir uns in diesem Artikel auf eine generische Lösung unter Verwendung einer Bibliothek konzentrieren. Doch bevor wir Elasticsearch benutzen können, muss es zunächst installiert und eingerichtet werden.

Elasticsearch: Installation und Konfiguration

Als Grundlage für die Beschreibung der Installation benutzen wir Ubuntu 12.04. Da Elasticsearch auf Lucene aufbaut, wird die Java-Laufzeitumgebung vorausgesetzt. Wenn diese auf dem Server noch nicht installiert wurde, kann man dies mit sudo apt-get install openjdk-7-jre-headless -y nachholen.

Zum Zeitpunkt, als der Artikel geschrieben wurde, war Elasticsearch 0.90.5 die aktuellste Version. Es gibt schon ein fertiges Deb-Package dafür und es kann mit nur wenigen Befehlen heruntergeladen, installiert und als Service gestartet werden:

wget ht tps://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-0.90.5.deb
sudo dpkg -i elasticsearch-0.90.5.deb
sudo service elasticsearch start

Mehr muss nicht gemacht werden. Wenn man jetzt im Browser die IP des Servers und Port 9200 aufruft (z. B. ht tp://192.168.4.164:9200/), sollte die JSON-Response aus Listing 1 kommen oder eine Ähnliche.

{
  "ok" : true,
  "status" : 200,
  "name" : "Yondu",
  "version" : {
    "number" : "0.90.5",
    "build_hash" : "5c38d6076448b899d758f29443329571e2522410",
    "build_timestamp" : "2013-08-06T13:18:31Z",
    "build_snapshot" : false,
    "lucene_version" : "4.4"
  },
  "tagline" : "You Know, for Search"
}

Nach der Installation liegen die Konfigurationsdateien elasticsearch.yml und logging.yml im Verzeichnis /etc/elasticsearch/. Wie die Dateiendung schon sagt, wird die ganze Konfiguration im YAML-Format gespeichert, was eine schnelle und unkomplizierte Anpassung der Einstellungen ermöglicht. Man ist allerdings nicht nur auf YAML beschränkt. Wenn man die Konfiguration im JSON-Format vornehmen möchte, muss nur die Datei in elasticseach.json umbenannt werden – und schon werden alle Einstellungen als JSON interpretiert. Für uns müssen lediglich folgende Einstellungen in der Konfigurationsdatei eingetragen werden:

cluster:
  name: "cluster"
node:
  name: "cluster-node"

Wenn wir elasticsearch mit sudo service elasticsearch restart neu starten und wieder ht tp://192.168.4.164:9200/ aufrufen, sehen wir, dass sich der Name unseres Nodes von beispielsweise Yondu in cluster-node geändert hat.

Für das Logging wird das in Elasticsearch bereits integrierte Apache-Projekt Log4j benutzt, dessen Standardkonfiguration in der Datei /etc/elasticsearch/logging.yml zu finden ist und bereits die wichtigsten Einstellungen beinhaltet. Nach diesen Schritten ist Elasticsearch installiert.

Installation von Elasticsearch-Head Manchmal möchte man den Search-Index sehen, ohne diesen programmatisch abfragen zu müssen, sei es denn, um sich einfach über den Status zu informieren oder schnell nachzuschauen, ob ein bestimmter Datensatz vorhanden ist. Dies und vieles mehr ist mit Elasticsearch-Head möglich, einem Web-Frontend für Elasticsearch. Die Installation ist ebenfalls einfach und in ein paar Schritten erledigt. Man geht in das bin-Verzeichnis von Elasticsearch (bei der Standardinstallation ist dies unter /usr/share/elasticsearch/bin) und führt das Kommando plugin -install mobz/elasticsearch-head aus. Jetzt kann man Elasticsearch-Head unter ht tp://192.168.4.164:9200/_plugin/head/ aufrufen (Abb. 1).
Abb. 1: Elasticsearch-Head im Browser
Abb. 1: Elasticsearch-Head im Browser
Installation von Elasticsearch-Tika-Plug-in Oft reicht es nicht, nur Daten direkt aus Strings zu indexieren – eine gute Suche muss unter Umständen auch Ergebnisse anzeigen können, die aus Word-, PDF- und anderen Dateien stammen. Bei Apache Foundation wird für das Einlesen dieser Dokumente ein Projekt unter dem Namen Tika geführt. Mithilfe von Tika ist es möglich, über 1 000 verschiedene Dateiformate zu parsen und dabei sowohl die Metadaten als auch den strukturierten Textinhalt zu extrahieren und für die Suche zur Verfügung zu stellen. Die Installation des Tika-Plug-ins für Elasticsearch ist sehr einfach. Im bin-Verzeichnis der Elasticsearch-Installation muss das Kommando plugin -install elasticsearch/elasticsearch-mapper-attachments/1.6.0 ausgeführt werden. Nach der Installation können dann bei den zu indexierenden Dokumenten einfach die Pfade zu den Dateien übergeben werden – den Rest erledigt Elasticsearch von alleine.

PHP-Libraries

Das API von Elasticsearch ist sehr mächtig. Außer dem Befüllen und Abfragen der Indizes kann man auch alle Einstellungen über das API vornehmen, neue Indizes anlegen und sogar die Konfiguration des Cores aktualisieren.

Für das PHP-Umfeld gibt es aber bereits fertige Libraries, die die ganze Funktionalität kapseln und zur Verfügung stellen, sodass man sich die meiste Zeit auf die Schnittstellen der Libraries konzentrieren kann.

Die offizielle Library für PHP ist auf GitHub zu finden. Sie bietet die Grundfunktionalität für die Arbeit mit Elasticsearch und wird aktiv unterstützt. Da eines der Ziele bei der Entwicklung dieser Library die Konsistenz zwischen den verschiedenen Implementierungen in unterschiedlichen Programmiersprachen ist, wird hier fast ausschließlich mit assoziativen Arrays gearbeitet. Das ist heute beim objektorientierten Ansatz nicht mehr besonders populär und kann die Arbeit und auch das Bugfixing erheblich erschweren.

Eine andere Library ist Sherlock. Sie versucht, die Philosophie von Elasticsearch auf ihre Arbeitsweise zu übertragen und arbeitet mit minimalen Anforderungen an die Einstellungen und versucht, ein einfaches und klares Interface zur Verfügung zu stellen. Leider ist diese Library noch im Alpha-Stadium, weswegen sehr gut abgewogen werden muss, ob man sie im Produktivbetrieb einsetzen sollte. Eine andere Library ist der Elasticsearch PHP Client. Diese stellt die grundsätzlichen Funktionen für die Verwendung mit Elasticsearch zur Verfügung, wurde aber das letzte Mal vor ca. sechs Monaten aktualisiert.

Es gibt aber auch eine Library, die sowohl sehr große Funktionalität in Verbindung mit Elasticsearch bietet als auch in einer stabilen Version vorliegt: Elastica. Die Versionierung der Library orientiert sich auch an den Versionsnummern von Elasticsearch. So sind die Elastica-Versionen 0.90.1.* kompatibel mit Elasticsearch in der Version 0.90.1 und auch 0.90.5. Diese Library eignet sich somit sehr gut dafür, die Funktionsweise von Elasticsearch und die Arbeit mit der Such-Engine zu zeigen. Im weiteren Verlauf des Artikels werden wir deshalb Elastica verwenden.

Erste Schritte mit Elastica

Ein gutes Beispiel, um die Funktionsweise von Elasticsearch und Elastica zu demonstrieren, ist eine einfache Anwendung, um Posts zu speichern und später nach bestimmten Posts zu suchen. Wir werden diese Applikation im Laufe des Artikels aufbauen und daran zeigen, wie Elasticsearch genau funktioniert. Schauen wir uns also an, wie wir mit Elastica die erste Verbindung zu Elasticsearch aufbauen können.

Als Erstes werden wir die Ordnerstruktur für unser Demoprojekt anlegen und Elastica einbinden. Die Struktur sieht folgendermaßen aus:

- lib
  - Elastica
     - Bulk
      - ...
        - Cluster
          - ...
         ...
- bootstrap.php
- index.php

In der index.php inkludieren wir erst einmal nur die bootstrap.php, in der die Verbindung zu Elasticsearch auch initialisiert wird:

require_once('bootstrap.php');

In der bootstrap.php erstellen wir einen neuen Client für Elasticsearch. Damit die Dateien nicht explizit einzeln inkludiert werden müssen, muss eventuell noch eine Autoload-Funktion mit spl_autoload_register registriert werden. Wir werden hier aber nicht weiter darauf eingehen und setzen voraus, dass dies bereits schon vor der Verwendung von Elastica in Ihrem Projekt umgesetzt wurde.

Ein Elasticsearch-Client wird mit folgendem Konstruktor in bootstrap.php initialisiert und muss mindestens ein Array aus Host und Port als Argument bekommen:

$client = new ElasticaClient(array(
  'host' => '192.168.4.164',
  'port' => '9200'
));

Mit diesem Code wird der Client als single node initialisiert. Es ist ebenfalls möglich, gleich eine Verbindung zu einem Cluster aufzubauen, der aus mehreren Nodes besteht. Hätten wir z. B. noch eine Instanz unter 192.168.4.165 laufen, könnte man die Verbindung auch folgendermaßen initialisieren:

$client = new ElasticaClient(array(
    array('host' => '192.168.4.164', 'port' => '9200'),
    array('host' => '192.168.4.165', 'port' => '9200')
));
Methode Funktionalität
setConfig(array $config) setzt neue Konfigurationswerte und belässt die anderen (falls über den Konstruktor bereits gesetzt)
getConfig($key = “) gibt die ganze Konfiguration zurück oder einen bestimmten Konfigurationswert
setConfigValue($key, $value) setzt einen Konfigurationswert
getConfigValue($keys, $default = null) gibt einen Konfigurationswert zurück, falls nicht vorhanden, gibt es $default zurück
getIndex($name) gibt einen Index mit dem übergebenen Namen für die aktuelle Verbindung zurück
addHeader($header, $headerValue) setzt einen manuellen Header für die Verbindung
removeHeader($header) löscht einen Header mit dem übergebenen Namen
addDocuments(array $docs) fügt mehrere Dokumente ein; Typ und Index müssen im Dokument gesetzt werden und können unterschiedlich sein
updateDocument($id, $data, $index, $type, array $options = array()) aktualisiert ein bereits angelegtes Dokument
deleteDocuments(array $docs) löscht übergebene Dokumente
deleteIds(array $ids, $index, $type) löscht Dokumente anhand übergebener IDs
optimizeAll($args = array()) optimiert alle Such-Indizes
setLogger(LoggerInterface $logger) setzt einen Logger; dieser muss das Interface LoggerInterface implementieren

Tabelle 1: Auszug der Methoden von „ElasticaClient“

Nach diesem Schritt ist die Verbindung zu Elasticsearch aufgebaut und wir können bereits einen Index anlegen, um die Posts zu speichern:

$index = $elasticaClient->getIndex('posts');
Methode Funktionalität
getType($type) gibt einen Typ mit dem übergebenen Namen für den aktuellen Index zurück
getStatus() gibt den aktuellen Status des Index zurück
getStats() gibt die aktuelle Statistik für den Index zurück
getMapping() gibt alle Mapping-Typen für den Intex zurück
addDocuments(array $docs) fügt mehrere Dokumente dem aktuellen Index hinzu
create(array $args = array(), $options = null) legt einen neuen Index an
delete() löscht den Index
optimize($args = array()) optimiert den aktuellen Index
refresh() aktualisiert den Index und triggert die Verarbeitung der neu hinzugefügten Dokumente
search($query = “, $options = null) sucht im aktuellen Index; $query kann ein String, ein Array oder ein ElasticaQuery-Objekt sein
addAlias($name, $replace = false) fügt dem Index einen neuen Alias hinzu
removeAlias($name) löscht einen Alias für den aktuellen Index
flush($refresh = false) löscht die Daten aus dem Arbeitsspeicher (Elasticsearch verwendet eigene Heuristiken, um dies automatisch zu erledigen)

Tabelle 2: Auszug der Methoden von „ElasticaIndex“

$index->create(
  array(
    'number_of_shards' => 4,
    'number_of_replicas' => 0,
    'analysis' => array(
      'analyzer' => array(
        'indexAnalyzer' => array(
          'type' => 'custom',
          'tokenizer' => 'standard',
          'filter' => array('lowercase', 'postsFilter')
        ),
        'searchAnalyzer' => array(
          'type' => 'custom',
          'tokenizer' => 'standard',
          'filter' => array('standard', 'lowercase', 'postsFilter')
        )
      ),
      'filter' => array(
        'postsFilter' => array(
          'type' => 'standard',
          'stopwords' => 'english'
        )
      )
    )
  ),
  true
);

Das Anlegen über einen Post-Request würde fast genauso aussehen, nur mit dem Unterschied, dass dieser an ht tp://192.168.4.164:9200/posts gesendet werden müsste und dasselbe Array im JSON-Format erhalten sollte.

Eine „0“ bei Replicas bedeutet, dass wir die vier neuen Shards nicht auf mehrere Nodes aufteilen wollen – wir haben bis jetzt nur einen einzigen im neuen Cluster. Wie man sieht, können für die Indexierung und das Abfragen des Index verschiedene Analyzer verwendet werden. Wir nehmen aber für unser einfaches Beispiel dieselbe Konfiguration für beide. Die Filterung für die Indizierung und Suche übernimmt in unserem Beispiel der postsFilter. Das ist ein Standardfilter mit einem englischen Stopword-Wörterbuch (mehr über Analyzer). Die Funktionalität des Standardfilters beschränkt sich im Wesentlichen auf die Verwendung des Standard-Tokenizers, Standard-Token-Filters, Lower-Case-Token-Filters und Stop-Token-Filters.

Das true als zweiter Parameter bei create() bedeutet, dass ein Index gelöscht werden soll, falls schon einer mit demselben Namen existiert. Falls dies nicht gesetzt oder false ist, bekommt man eine Fehlermeldung von Elasticsearch und der bereits angelegte Index wird nicht überschrieben.

Abb. 2: Index für Posts

Abb. 2: Index für Posts

 

Nach diesem Schritt ist der Index konfiguriert (Abb. 2). Wir müssen aber noch das Mapping für Posts anlegen, damit die einzelnen Felder richtig geparst und durchsucht werden können. Das ist nicht zwingend notwendig, denn Elasticsearch kann die Typen der Felder selbst erraten. In diesem Fall müsste man jedoch im vorherigen Schritt bei den Analyzern statt indexAnalyzer und searchAnalyzer nur default_index und default_search angeben. Die Posts sollen in unserem Beispiel folgenden Aufbau haben:

- id
- timestamp
- topic
- post
- tags
- user
  - first name
      - last name

Um das Mapping zu erstellen, muss Listing 3 ausgeführt werden.

$type = $index->getType('post');

$typeMapping = new ElasticaTypeMapping();
$typeMapping->setType($type);
$typeMapping->setParam('index_analyzer', 'indexAnalyzer');
$typeMapping->setParam('search_analyzer', 'searchAnalyzer');

$typeMapping->setProperties(array(
    'id'        => array('type' => 'integer', 'include_in_all' => true),
    'timestamp' => array('type' => 'date', 'include_in_all' => true),
      'topic'     => array('type' => 'string', 'include_in_all' => true),
    'post'      => array('type' => 'string', 'include_in_all' => true),
    'tags'      => array('type' => 'string', 'include_in_all' => true),
    'user'      => array(
      'type'       => 'object',
      'properties' => array(
        'firstName' => array('type' => 'string', 'include_in_all' => true),
        'lastName'  => array('type' => 'string', 'include_in_all' => true)
      ),
    ),
));

$typeMapping->send();

Wir definieren hier einen neuen Typ für die Posts und legen für ihn ein Mapping an. Als index_analyzer und search_analyzer übergeben wir die von uns im früheren Schritt angelegten Analyzer. Der Einfachheit halber müssen bei allen Posts alle Felder standardmäßig durchsuchbar sein, auch wenn diese bei einigen Posts leer sein können. Deswegen wird bei allen Feldern include_in_all auf true gesetzt. Am Beispiel des Users sieht man auch, dass die Daten nicht nur eine flache Struktur, sondern auch eine verschachtelte haben können. Das erlaubt es, ohne großen Aufwand in einem Index alle relevanten Informationen bereitzustellen und auf externe Beziehungen zu verzichten.

Jetzt sind Index und Mapping angelegt und konfiguriert. Nach diesem Schritt können wir bereits die ersten Posts speichern (Listing 4).

$topics = array('apples','pears','peaches','tomatoes','peppers');
$tags = array('green fruit','green fruit','red fruit','red vegetable','red vegetable');
$postDocuments = array();
for ($i = 0; $i  $i + 1,
      'timestamp' => time() + $i,
      'topic'     => $topics[$i],
      'post'      => 'Here is a post about ' . $topics[$i],
      'tags'      => $tags[$i],
      'user'      => array(
        'firstName' => 'John',
        'lastName'  => 'Doe'
      )
  ));
}

$type->addDocuments($postDocuments);
$type->getIndex()->refresh();
Methode Funktionalität
addFile($key, $filepath, $mimeType = “) fügt eine Datei zum Dokument hinzu; das Tika-Plug-in muss vorher installiert sein
addGeoPoint($key, $latitude, $longitude) fügt einen Geo-Punkt hinzu
setData($data) überschreibt die Daten des Dokuments mit den übergebenen
setTtl($ttl) setzt die Lebensdauer des Dokuments
setType($type) setzt den Typ des Dokuments; falls die Dokumente über $type->addDocuments() hinzugefügt werden, muss er nicht explizit gesetzt werden
setVersion($version) setzt die Version des Dokuments
setParent($parent) setzt das Eltern-Element für das aktuelle Dokument

Tabelle 3: Auszug der Methoden von „ElasticaDocument“

Hier sehen wir auch direkt, dass mehrere Dokumente in einem Schritt mit addDocuments() zum Index hinzugefügt werden können, indem man dieser Methode ein Array mit ElasticaDocument-Objekten übergibt. Die Dokumente können auch einzeln hinzugefügt werden, indem man addDocument() aufruft und dieser Methode nur ein ElasticaDocument übergibt. Wenn wir jetzt im Index nachschauen, sehen wir, dass alle fünf Posts erfolgreich angelegt wurden (Abb. 3).

Abb. 3: Dokumente im Elasticsearch-Index

Abb. 3: Dokumente im Elasticsearch-Index

 

Query

Nach diesen Schritten sind nun alle Daten im Index und man kann mit dem Abfragen unseres Index beginnen. Die einfachste Abfrage würde zum Beispiel darin bestehen, einen Post zu finden, der die Wörter here oder about enthält. Dies lässt sich mit dem Code aus Listing 5 umsetzen.

$queryString  = new ElasticaQueryQueryString();
$queryString->setQuery('here about');

$query = new ElasticaQuery();
$query->setQuery($queryString);

$hits = $resultSet->getTotalHits();
$results = $resultSet->getResults();

Werden der Methode setQuery() mehrere Wörter in einem String übergeben, wird implizit mit einem booleschen Oder gesucht. Wenn man aber nach den Posts suchen will, die diese beiden Wörter enthalten müssen, könnte dies mit $queryString->setDefaultOperator(‚AND‘); erreicht werden.

Methode Funktionalität
setQuery($query = “) setzt den Query-String
setDefaultField($field) setzt das zu durchsuchende Feld; standardmäßig werden alle Felder durchsucht
setFields(array $fields) setzt die zu durchsuchenden Felder; standardmäßig sind alle Felder gesetzt
setDefaultOperator($operator) setzt den Operator; standardmäßig wird das logische Oder benutzt
setFuzzyMinSim($minSim = 0.5) setzt die minimale Ähnlichkeit für die Fuzzy-Suche
setFuzzyPrefixLength($length = 0) setzt die Präfixlänge für die Fuzzy-Query

Tabelle 4: Auszug der Methoden von „ElasticaQueryQueryString“

Methode Funktionalität
setQuery(AbstractQuery $query) setzt die Query
setFilter(AbstractFilter $filter) setzt den Filter
setFacets(array $facets) setzt die Facetten
setFrom($from) setzt einen Offset für die Suchergebnisse
setLimit($limit = 10) / setSize($size = 10) setzt das Limit für die Anzahl der Suchergebnisse
addSort($sort) setzt die Sortierung der Suchergebnisse
setHighlight(array $highlightArgs) setzt einen Highlighter für die gesuchten Wörter

Tabelle 5: Auszug der Methoden von „ElasticaQuery“

Nach dieser Abfrage befindet sich in $hits die Anzahl der gefundenen Dokumente und im Array $results die Dokumente, die für diese Query gefunden wurden. Das Ergebnis ist ein Array mit allen Posts, die wir bis jetzt zum Index hinzugefügt haben, da die Wörter here und about in allen Dokumenten enthalten sind. Die Response ist ein Array bestehend aus Objekten vom Typ ElasticaResult. Falls man auf diesen Objekten die Methode getData() aufrufen würde, hätte man ein assoziatives Array zurückbekommen, das dieselbe Struktur hätte wie beim Befüllen des Index.

Facetten

Jetzt möchten wir wissen, wie viele Tags es in unserer Ergebnismenge gibt und wie häufig diese vorkommen. Dafür müssen wir die Ergebnisse der Query nicht erst mühsam in PHP parsen und interpretieren, Elasticsearch kann das für uns viel schneller und effektiver übernehmen:

$tagFacet = new ElasticaFacetTerms('tagFacet');
$tagFacet->setField('tags');
$tagFacet->setOrder('count');

$query->addFacet($tagFacet);

Der Code bewirkt, dass wir jetzt neue Facetten haben, die sich auf das Feld tags beziehen. Die Reihenfolge der Facetten wird danach bestimmt, wie häufig die Tags in den Ergebnissen vorkommen. Wir könnten die Anzahl der Facetten auch mit $tagFacet->setSize(3); auf die drei häufigsten limitieren. Die um die Facetten erweiterte Query kann jetzt ausgeführt werden und die Ergebnisse werden wie bisher ausgelesen. Zusätzlich können wir aber auch noch mit $resultFacets = $resultSet->getFacets(); die Facetten auslesen, die Elasticsearch anhand der Query generiert hat. $resultFacets ist in diesem Beispiel ein Array, das aufgebaut ist wie in Listing 6.

Array (
  [tagFacet] => Array (
    [_type] => terms
    [missing] => 0
    [total] => 10
    [other] => 0
    [terms] => Array (
      [0] => Array (
        [term] => red
        [count] => 3
      )
      [1] => Array (
        [term] => fruit
        [count] => 3
      )
      [2] => Array (
        [term] => vegetable
        [count] => 2
      )
      [3] => Array (
        [term] => green
        [count] => 2
      )
    )
  )
)

Filter

Jetzt haben wir alle Posts und die dazugehörigen Facetten und wollen in diesen Posts unsere Suche noch weiter verfeinern, sodass wir nur die Posts erhalten, bei denen es sich um Gemüse handelt. In unserem Beispiel wären es zwei Posts über Tomaten und Pfeffer. Gleichzeitig wollen wir aber die Facetten erhalten, die zum Beispiel nach wie vor für eine Navigation interessant sein könnten. Diese Eingrenzung kann ohne großen Aufwand mit Filtern bewerkstelligt werden. Filter können die Ergebnismenge weiter eingrenzen, ohne dabei die über die Query bestimmte Facettierung zu beeinflussen. Hierfür fügen wir nach der Zeile $query->setQuery($queryString); noch folgenden Code aus Listing 7 ein.

$filterTopicTomatoes = new ElasticaFilterTerm();
$filterTopicTomatoes->setTerm('topic', 'tomatoes');

$filterTopicPeppers = new ElasticaFilterTerm();
$filterTopicPeppers->setTerm('topic', 'peppers');

$filterOr = new ElasticaFilterBoolOr();
$filterOr->addFilter($filterTopicTomatoes);
$filterOr->addFilter($filterTopicPeppers);

$query->setFilter($filterOr);

Unsere Ergebnismenge wird mit einem boolschen Oder-Filter erzeugt und liefert wie erwartet nur zwei Ergebnisse zurück. Wir haben hier nur die Filter angepasst und nicht die Query – wenn wir jetzt nach den Facetten schauen, hat sich dort nichts geändert, und wir haben immer noch Facetten für die Dokumente, die unter Umständen nicht mehr in unserer Ergebnismenge vorhanden sind.

Fazit

Auch wenn dieses Thema sehr groß ist und die Menge an unterschiedlichen Einsatzmöglichkeiten einen schnell überwältigen kann, ist der Einstieg doch relativ einfach und mit wenig Aufwand lassen sich bereits sehr brauchbare Ergebnisse erzielen. Auch die Qualität der Dokumentation und die Hilfsbereitschaft in der Community lassen fast keine Wünsche offen. Wir können deswegen jedem, der eine Suche in seiner Applikation implementieren will, nur empfehlen, Elasticsearch in Betracht zu ziehen und einfach mal auszuprobieren.

Michael Ryvlin

Autor

Michael Ryvlin

Michael Ryvlin ist zertifizierter Zend Engineer und Magento Developer und arbeitet als Softwareentwickler bei der Sitewards GmbH. Neben seiner großen Begeisterung für Magento interessiert er sich auch für Themen rund um Zend Framework, Doctrine und jQuery.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -