Elasticsearch ist eine Suchmaschine und Analysespeicher, die auf Apache Lucene aufbaut. Sie speichert Dokumente im JSON-Format und durchsucht sie in Millisekunden – auch bei Millionen Einträgen. Dieser Artikel beschreibt die Denkweise und den Betrieb.
Die Grundidee: Umgekehrter Index
Der entscheidende Unterschied zu einer Datenbank: Elasticsearch indiziert Begriffe, nicht Zeilen. Für jedes Wort wird festgehalten, in welchen Dokumenten es vorkommt. Eine Suche nach „Subnetting“ muss deshalb nicht alle Dokumente durchgehen, sondern schlägt im Index nach.
Der Preis dafür ist Schreibaufwand: Beim Speichern wird jedes Dokument analysiert, in Begriffe zerlegt und in den Index eingetragen. Elasticsearch ist deshalb kein Ersatz für eine Datenbank, sondern eine Ergänzung: Die Datenbank führt die Wahrheit, Elasticsearch macht sie durchsuchbar.
Grundbegriffe
- Index: Eine Sammlung von Dokumenten – vergleichbar mit einer Tabelle. Klein geschrieben, keine Leerzeichen.
- Dokument: Ein JSON-Objekt mit Kennung – vergleichbar mit einer Zeile.
- Feld: Ein Schlüssel im Dokument – vergleichbar mit einer Spalte.
- Mapping: Die Festlegung, wie Felder indiziert werden. Wird beim ersten Dokument abgeleitet, wenn nicht vorher festgelegt.
- Shard: Eine Teilmenge eines Index. Ein Index besteht aus mehreren Shards, die auf verschiedene Knoten verteilt werden.
- Replica: Eine Kopie eines Shards für Ausfallsicherheit und paralleles Lesen.
- Analyse: Die Zerlegung von Text in Begriffe (Kleinschreibung, Grundformen, Satzzeichen).
Ein Index mit sauberem Mapping
Die abgeleitete Zuordnung ist bequem, aber riskant: Ein Feld mit Text und Zahlen gemischt wird als Text behandelt, und eine spätere Änderung ist nicht ohne Neuaufbau möglich. Deshalb: Mapping vorab definieren.
PUT /protokolle
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"refresh_interval": "5s"
},
"mappings": {
"properties": {
"zeitpunkt": {"type": "date"},
"host": {"type": "keyword"}, // exakt, nicht analysiert
"dienst": {"type": "keyword"},
"status": {"type": "integer"},
"dauer_ms": {"type": "float"},
"nachricht": {"type": "text"}, // analysiert, durchsuchbar
"benutzer": {"type": "keyword"},
"tags": {"type": "keyword"},
"quelle": {
"type": "text",
"fields": {
"exakt": {"type": "keyword"} // zwei Sichten auf ein Feld
}
}
}
}
}
# keyword vs. text - der wichtigste Unterschied:
# text -> wird analysiert, Teiltreffer, fuer Suche und Relevanz
# keyword -> unveraendert, exakte Treffer, fuer Filter, Sortierung,
# Aggregationen. Nur keyword-Felder lassen sich sinnvoll
# aggregieren und sortieren.
Suchen und Filtern
# Einfache Suche
GET /protokolle/_search
{
"query": {"match": {"nachricht": "verbindungsfehler"}},
"size": 20
}
# Filtern (kein Relevanzbezug -> schneller und zwischenspeicherbar)
GET /protokolle/_search
{
"query": {
"bool": {
"filter": [
{"term": {"host": "web01"}},
{"range": {"zeitpunkt": {"gte": "now-1h"}}},
{"range": {"status": {"gte": 500}}}
],
"must": [
{"match": {"nachricht": "verbindungsfehler"}}
],
"must_not": [
{"term": {"dienst": "healthcheck"}}
]
}
},
"sort": [{"zeitpunkt": "desc"}],
"aggs": {
"je_dienst": {"terms": {"field": "dienst", "size": 10}},
"dauer": {"stats": {"field": "dauer_ms"}}
}
}
# Wichtig: FILTER statt MUST, wenn die Relevanzbewertung unnoetig ist.
# Filter werden zwischengespeichert - erheblich schneller.
Massenimport: der Bulk-Endpunkt
Einzeldokumente zu senden ist bei größeren Mengen aussichtslos. Der Grund liegt im Aufwand pro Anfrage:
# Ueber die API (eine Anfrage je Dokument) - LANGSAM
# Stattdessen: _bulk mit mehreren Dokumenten je Anfrage
POST /_bulk
{"index": {"_index": "protokolle"}}
{"zeitpunkt": "2026-09-26T12:00:00Z", "host": "web01", "status": 200}
{"index": {"_index": "protokolle"}}
{"zeitpunkt": "2026-09-26T12:00:01Z", "host": "web01", "status": 500}
# In Python mit dem offiziellen Client
from elasticsearch import Elasticsearch, helpers
es = Elasticsearch("https://es.example.org:9200",
basic_auth=("benutzer", "geheim"),
verify_certs=True)
def dokumente():
for zeile in offene_protokollzeilen():
yield {
"_index": "protokolle",
"_source": {
"zeitpunkt": zeile["zeit"],
"host": zeile["host"],
"status": int(zeile["status"]),
"nachricht": zeile["text"],
},
}
erfolg, fehler = helpers.bulk(es, dokumente(), chunk_size=1000)
print(f"importiert: {erfolg}, fehler: {len(fehler)}")
# Der Client buendelt automatisch zu grossen Anfragen.
# chunk_size je nach Dokumentgroesse anpassen: 500-2000 ist ueblich.
Betrieb
# Zustand
GET /_cluster/health?pretty
# status: green alles in Ordnung
# yellow Repliken nicht zugewiesen (bei einem Knoten normal)
# red Primaer-Shards fehlen - sofort handeln!
GET /_cat/indices?v&s=store.size:desc&s;
GET /_cat/shards?v
GET /_cat/nodes?v
GET /_cat/allocation?v
# Warum ist kein Speicher frei?
GET /_cat/allocation?v&h=node,disk.percent,disk.avail
# Standardmaessig weist Elasticsearch keine Shards mehr zu,
# wenn die Platte zu 85 % voll ist ("low watermark").
# Ab 90 % werden Shards sogar verschoben ("high watermark").
# Der letzte verfuegbare Knoten ist davon ausgenommen,
# aber nicht vor dem Ueberlaufen geschuetzt.
# Speicher je Index
GET /protokolle/_stats?pretty
# Alte Indizes loeschen (Lebenszyklus)
GET /_ilm/policy
GET /protokolle-*/_ilm/explain
Datenmengen in den Griff bekommen: Index Lifecycle
Protokolldaten wachsen unbegrenzt. Täglich ein neuer Index und eine Richtlinie für den Lebenszyklus sind der Weg:
# Indexvorlage: jeder Tagesindex erbt Einstellungen und Mapping
PUT /_index_template/protokolle
{
"index_patterns": ["protokolle-*"],
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"refresh_interval": "10s"
},
"mappings": { ... }
}
}
# Lebenszyklusrichtlinie
PUT /_ilm/policy/protokoll_policy
{
"policy": {
"phases": {
"hot": {"actions": {"rollover": {"max_size": "20gb", "max_age": "1d"}}},
"delete": {"min_age": "30d", "actions": {"delete": {}}}
}
}
}
Die Stufen sind im Wesentlichen:
- hot: Der aktuelle Index, wird beschrieben und gelesen.
- warm: Nicht mehr beschrieben, nur gelesen. Segmente werden zusammengeführt, Repliken reduziert – das spart Speicher.
- cold: Selten gelesen, auf günstigerem Speicher.
- delete: Löschen nach Ablauf der Aufbewahrung. Diese Stufe muss aktiv konfiguriert werden – ohne sie wächst der Bestand unbegrenzt.
Sicherheit und Betriebsregeln
- Niemals ungeschützt erreichbar. Ein offenes Elasticsearch im Internet wird binnen Stunden leergeräumt. Seit Version 8 ist Sicherheit standardmäßig aktiv – nicht abschalten.
- Zugriff auf das interne Netz beschränken und TLS verwenden.
- Kein
_searchohne Grenzen. Eine Abfrage über alle Indizes aller Zeiten kann das Cluster lahmlegen. Immer Indexmuster undsizeangeben. - Auf
fielddataachten. Aggregationen auftext-Feldern laden den gesamten Feldinhalt in den Speicher und führen schnell zum Speichermangel. Deshalbkeywordverwenden. - Speicher richtig einstellen: Etwa die Hälfte des Arbeitsspeichers für den Java-Haufen, nicht mehr als 32 GB (darüber verliert die JVM die komprimierten Zeiger). Der Rest bleibt für den Dateisystem-Zwischenspeicher.
- Sicherungen konfigurieren: Ein Abbild (Snapshot) in ein entferntes Repositorium – ein Clusterausfall ohne Sicherung bedeutet vollständige Neuindizierung.
- Shard-Anzahl überlegt wählen. Zu viele kleine Shards kosten Verwaltungsaufwand. Bei einem einstelligen Datenvolumen in Gigabyte ist ein Shard je Index meist richtig.
Wann Elasticsearch sinnvoll ist – und wann nicht
Geeignet: Volltextsuche über große Textmengen, Protokollauswertung, Analytik mit Aggregationen, Fehlersuche in verteilten Systemen (mit Kibana).
Weniger geeignet: Als alleinige Datenbank (keine echten Transaktionen, keine Beziehungen, verlorene Schreibvorgänge bei Ausfall möglich), für kleine Datenmengen (eine PostgreSQL-Volltextsuche genügt), und für Aufgaben, in denen die Daten dauerhaft und exakt verfügbar sein müssen. Für reine Zeitreihen sind spezialisierte Systeme wie Prometheus oder TimescaleDB besser geeignet.
Verwandte Grundlagen: Elasticsearch-Befehle, Logging, PostgreSQL im Betrieb, Observability.