Beherrschung des Elasticsearch Query DSL: Wesentliche Befehle für die Datenabfrage

Entfesseln Sie die Leistungsfähigkeit der Elasticsearch-Abfrage, indem Sie das Query DSL meistern. Dieser Leitfaden erläutert wesentliche JSON-Abfragestrukturen und konzentriert sich auf die praktische Verwendung von `match`-, `term`- und Bereichsabfragen. Lernen Sie den entscheidenden Unterschied zwischen `must` (Bewertung) und `filter` (Caching)-Klauseln innerhalb der grundlegenden `bool`-Abfrage kennen, um komplexe, leistungsstarke Datensuchen effizient zu erstellen.

Beherrschung des Elasticsearch Query DSL: Wesentliche Befehle für die Datenabfrage

Elasticsearch Query DSL ist die JSON-Sprache, die Sie verwenden, wenn ein einfaches Suchfeld nicht ausreicht. Es ermöglicht Ihnen, Volltextsuche, exakte Filter, Datumsbereiche, Sortierung, Paginierung und Aggregationen in einer einzigen Anfrage zu kombinieren. Diese Flexibilität ist nützlich, aber sie macht es auch leicht, eine Abfrage zu schreiben, die die falschen Dokumente zurückgibt oder im Test gut funktioniert und in der Produktion langsamer wird.

Der beste Weg, Query DSL zu lernen, ist, sich zwei Fragen zu merken: "Suche ich nach Text mit Relevanz?" und "Filtere ich nach exakten Werten?" Die meisten Abfrageentscheidungen folgen aus dieser Trennung.

Die Anatomie einer Elasticsearch-Suchanfrage

Alle Elasticsearch-Suchen werden gegen den _search-Endpunkt eines bestimmten Index (oder mehrerer Indizes) durchgeführt. Eine grundlegende Suchanfrage ist eine POST-Anfrage, die einen JSON-Body enthält, der die Abfrageparameter definiert. Der wichtigste Teil dieses Bodys ist das query-Objekt.

Grundlegende Struktur:

POST /your_index_name/_search
{
  "query": { ... Definieren Sie hier Ihre Abfragestruktur ... },
  "size": 10, 
  "from": 0
}

Kernabfragetypen: Präzision und Relevanz

Das Query DSL bietet eine breite Palette von Abfragen, die auf verschiedene Datentypen und Matching-Anforderungen zugeschnitten sind. Die Wahl der Abfrage hat erhebliche Auswirkungen auf die Relevanzbewertung und die Leistung.

1. Volltextsuche: Die match-Abfrage

Die match-Abfrage ist der Standard für die Volltextsuche über analysierte Felder. Sie tokenisiert den Suchbegriff und prüft auf übereinstimmende Token in den angegebenen Feldern.

Anwendungsfall: Suche nach natürlichsprachlichem Text, bei dem die Relevanzbewertung wichtig ist.

Beispiel: Finden von Dokumenten, in denen das Feld 'description' die Wörter 'cloud' oder 'computing' enthält.

GET /products/_search
{
  "query": {
    "match": {
      "description": "cloud computing"
    }
  }
}

2. Exakte Wertübereinstimmung: Die term-Abfrage

Die term-Abfrage sucht nach Dokumenten, die den exakten angegebenen Begriff enthalten. Im Gegensatz zu match führt sie keine Analyse des Suchstrings durch, was sie ideal für exakte Übereinstimmungen bei Schlüsselwörtern, IDs oder numerisch indizierten Feldern macht.

Anwendungsfall: Filtern nach exakten Werten in nicht analysierten Feldern (wie keyword-Feldern oder Zahlen).

Beispiel: Abrufen eines Produkts mit der genauen ID SKU10021.

GET /products/_search
{
  "query": {
    "term": {
      "product_id": "SKU10021"
    }
  }
}

3. Bereichsabfragen

Bereichsabfragen ermöglichen es Ihnen, Dokumente zu filtern, bei denen der Wert eines Feldes innerhalb eines bestimmten Bereichs liegt (numerisch, Datum oder Zeichenfolge).

Syntax: Verwendet gt (größer als), gte (größer oder gleich), lt (kleiner als) und lte (kleiner oder gleich).

Beispiel: Finden von Bestellungen, die nach dem 1. Januar 2024 aufgegeben wurden.

GET /orders/_search
{
  "query": {
    "range": {
      "order_date": {
        "gte": "2024-01-01",
        "lt": "2025-01-01"
      }
    }
  }
}

4. Filtern nach Vorhandensein: Die exists-Abfrage

Die exists-Abfrage identifiziert Dokumente, in denen ein bestimmtes Feld vorhanden ist (d. h. nicht null oder fehlend).

Beispiel: Finden aller Benutzer, die eine E-Mail-Adresse angegeben haben.

GET /users/_search
{
  "query": {
    "exists": {
      "field": "email_address"
    }
  }
}

Erstellen komplexer Logik mit der bool-Abfrage

Für praktisch alle realen Suchanwendungen müssen Sie mehrere Kriterien kombinieren. Die bool-Abfrage ist das wesentliche Werkzeug dafür, da sie es Ihnen ermöglicht, andere Abfrageklauseln mit boolescher Logik zu kombinieren.

Klauseln innerhalb von bool

Die bool-Abfrage akzeptiert vier primäre Klauseln:

  1. must: Alle Klauseln in diesem Array müssen übereinstimmen. Klauseln in must tragen zum Relevanz-Score bei.
  2. filter: Alle Klauseln in diesem Array müssen übereinstimmen, werden aber in einem nicht bewertenden Kontext ausgeführt. Dies macht sie viel schneller für strenge Einschluss-/Ausschlusskriterien.
  3. should: Mindestens eine Klausel in diesem Array sollte übereinstimmen. Diese Klauseln beeinflussen den Relevanz-Score, sind aber für die Übereinstimmung optional.
  4. must_not: Keine der Klauseln in diesem Array darf übereinstimmen (das Äquivalent zu einem logischen NICHT).

Praktisches bool-Abfragebeispiel

Kombinieren wir mehrere Konzepte, um hochpriorisierte Dokumente zu finden, die 'security' erwähnen, aber Entwürfe ausschließen und in der Region 'US' verfügbar sind.

GET /logs/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "content": "security breach"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "region.keyword": "US"
          }
        }
      ],
      "should": [
        {
          "term": {
            "priority": 5
          }
        }
      ],
      "must_not": [
        {
          "term": {
            "status.keyword": "DRAFT"
          }
        }
      ]
    }
  }
}

Erklärung des Beispiels:

  • Must: Das Dokument muss den Ausdruck "security breach" im analysierten Inhaltsfeld enthalten.
  • Filter: Das Dokument muss für die Region 'US' gekennzeichnet sein (eine schnelle, exakte Übereinstimmung).
  • Should: Dokumente, die priority: 5 entsprechen, erhalten eine Erhöhung ihres Relevanz-Scores, aber Dokumente mit niedrigeren Prioritäten, die die must- und filter-Klauseln erfüllen, werden trotzdem zurückgegeben.
  • Must Not: Als 'DRAFT' gekennzeichnete Dokumente werden strikt ausgeschlossen.

Best Practices für die Abfrageerstellung

Um sicherzustellen, dass Ihre Suchen sowohl genau als auch leistungsstark sind, befolgen Sie diese Richtlinien:

  • Bevorzugen Sie filter gegenüber must für nicht bewertende Kriterien. Wenn Sie nur auf Einschluss/Ausschluss prüfen (z. B. Filtern nach ID, exaktem Datum oder Status), verwenden Sie immer die filter-Klausel innerhalb einer bool-Abfrage. Dies nutzt Caching und vermeidet teure Bewertungsberechnungen.
  • Verwenden Sie exakte Abfragen mit Bedacht: Verwenden Sie für Felder, die als text (analysiert) zugeordnet sind, match. Verwenden Sie für Felder, die als keyword (nicht analysiert) zugeordnet sind, term- oder Bereichsabfragen.
  • Vermeiden Sie tiefe Verschachtelung: Obwohl möglich, können tief verschachtelte bool-Abfragen schwer zu lesen und zu debuggen sein und manchmal zu Leistungseinbußen führen.
  • Nutzen Sie minimum_should_match: Für should-Klauseln erzwingt das Setzen von minimum_should_match (z. B. auf 1 oder 2), dass eine bestimmte Anzahl dieser optionalen Kriterien erfüllt sein muss, was sie effektiv in erforderliche Kriterien verwandelt, während sie dennoch zur Bewertung beitragen können.

Das Mapping entscheidet, welche Abfrage sinnvoll ist

Die meisten Query-DSL-Fehler beginnen mit dem Mapping. Eine Abfrage kann korrekt aussehen und dennoch verwirrende Ergebnisse liefern, wenn das Feld anders zugeordnet ist, als Sie denken.

Ein häufiges Muster ist ein Textfeld mit einem Keyword-Unterfeld:

{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      },
      "status": { "type": "keyword" },
      "created_at": { "type": "date" },
      "price": { "type": "double" }
    }
  }
}

Verwenden Sie match für title, wenn Sie analysiertes Volltextverhalten wünschen. Verwenden Sie term für title.keyword, wenn Sie den genauen Titelwert benötigen. Verwenden Sie term für status, da es bereits ein Keyword ist. Verwenden Sie range für created_at oder price, da diese Felder Datums- und Zahlenwerte sind.

Wenn eine term-Abfrage für ein Textfeld nicht wie erwartet funktioniert, liegt das Problem oft an der Analyse. Die gespeicherten Token können kleingeschrieben, geteilt, gestemmt oder anderweitig geändert worden sein. Überprüfen Sie das Mapping, bevor Sie die Abfrage ändern.

GET /products/_mapping

Bei Textanalyseproblemen ist _analyze nützlich:

GET /products/_analyze
{
  "field": "description",
  "text": "Cloud Computing"
}

Das zeigt, welche Token Elasticsearch durchsuchen wird.

match, match_phrase und multi_match

match ist die alltägliche Volltextabfrage, aber nicht die einzige, die Sie verwenden werden.

Verwenden Sie match_phrase, wenn die Wortreihenfolge wichtig ist:

GET /products/_search
{
  "query": {
    "match_phrase": {
      "description": "wireless charging stand"
    }
  }
}

Dies ist nützlich für Produktnamen, Protokollmeldungen, Dokumenttitel und Phrasen, bei denen die genaue Reihenfolge eine Bedeutung hat. Es ist strenger als match, kann also weniger Dokumente zurückgeben.

Verwenden Sie multi_match, wenn dieselbe Benutzereingabe mehrere Felder durchsuchen soll:

GET /products/_search
{
  "query": {
    "multi_match": {
      "query": "noise cancelling headphones",
      "fields": ["title^3", "description", "brand^2"]
    }
  }
}

Die Boosts ^3 und ^2 teilen Elasticsearch mit, dass Übereinstimmungen in title und brand mehr zählen sollen als Übereinstimmungen in description. Boosting ist keine Garantie dafür, dass ein Dokument an erster Stelle steht; es ist ein Bewertungshinweis. Testen Sie mit echten Abfragen, bevor Sie Boosts zu aggressiv einstellen.

Paginierung ohne den Cluster zu belasten

Die grundlegenden Parameter from und size sind für flache Paginierung in Ordnung:

GET /products/_search
{
  "from": 20,
  "size": 10,
  "query": {
    "match": {
      "description": "laptop sleeve"
    }
  }
}

Tiefe Paginierung ist anders. Die Anforderung von Seite 1.000 zwingt Elasticsearch, viele Ergebnisse zu sortieren und zu überspringen. Vermeiden Sie für benutzerseitige Suchen unbegrenztes tiefes Blättern. Verwenden Sie für Exporte oder Hintergrundscans search_after mit einer stabilen Sortierung:

GET /products/_search
{
  "size": 100,
  "sort": [
    { "created_at": "asc" },
    { "_id": "asc" }
  ],
  "search_after": ["2025-01-10T12:00:00Z", "abc123"],
  "query": {
    "term": {
      "status": "active"
    }
  }
}

Die Werte in search_after stammen aus dem sort-Array des letzten Treffers in der vorherigen Antwort. Dieser Ansatz ist stabiler für das Durchlaufen großer Ergebnismengen.

Quellfilterung hält Antworten nützlich

Die Suchleistung hängt nicht nur von der Abfrageausführung ab. Die Rückgabe großer Dokumente kann den Client, das Netzwerk und den koordinierenden Knoten verlangsamen. Wenn die Benutzeroberfläche nur wenige Felder benötigt, fragen Sie nach diesen Feldern:

GET /orders/_search
{
  "_source": ["order_id", "customer_id", "total", "created_at", "status"],
  "query": {
    "bool": {
      "filter": [
        { "term": { "status": "paid" } },
        { "range": { "created_at": { "gte": "now-7d/d" } } }
      ]
    }
  }
}

Dies macht die Antwort leichter lesbar und kann die Nutzlastgröße reduzieren. Es ersetzt kein gutes Indexdesign, hilft aber, wenn Dokumente große Beschreibungen, Metadaten-Blobs oder verschachtelte Arrays enthalten, die die aktuelle Seite nicht benötigt.

Sortierung und Aggregationen benötigen die richtigen Felder

Das Sortieren nach analysiertem Text ist normalerweise ein Fehler. Sortieren Sie nach Keyword-, numerischen oder Datumsfeldern:

GET /products/_search
{
  "sort": [
    { "price": "asc" },
    { "title.keyword": "asc" }
  ],
  "query": {
    "term": {
      "status": "active"
    }
  }
}

Das Gleiche gilt für viele Aggregationen. Wenn Sie Zählungen nach Status wünschen, aggregieren Sie auf einem Keyword-Feld:

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "orders_by_status": {
      "terms": {
        "field": "status"
      }
    }
  },
  "query": {
    "range": {
      "created_at": {
        "gte": "now-30d/d"
      }
    }
  }
}

size: 0 teilt Elasticsearch mit, dass Sie nur Aggregationsergebnisse wünschen, keine übereinstimmenden Dokumente. Das ist eine kleine Gewohnheit, die Antworten sauberer hält.

Debuggen von Abfragen mit explain und profile

Wenn ein Ergebnis seltsam eingestuft wird, verwenden Sie explain für ein einzelnes Dokument:

GET /products/_explain/SKU10021
{
  "query": {
    "match": {
      "description": "cloud computing"
    }
  }
}

Wenn eine Abfrage langsam ist, verwenden Sie profile in einem Nicht-Produktions- oder sorgfältig kontrollierten Produktionstest:

GET /products/_search
{
  "profile": true,
  "query": {
    "bool": {
      "must": [
        { "match": { "description": "cloud computing" } }
      ],
      "filter": [
        { "term": { "status": "active" } }
      ]
    }
  }
}

Die Profilausgabe ist ausführlich, kann aber zeigen, ob Zeit in einer Textabfrage, einem Filter, einem Skript oder einem anderen Teil der Anfrage verbracht wird. Lassen Sie die Profilerstellung nicht im Anwendungscode aktiviert; verwenden Sie sie als Debugging-Werkzeug.

Eine sinnvolle Gewohnheit beim Erstellen von Abfragen

Erstellen Sie für die meisten Anwendungssuchen die Anfrage in dieser Reihenfolge:

  1. Setzen Sie exakte Einschränkungen in filter: Mandanten-ID, Status, Region, Zeitfenster, Berechtigungen.
  2. Setzen Sie vom Benutzer eingegebenen Text in must mit match, match_phrase oder multi_match.
  3. Verwenden Sie should für Ranking-Präferenzen, nicht für harte Anforderungen, es sei denn, Sie setzen minimum_should_match.
  4. Beschränken Sie _source auf Felder, die der Aufrufer benötigt.
  5. Fügen Sie eine stabile Sortierung hinzu, wenn Paginierung oder Exporte wichtig sind.
  6. Überprüfen Sie das Mapping, bevor Sie Elasticsearch die Schuld geben.

Das Query DSL ist leistungsstark, weil es Filtern, Bewerten, Sortieren und Antwortgestaltung trennt. Sobald Sie diese Aufgaben getrennt halten, werden Abfragen leichter lesbar, leichter abstimmbar und in der Produktion weniger überraschend.

Ein kleines Fehlerbehebungsbeispiel

Angenommen, ein Benutzer sucht nach ACME-1000 und erhält kein Ergebnis, obwohl das Produkt existiert. Fügen Sie nicht sofort Platzhalter hinzu. Überprüfen Sie zuerst das Mapping. Wenn sku ein keyword ist, sollte dies funktionieren:

GET /products/_search
{
  "query": {
    "term": {
      "sku": "ACME-1000"
    }
  }
}

Wenn sku versehentlich als text zugeordnet wurde, hat die Analyse den Wert möglicherweise geteilt oder geändert. Sie können es in einigen Fällen trotzdem abfragen, aber die bessere Lösung ist normalerweise eine Mapping-Änderung für zukünftige Indizes. Exakte Identifikatoren, Status, Regionen und Mandanten-IDs sollten keyword-ähnliche Felder sein. Von Menschen geschriebene Beschreibungen und Titel sollten Textfelder sein. Das Query DSL wird viel einfacher, wenn das Mapping der Art und Weise entspricht, wie die Leute die Daten tatsächlich abrufen.