Forschung & Entwicklung (R&D) - Insights & News

So misst du, ob dein erstes R&D-Projekt funktioniert

Ein Suchprototyp liefert bessere Treffer in der Demo. Ob er gegenüber der bestehenden Datenbank wirklich gewinnt, ist damit noch offen: Unter Last können Abfragen langsamer werden, neue Dokumente verspätet erscheinen oder nur die vorgeführten Suchanfragen profitieren. Meine Position: Ein R&D-Versuch funktioniert erst, wenn er eine vorher festgelegte Entscheidung anhand von Nutzensignalen und Gegenkosten ermöglicht.

Eine erfolgreiche Demo beantwortet noch keine Entscheidungsfrage

Angenommen, deine Anwendung sucht bislang mit PostgreSQL 16 über eine tsvector-Spalte. Ein Team möchte OpenSearch 2.x testen, weil Nutzende nach Produktnamen, Synonymen und Tippfehlern suchen. „Die Treffer sehen besser aus“ ist dafür keine ausreichende Hypothese, weil sich aus ausgewählten Beispielen weder die Häufigkeit der Verbesserung noch ihr Preis ableiten lässt.

Formuliere stattdessen vor dem Aufbau des Versuchs eine Entscheidung: OpenSearch kommt für die Suche infrage, wenn es bei denselben repräsentativen Anfragen mehr relevante Treffer liefert, ohne die vereinbarte Antwortzeit und Aktualität zu verletzen. Als zu kalibrierende Startwerte könntest du einen Recall@10 von mindestens 0,90, eine p95-Antwortzeit unter 200 Millisekunden und eine p95-Indexverzögerung unter 30 Sekunden festlegen. Das sind keine allgemeingültigen Grenzwerte; sie müssen aus den Anforderungen deiner Anwendung und der vorhandenen PostgreSQL-Baseline entstehen.

Recall@10 beantwortet eine andere Frage als Latenz: Wie viele der als relevant markierten Dokumente erscheinen unter den ersten zehn Treffern? Die p95-Latenz beschreibt dagegen, wie lange 95 Prozent der beobachteten Anfragen höchstens dauern. Die Indexverzögerung misst die Zeit zwischen einer bestätigten Änderung in PostgreSQL und ihrer Sichtbarkeit in der Suche. Keiner dieser Werte ersetzt die anderen, weil ein schneller Index mit veralteten oder irrelevanten Treffern den Zweck der Suche verfehlt.

Dein erster R&D-Prototyp braucht keinen Rewrite ist als Arbeitsregel plausibel, beantwortet aber die Entscheidungsfrage nicht, weil der Zustand des Prototyps nichts über seinen Nutzen für echte Suchanfragen aussagt. Ich würde deshalb nicht zuerst Produktionsarchitektur, Fehlertoleranz und Deployment perfektionieren: Diese Arbeit erhöht die Versuchskosten, bevor feststeht, ob der neue Suchansatz überhaupt einen relevanten Vorteil hat. Ein einmalig reproduzierbarer Datenimport und ein überprüfbarer Messpfad reichen zunächst aus.

Halte zugleich die Abbruchregel schriftlich fest. Wenn die Trefferqualität nur bei wenigen handverlesenen Anfragen steigt oder die Aktualitätsgrenze im vorgesehenen Schreibbetrieb regelmäßig überschritten wird, lautet das Ergebnis nicht „noch etwas tunen“, sondern „unter diesen Bedingungen vorerst nicht wechseln“. Ein negativer Versuch ist nützlich, weil er eine kostspielige Einführung mit einem nachvollziehbaren Grund verhindert.

Die Vergleichsgruppe muss dieselben Anfragen und Änderungen sehen

PostgreSQL-Full-Text-Suche gegen OpenSearch ist ein Vergleich zweier konkreter Optionen, kein Wettlauf zweier Standardeinstellungen. PostgreSQL gewinnt häufig, wenn das vorhandene Suchverhalten genügt und neue oder geänderte Datensätze unmittelbar nach dem Commit auffindbar sein müssen: Es bleibt bei einem Datenbestand und einem Betriebsweg. Das kostet mögliche Verbesserungen bei Ranking und sprachlicher Suche. OpenSearch gewinnt, wenn seine Trefferqualität für die tatsächlichen Anfragen deutlich besser ausfällt und die Anwendung eine messbare Verzögerung beim Indexieren verträgt. Dafür kostet es einen zweiten Datenbestand, Synchronisation und einen zusätzlichen Fehlerpfad. Welche Seite gewinnt, kann nur der Versuch für diese Anwendung entscheiden.

Ziehe für beide Varianten dieselben Suchanfragen aus einem passenden Zeitraum und entferne personenbezogene Daten vor dem Test. Teile sie vor dem Tuning in Entwicklungs- und Prüfmenge; andernfalls optimierst du womöglich auf genau die Anfragen, mit denen du später Erfolg behauptest. Ein geplanter Prüfsatz von 5.000 Anfragen ist beispielsweise ein Arbeitswert, keine statistische Garantie: Wenn fast alle Anfragen identisch sind, fehlen trotzdem seltene, aber wichtige Fälle wie Tippfehler, leere Ergebnisse und mehrdeutige Namen.

Für die Qualitätsprüfung brauchst du Relevanzurteile. Markiere zu einer geschichteten Auswahl von Anfragen passende Dokumente, und ziehe Kandidaten aus den Treffern beider Systeme. Würdest du nur PostgreSQL-Treffer beurteilen lassen, könnten passende Dokumente, die ausschließlich OpenSearch findet, im Bewertungsmaßstab fehlen. Halte Regeln für Grenzfälle schriftlich fest und lass strittige Urteile gegenprüfen, weil sonst Unterschiede zwischen Bewertenden als vermeintlicher Suchfortschritt erscheinen können.

Die Last muss ebenfalls vergleichbar sein. Nutze beispielsweise k6 v0.49 für denselben Anfrage-Mix und dieselbe Nebenlast durch Dokumentänderungen. Verifiziere mit EXPLAIN (ANALYZE, BUFFERS) und pg_stat_statements, welche PostgreSQL-Abfragen tatsächlich laufen; erfasse bei OpenSearch die Such- und Indexierungszeiten getrennt. Eine aufgewärmte OpenSearch-Instanz mit einer kalten PostgreSQL-Datenbank zu vergleichen, wäre unfair, weil dann der Cache-Zustand und nicht die Suchoption den Unterschied erklären könnte.

Vergiss die Aktualität nicht: Schreibe Testdokumente mit eindeutiger ID und Zeitstempel, bestätige zuerst die Änderung in PostgreSQL und frage anschließend wiederholt beide Suchen ab. Miss die Zeit bis zum ersten korrekten Treffer beziehungsweise bis zum Verschwinden eines gelöschten Dokuments. Gerade Löschungen gehören in den Test, weil ein System mit schneller Aufnahme neuer Dokumente trotzdem veraltete Ergebnisse ausliefern kann.

Eine einzelne Kennzahl verdeckt den Preis eines besseren Rankings

Für einen ersten reproduzierbaren Durchlauf genügt eine JSONL-Datei: Pro Zeile stehen die als relevant beurteilten IDs in truth, die geordneten Treffer in hits, die gemessene Antwortzeit in ms und die Indexverzögerung in lag_s. Das folgende Skript läuft mit Python 3 als python3 eval.py queries.jsonl. Es erwartet mindestens eine Zeile und für jede Anfrage wenigstens ein relevantes Dokument.

import json, math, sys
rows = [json.loads(line) for line in open(sys.argv[1], encoding="utf-8")]
assert rows and all(r["truth"] for r in rows)
recall = [len(set(r["hits"][:10]) & set(r["truth"])) / len(set(r["truth"])) for r in rows]
ms = sorted(r["ms"] for r in rows)
lag = sorted(r["lag_s"] for r in rows)
idx = math.ceil(0.95 * len(rows)) - 1
print(f"recall@10={sum(recall)/len(rows):.3f}")
print(f"p95_ms={ms[idx]} p95_lag_s={lag[idx]}")

Das Skript macht die Berechnung sichtbar, aber noch keinen belastbaren Versuch. Es mittelt Recall@10 über Anfragen; eine Anfrage mit einem relevanten Dokument zählt damit genauso viel wie eine mit mehreren. Das kann für eine nutzerbezogene Entscheidung sinnvoll sein, sollte jedoch ausdrücklich festgelegt werden. Außerdem unterschätzt ein offline beurteiltes Set leicht den Schaden durch erfolglose Suchen, wenn nur Anfragen mit bekannten Treffern aufgenommen wurden.

Ein ausdrücklich fiktiver Testlauf könnte für OpenSearch einen p95-Wert von 184 Millisekunden ergeben. Dieser Wert läge unter der oben gesetzten Schranke, wäre für sich genommen aber kein Erfolg: Er sagt weder, ob PostgreSQL schneller war, noch, wie viele Anfragen fehlgeschlagen sind. Erfasse deshalb HTTP-Fehler, Timeouts und leere Ergebnisse als eigene Zähler. Entferne fehlgeschlagene Anfragen nicht stillschweigend aus der Latenzverteilung, weil die verbleibenden erfolgreichen Antworten sonst ein zu gutes Bild zeichnen.

OpenTelemetry kann die Anfrage vom API-Endpunkt bis zum jeweiligen Suchaufruf verfolgen; Prometheus-Histogramme liefern Latenzverteilungen, die Grafana getrennt nach Suchvariante sichtbar macht. Nutze für den Vergleich dieselben Histogramm-Buckets und kontrolliere die Abfrage hinter histogram_quantile(0.95, …), weil unterschiedlich aggregierte Zeitreihen scheinbare p95-Unterschiede erzeugen können. Traces helfen anschließend bei der Ursachenanalyse, sind aber kein Ersatz für Zähler und Verteilungen, weil gesampelte Einzelanfragen seltene Fehler übersehen können.

Notiere neben dem Ergebnis auch die Unsicherheit. Wiederhole Lastläufe unter möglichst ähnlichen Bedingungen und prüfe Qualitätsunterschiede auf mehreren Teilmengen, etwa häufigen Anfragen und Tippfehlern. Wenn OpenSearch nur in einer kleinen Teilmenge gewinnt, kann ein gezielter Umgang mit genau diesen Anfragen günstiger sein als ein vollständiger Wechsel. Die Teilmengen sollten vor der Auswertung definiert werden, weil nachträglich ausgewählte Gewinnergruppen die Entscheidung verzerren.

Der Versuch endet mit einer Entscheidung, nicht mit einem Dashboard

R&D in der Softwareentwicklung: Trends und Best Practices liefert Anstöße für neue Ansätze, doch eine Trendliste kann die Freigabe dieses Suchsystems nicht begründen, weil dafür lokale Daten, Nutzeranfragen und Betriebsgrenzen ausschlaggebend sind. Ein Dashboard ist ebenso wenig eine Entscheidung, solange niemand festgelegt hat, welche Kombination aus Qualitätsgewinn, Latenz und Indexverzögerung akzeptabel ist.

Schreibe nach dem Versuch eine kurze Entscheidungsnotiz mit Hypothese, Datenherkunft, Varianten, Messfenster, Relevanzregeln und Abbruchgründen. Trenne Beobachtung und Urteil. „Im Prüfsatz steigt Recall@10“ ist eine Beobachtung; „Wir führen OpenSearch ein“ ist ein Urteil, das zusätzlich die Aktualität, Fehler und den Betriebsaufwand berücksichtigen muss. Füge die Rohdaten oder einen reproduzierbaren Verweis hinzu, weil ein Ergebnis ohne überprüfbare Grundlage bei der nächsten Produktänderung kaum erneut bewertet werden kann.

Prüfe auch, ob das Experiment die Produktion ausreichend abbildet. Ein isolierter Lasttest ist gut, um einen offensichtlichen Engpass günstig zu finden; er kostet wenig Abstimmung, blendet aber konkurrierende Dienste und reale Anfragefolgen aus. Ein begrenzter Shadow-Traffic-Versuch spielt echte Anfragen zusätzlich gegen die neue Suche, ohne deren Treffer auszuliefern; er gewinnt bei realistischem Mix, kostet jedoch zusätzliche Ressourcen und verlangt sorgfältigen Umgang mit Daten. Ich würde mit dem isolierten Test starten und erst bei einem klaren Qualitätssignal Shadow Traffic einsetzen, weil teurere Messung ohne plausible Erfolgschance keinen Erkenntnisgewinn verspricht.

Bleibt das Ergebnis uneindeutig, benenne die konkrete Lücke: Fehlen Relevanzurteile für seltene Anfragen? Schwankt die Indexverzögerung bei Schreiblast? Ist der Qualitätsgewinn kleiner als die Streuung zwischen Testläufen? Dann folgt ein begrenzter weiterer Versuch mit genau dieser offenen Frage. „Mehr Daten sammeln“ ohne benannte Unsicherheit würde ich nicht als nächsten Schritt akzeptieren, weil es Aufwand erzeugt, ohne die Entscheidbarkeit gezielt zu verbessern.

Der erste Schritt ist eine überprüfbare Suchfrage

Nimm morgen zwanzig anonymisierte Suchanfragen und notiere für jede, welche Dokumente tatsächlich hilfreich wären. Miss danach mit der bestehenden PostgreSQL-Suche Treffer, Antwortzeit und Sichtbarkeit einer frisch geänderten Datei. Erst mit dieser kleinen Baseline kannst du einen Grenzwert begründen und entscheiden, ob ein OpenSearch-Versuch überhaupt eine offene Frage beantwortet. Ohne Baseline misst du vor allem Begeisterung für den neuen Ansatz.