neue Feldideen testen, bevor das Mapping fest steht
Runtime Fields berechnen ein Feld erst zur Abfragezeit mit einem Painless-Skript, statt es beim Indexieren fest im Mapping zu speichern. Damit lassen sich neue Auswertungsideen sofort ausprobieren, ohne Milliarden Dokumente neu zu indexieren, bevor feststeht, ob sich die Idee überhaupt lohnt.
Inhaltsverzeichnis
- 1. Warum Runtime Fields den Reindex-Zyklus verkürzen
- 2. Grundsyntax: runtime_mappings in der Query
- 3. Painless-Skripte für berechnete Felder
- 4. Runtime Fields im Index-Mapping definieren
- 5. Runtime Fields in Aggregationen einsetzen
- 6. Explorationsphase: neue Kennzahlen testen
- 7. Der Performance-Tradeoff im Detail
- 8. Von Runtime Field zu indexiertem Feld migrieren
- 9. Typische Fehler und Debugging
- 10. Zusammenfassung
- 11. FAQ
1. Warum Runtime Fields den Reindex-Zyklus verkürzen
In einem klassischen Elasticsearch-Workflow entsteht ein neues Feld über eine Mapping-Änderung und einen anschließenden Reindex, der alle Dokumente neu verarbeitet. Bei Indizes mit Milliarden Dokumenten dauert dieser Zyklus Stunden bis Tage und bindet Cluster-Ressourcen, nur um herauszufinden, ob die neue Auswertung überhaupt fachlich nützlich ist. Runtime Fields lösen genau dieses Problem, indem sie ein Feld nicht beim Indexieren, sondern erst zum Zeitpunkt der Abfrage aus einem Painless-Skript berechnen.
Der zentrale Vorteil ist die Entkopplung von Datenmodellierung und Iterationsgeschwindigkeit. Ein Analyst kann mit einem Runtime Field sofort testen, ob eine abgeleitete Kennzahl wie "Bestellwert pro Artikel" oder "Kategorisierung nach Preisniveau" in der Praxis brauchbare Auswertungen liefert, ohne vorher einen Reindex-Job zu planen. Erst wenn sich ein Runtime Field als dauerhaft nützlich erweist, lohnt sich der Aufwand, es in ein echtes, indexiertes Feld zu überführen.
Die folgenden Abschnitte zeigen Runtime Fields von der Grundsyntax über Painless-Skripte bis zur Migration in ein indexiertes Feld. Jedes Beispiel nutzt reale Query- und Mapping-Syntax, wie sie gegen einen produktiven Elasticsearch- oder OpenSearch-Cluster läuft.
2. Grundsyntax: runtime_mappings in der Query
Der einfachste Weg, ein Runtime Field zu nutzen, ist der Parameter runtime_mappings direkt in der Such-Query. Dabei wird kein bestehendes Mapping verändert, das Feld existiert nur für die Dauer dieser einen Anfrage. Jedes runtime_mappings-Feld braucht mindestens einen Typ, etwa keyword, long oder double, sowie ein script, das den Wert für jedes Dokument berechnet und über emit() an das Ergebnis übergibt.
Dieser Ansatz eignet sich besonders für explorative Auswertungen in Kibana oder direkt über die REST-API, weil keine Berechtigung für Mapping-Änderungen am Index nötig ist und nichts dauerhaft am Cluster verändert wird. Ein Runtime Field, das nur in runtime_mappings der Query definiert ist, verschwindet automatisch wieder, sobald die Anfrage beendet ist, und hinterlässt keine Spuren im persistenten Mapping.
GET /orders/_search
{
"runtime_mappings": {
"order_value_bucket": {
"type": "keyword",
"script": {
"source": """
double total = doc['grand_total'].value;
if (total < 50) emit('low');
else if (total < 200) emit('medium');
else emit('high');
"""
}
}
},
"query": { "match_all": {} },
"aggs": {
"orders_by_value_bucket": {
"terms": { "field": "order_value_bucket" }
}
}
}
3. Painless-Skripte für berechnete Felder
Das Painless-Skript im Kern eines Runtime Field hat Zugriff auf das doc-Objekt, das die indexierten Feldwerte des aktuellen Dokuments über doc_values bereitstellt, sowie über params.\_source auf das komplette Original-JSON-Dokument. Der Zugriff über doc ist deutlich schneller als über \_source, weil doc_values bereits spaltenorientiert und für schnellen Zugriff optimiert im Index liegen, während \_source aus dem gespeicherten Rohdokument bei jedem Aufruf neu geparst werden muss.
Innerhalb des Skripts stehen die üblichen Painless-Sprachkonstrukte zur Verfügung: Bedingungen, Schleifen, arithmetische Operationen und ein eingeschränktes Set an Standardbibliotheks-Methoden für Strings, Datumswerte und Mathematik. Für ein Runtime Field, das mehrere Quellfelder kombiniert, etwa eine Netto-Marge aus Verkaufspreis und Einkaufspreis, reicht meist ein wenige Zeilen langes Skript, das beide Werte liest, die Differenz berechnet und das Ergebnis über emit() zurückgibt.
GET /products/_search
{
"runtime_mappings": {
"margin_percent": {
"type": "double",
"script": {
"source": """
if (doc['cost_price'].size() == 0 || doc['sale_price'].size() == 0) {
return;
}
double cost = doc['cost_price'].value;
double sale = doc['sale_price'].value;
if (cost == 0) return;
emit(((sale - cost) / cost) * 100);
"""
}
}
},
"query": {
"range": { "margin_percent": { "lt": 10 } }
}
}
4. Runtime Fields im Index-Mapping definieren
Statt ein Runtime Field in jeder einzelnen Query neu zu definieren, lässt es sich auch dauerhaft im Index-Mapping unter dem Schlüssel runtime hinterlegen. Das Feld verhält sich dann für alle nachfolgenden Anfragen wie ein regulärer Feldname, ohne dass runtime_mappings in jeder einzelnen Query wiederholt werden muss. Der entscheidende Unterschied zu einem normalen indexierten Feld bleibt aber bestehen: Der Wert wird weiterhin bei jeder Abfrage neu berechnet, es findet keine Vorberechnung beim Indexieren statt.
Dieser Modus eignet sich für Runtime Fields, die sich als nützlich erwiesen haben und häufiger genutzt werden sollen, aber noch nicht den Aufwand eines vollständigen Reindex rechtfertigen. Ein im Mapping definiertes Runtime Field lässt sich zudem jederzeit ohne Reindex wieder entfernen oder anpassen, weil die Änderung nur das Mapping betrifft und keine gespeicherten Werte im invertierten Index oder in doc_values erzeugt.
PUT /products/_mapping
{
"runtime": {
"margin_percent": {
"type": "double",
"script": {
"source": """
if (doc['cost_price'].size() == 0 || doc['sale_price'].size() == 0) {
return;
}
double cost = doc['cost_price'].value;
double sale = doc['sale_price'].value;
if (cost == 0) return;
emit(((sale - cost) / cost) * 100);
"""
}
}
}
}
5. Runtime Fields in Aggregationen einsetzen
Ein zentraler Vorteil von Runtime Fields ist, dass sie sich in Aggregationen genau wie indexierte Felder verwenden lassen. Eine Terms-Aggregation auf ein Runtime Field wie order_value_bucket gruppiert Dokumente nach dem berechneten Bucket-Wert, ohne dass dieser Wert jemals persistent gespeichert wurde. Auch Metrik-Aggregationen wie avg oder sum funktionieren auf numerischen Runtime Fields wie margin_percent, was komplexe Ad-hoc-Auswertungen ermöglicht, die vorher eine Anpassung des Index-Schemas erfordert hätten.
Wichtig ist dabei die Erwartungshaltung an die Performance: Da Elasticsearch das Skript für jedes einzelne Dokument im Aggregations-Kandidatensatz ausführen muss, skaliert der Rechenaufwand einer Aggregation auf einem Runtime Field linear mit der Anzahl der betroffenen Dokumente, während eine Aggregation auf einem indexierten keyword-Feld von Global Ordinals und vorab aufgebauten Strukturen profitiert. Für eine schnelle Machbarkeitsprüfung ist das meist kein Problem, für hochfrequentierte Produktions-Dashboards mit Millionen Dokumenten pro Anfrage kann der Unterschied spürbar werden.
6. Explorationsphase: neue Kennzahlen testen
Der typische Anwendungsfall für Runtime Fields in der Praxis ist eine Explorationsphase, in der ein Team eine neue fachliche Kennzahl definieren möchte, deren genaue Berechnungslogik aber noch nicht final feststeht. Statt sofort ein Mapping zu entwerfen und einen Reindex zu planen, wird die Berechnungslogik zunächst als Runtime Field formuliert und gegen echte Produktionsdaten getestet. Stakeholder können die Ergebnisse in Kibana ansehen, Grenzfälle identifizieren und das Skript iterativ anpassen, bevor überhaupt eine Zeile Infrastruktur-Code geschrieben wird.
Dieser iterative Ansatz reduziert das Risiko fehlerhafter Reindex-Zyklen erheblich. Ein Fehler in der Berechnungslogik eines Runtime Field lässt sich sofort im Skript korrigieren und beim nächsten Query-Aufruf testen, ohne dass bereits Milliarden Dokumente mit einer fehlerhaften Berechnung neu indexiert wurden. Erst wenn die Logik stabil ist und die Kennzahl regelmäßig in Produktions-Dashboards gebraucht wird, folgt der Schritt zur Migration in ein echtes indexiertes Feld.
| Kriterium | Runtime Field | Indexiertes Feld | Empfehlung |
|---|---|---|---|
| Setup-Aufwand | Sofort verfügbar, kein Reindex | Mapping-Änderung plus Reindex nötig | Exploration: Runtime Field |
| Abfrage-Performance | Skript läuft pro Dokument zur Query-Zeit | Vorberechnet, direkt aus doc_values lesbar | Häufige Nutzung: indexiertes Feld |
| Speicherbedarf | Kein zusätzlicher Speicher im Index | Zusätzlicher Speicher für doc_values | Selten genutzte Felder: Runtime Field |
| Änderbarkeit | Skript jederzeit ohne Reindex anpassbar | Änderung erfordert erneuten Reindex | Instabile Logik: Runtime Field |
| Aggregations-Skalierung | Linear mit Dokumentanzahl | Profitiert von Global Ordinals | Große Produktions-Dashboards: indexiertes Feld |
7. Der Performance-Tradeoff im Detail
Der Performance-Unterschied zwischen einem Runtime Field und einem indexierten Feld ist keine pauschale Aussage, sondern hängt stark vom Anwendungsfall ab. Für eine Query, die ohnehin nur wenige hundert Dokumente über einen restriktiven Filter zurückgibt, ist der Overhead der Skript-Ausführung pro Dokument vernachlässigbar, weil die absolute Anzahl der Skript-Aufrufe klein bleibt. Für eine Aggregation, die potenziell Millionen Dokumente durchläuft, etwa eine Terms-Aggregation ohne einschränkenden Filter, kann derselbe Overhead spürbar werden und die Antwortzeit um ein Vielfaches erhöhen.
Ein wichtiger Optimierungshebel ist, das Painless-Skript eines Runtime Field so einfach wie möglich zu halten und komplexe Berechnungen zu vermeiden, die Schleifen über verschachtelte Strukturen oder aufwendige String-Operationen enthalten. Elasticsearch kompiliert Painless-Skripte zwar in Bytecode und cached diese Kompilate, aber die eigentliche Ausführung pro Dokument bleibt dennoch teurer als ein reiner doc_values-Zugriff auf ein vorberechnetes Feld. Wer ein Runtime Field regelmäßig in Aggregationen über den gesamten Index nutzt, sollte die Migration zu einem indexierten Feld ernsthaft in Betracht ziehen.
8. Von Runtime Field zu indexiertem Feld migrieren
Sobald sich ein Runtime Field als dauerhaft nützlich erwiesen hat, ist der nächste Schritt, dieselbe Berechnungslogik in einen Ingest-Pipeline-Prozessor oder direkt in die Indexierungslogik der Anwendung zu übertragen, sodass der Wert beim Schreiben des Dokuments einmalig berechnet und als echtes indexiertes Feld gespeichert wird. Diese Migration profitiert davon, dass das Painless-Skript aus der Runtime-Field-Definition praktisch unverändert in einen script-Prozessor der Ingest-Pipeline übernommen werden kann, weil beide dieselbe Painless-Umgebung nutzen.
Nach der Migration läuft ein vollständiger Reindex, der jedes bestehende Dokument mit dem neuen, jetzt indexierten Feld anreichert. Ab diesem Zeitpunkt profitiert das Feld von Global Ordinals, Terms-Aggregationen mit eager_global_ordinals und allen übrigen Optimierungen regulärer Felder. Das ursprüngliche Runtime Field-Mapping kann anschließend entfernt werden, sofern kein Anwendungsfall mehr verbleibt, der die dynamische, ungespeicherte Berechnung benötigt.
PUT /_ingest/pipeline/compute_margin_percent
{
"processors": [
{
"script": {
"source": """
if (ctx.cost_price == null || ctx.sale_price == null) {
return;
}
double cost = ctx.cost_price;
double sale = ctx.sale_price;
if (cost == 0) return;
ctx.margin_percent = ((sale - cost) / cost) * 100;
"""
}
}
]
}
POST /_reindex
{
"source": { "index": "products" },
"dest": { "index": "products_v2", "pipeline": "compute_margin_percent" }
}
9. Typische Fehler und Debugging
Der häufigste Fehler bei Runtime Fields ist, ein fehlendes Quellfeld im Painless-Skript nicht abzufangen. Enthält ein Dokument das referenzierte Feld nicht, wirft doc['feldname'].value ohne vorherige size()-Prüfung eine Exception, die die gesamte Query fehlschlagen lässt, statt das Dokument einfach zu überspringen. Der korrekte Weg ist immer eine explizite size()-Prüfung vor dem Zugriff auf value, kombiniert mit einem frühzeitigen return, wenn das Feld fehlt.
// WRONG: no null-check, throws on documents missing the field
{
"script": {
"source": "emit(doc['discount_percent'].value)"
}
}
// RIGHT: explicit size check before accessing value
{
"script": {
"source": """
if (doc['discount_percent'].size() == 0) {
return;
}
emit(doc['discount_percent'].value);
"""
}
}
Ein zweiter häufiger Fehler ist die Verwechslung von doc-Zugriff und \_source-Zugriff. doc liefert für multivalue-Felder immer den ersten Wert oder erfordert explizite Iteration über alle Werte, während \_source das komplette Original-Array liefert. Wer ein Runtime Field über ein Array-Attribut wie Tags definiert und dabei doc statt \_source nutzt, verliert stillschweigend alle Werte außer dem ersten, was in Aggregationen zu unvollständigen und irreführenden Ergebnissen führt.
Mironsoft
Elasticsearch- und OpenSearch-Beratung für Suche, Analytics und Dashboards
Neue Kennzahlen testen, ohne den Index neu aufzubauen?
Wir setzen Runtime Fields für schnelle Machbarkeitsprüfungen ein, begleiten die Explorationsphase mit Painless-Skripten und übernehmen die Migration zu indexierten Feldern, sobald sich eine Kennzahl bewährt hat.
Kennzahlen-Prototyping
Runtime Fields und Painless-Skripte für neue Auswertungsideen
Migration zu Mapping
Ingest-Pipeline-Prozessoren und Reindex-Strategie für bewährte Felder
Performance-Analyse
Vergleich von Runtime Field und indexiertem Feld für konkrete Query-Muster
10. Zusammenfassung
Runtime Fields verschieben die Feldberechnung von der Indexierungszeit auf die Abfragezeit und lösen damit ein zentrales Problem klassischer Elasticsearch-Workflows: die lange Latenz zwischen einer neuen Auswertungsidee und ihrer ersten Testmöglichkeit. Über runtime_mappings in einer einzelnen Query oder dauerhaft im Index-Mapping lassen sich Painless-Skripte definieren, die wie normale Felder in Queries, Filtern und Aggregationen genutzt werden können, ohne einen Reindex auszulösen.
Der Preis für diese Flexibilität ist der Performance-Tradeoff: Ein Runtime Field führt sein Skript bei jeder Abfrage erneut aus, während ein indexiertes Feld von vorberechneten doc_values und Global Ordinals profitiert. Für Exploration und selten genutzte Auswertungen ist dieser Tradeoff meist unproblematisch, für hochfrequentierte Produktions-Dashboards lohnt sich nach der Bewährungsphase die Migration in ein echtes indexiertes Feld mit anschließendem Reindex.
Runtime Fields für flexible Auswertungen, das Wichtigste auf einen Blick
Sofort verfügbar
runtime_mappings in der Query definiert ein Feld ohne Mapping-Änderung und ohne Reindex.
Painless-Zugriff
doc für schnellen Zugriff auf indexierte Werte, immer mit size()-Prüfung vor value.
Performance-Tradeoff
Skript läuft pro Dokument zur Query-Zeit, skaliert linear statt von Global Ordinals zu profitieren.
Migrationspfad
Bewährtes Skript in Ingest-Pipeline übernehmen, Reindex ausführen, Feld wird zu regulärem indexierten Feld.