Suche verstehen
Wir helfen Ihnen dabei, die Such­anfragen Ihrer Kund*innen und Mit­ar­bei­­ter*innen zu ver­stehen und schnell re­le­vante Ergebnisse zu liefern.

Die Cursor API von Apache Solr

In diesem Artikel möchten wir die Cursor API von Apache Solr vorstellen und ihre Anwendungsszenarien und Vorteile genauer betrachten.

Wozu ist die Cursor API überhaupt notwendig?

In vielen Applikationen, die eine Suche enthalten, werden die relevantesten Ergebnisse zuerst angezeigt. Typischerweise erfolgt dies in Form von einzelnen Seiten, die jeweils eine bestimmte Anzahl an Treffern enthalten. Für gewöhnlich verwenden die Benutzer nur die ersten paar Seiten, da die Sortierung nach Relevanz die gesuchten Treffer idealerweise nach vorn reiht.

In manchen Situation ist es allerdings erwünscht, auch weiter hinten liegende Seiten aus Solr zu extrahieren. Wenn die Ergebnisse exportiert oder in andere System überführt werden sollen, müssen sogar alle Treffer abgerufen werden.

Über die Parameter start und rows kann einfach bestimmt werden, welcher Ausschnitt aus dem Suchergebnis gewünscht ist. Man wird allerdings feststellen, dass große Seiten problematisch sind, da sie höhere Speicheranforderungen stellen. Schließlich muss Solr die gesamte Seite im Speicher abbilden und sortieren. Beim Einsatz von SolrCloud wird das Problem noch dramatischer, da dieser Schritt auf jedem einzelnen Knoten abläuft und die Zwischenergebnisse anschließend aggregiert werden. Die maximale Anzahl an Dokumenten, die sortiert werden müssen, steigt so auf ein Vielfaches der angefragten Seitengröße an.

Viele kleinere Seiten abzufragen bringt aber kaum Besserung, da die Antwortzeiten mit dem Wert von start steigen. Das liegt daran, dass ein Suchergebnis in Solr immer sortiert ist und das Dokument an Position 10.000 erst ermittelt werden kann, wenn alle Dokumente bis zur Position 9.999 ermittelt und sortiert worden sind.

Die Cursor API umgeht dieses Problem, indem nicht mehr mit direkten Positionsangaben gearbeitet wird. Stattdessen gibt es den Parameter cursorMark, der den Beginn innerhalb der Sortierung angibt. Für Solr ist es damit nicht mehr notwendig, alle vorhergehenden Seiten zu bestimmen. Die Antwortzeiten hängen so nicht mehr davon ab, welcher Ausschnitt aus dem Gesamtergebnisse angefragt wurde und “deep paging”, d.h. die Abfrage weit hinten liegender Seiten, kann ohne Geschwindigkeitseinbußen erfolgen.

Verwenden der Cursor API

Um die erste Seite eines Suchergebnis zu erhalten, muss der Parameter cursorMark auf den Wert “*” gesetzt werden. Die Seitengröße wird weiterhin über rows festgelegt. Ebenfalls muss eine geeignete Sortierung gewählt werden (siehe dazu Einschränkungen).

Zum Beispiel:

q=sprache:deutsch&sort=erstellung desc,id asc&rows=20&cursorMark=*

In der Antwort liefert Solr nun neben den eigentlichen Ergebnissen auch den Wert, der benötigt wird, um die nächste Seite abzurufen:

{
  "responseHeader":{
    "status":0,
    "QTime":1,
    "params":{
      "q":"sprache:deutsch",
      "cursorMark":"*",
      "sort":"erstellung desc, id asc",
      "rows":"20"
    }
  },
  "response":{
    "numFound":878,
    "start":0,
    "docs":[
      {
        "id":"1617291029",
        "sprache":"deutsch",
        "_version_":1534126758819790848
      },
      {
        "id":"978-1617291050",
        "sprache":"deutsch",
        "_version_":1534126786091155456
      }
    ]
  },
  "nextCursorMark":"AoI/CSogc2NoYWRlbiAtIGJvZGVuYmVsYWcgaG9seiAvIGt1bnN0c3RvZmY/IGRlZXBzZWFyY2glM0Fhc3Npc3QlMkYzZmMxODlhMS00M2IwLTRiMzQtYTk0Yy05N2ZmNDliMDI5MDJfbnVsbA=="
}

Für die folgende Seite lautet die Abfrage dann:

q=sprache:deutsch&sort=erstellung desc,id asc&rows=20&cursorMark=AoI/CSogc2NoYWRlbiAtIGJvZGVuYmVsYWcgaG9seiAvIGt1bnN0c3RvZmY/IGRlZXBzZWFyY2glM0Fhc3Npc3QlMkYzZmMxODlhMS00M2IwLTRiMzQtYTk0Yy05N2ZmNDliMDI5MDJfbnVsbA==

Ist das Ende des Ergebnisses erreicht, werden keine weiteren Dokumente in der Antwort mitgeliefert und “nextCursorMark” ist identisch zu “cursorMark”.

Wenn der Aufrufer entscheidet, dass keine weiteren Dokumente benötigt werden, kann er einfach aufhören, weitere Anfragen zu senden. Der Cursor selbst muss nicht freigegeben oder gar gelöscht werden. Das liegt daran, dass der Cursor keinen gespeicherten Zustand in Solr referenziert, sondern direkt die Daten für die Sortierung beschreibt. Damit sind Cursor problemlos auch in SolrCloud verwendbar.

Der Cursor ist völlig unabhängig von der verwendeten Abfrage und kann überall dort verwendet werden, wo auch herkömmliches Paging mit start möglich ist. Sie ist deshalb auch für Block-Joins, Geosuchen und sogar selbstentwickelte Komponenten einsetzbar.

Einschränkungen

Die Verwendung eines Cursors kann allerdings nicht mit dem Parameter “start” kombiniert werden – Solr antwortet dann mit HTTP/400. Dadurch ist es nicht möglich, mittels Cursor direkt zu einer bestimmten Stelle im Ergebnis zu springen. Nur das sequentielle Durchblättern wird unterstützt.

Die Sortierung muss immer auch das “UniqueKey”-Feld, das im Schema definiert wurde, enthalten. Dies ist erforderlich, um die Reihenfolge der Dokumente vollständig festzulegen. Hätten mehrere Dokumente identische Werte bei den Sortierfieldern und würden diese direkt an einem Seitenwechsel liegen, könnte der Cursor nicht eindeutig beschreiben, welches Dokument das tatsächliche Ende der Seite war – es könnte zu Lücken oder Duplikaten kommen. In der Regel ist diese Einschränkung allerdings nicht hinderlich, da die Sortierung nach dem “UniqueKey”-Feld einfach als zusätzliche Sortieroption angehängt werden kann.

Fazit

Die Cursor API erlaubt es, mit konstanten Antwortzeiten sequentiell durch ein Suchergebnis zu blättern und dabei jedes einzelne Dokument abzurufen. Die Anfragen müssen nur minimal verändert werden und erfordern keine zusätzliche Konfiguration an der Solr-Instanz. Durch ihre zustandsfreie Implementierung besteht keine Gefahr von Resource-Leaks und uneingeschränkte Nutzbarkeit in SolrCloud.