28 Punkte von xguru 2024-08-26 | 2 Kommentare | Auf WhatsApp teilen
  • In Postgres selbst lässt sich eine hybride Suchmaschine aufbauen, die semantische Suche, Volltextsuche und Fuzzy Search vereint
  • Suche ist in vielen Apps ein wichtiger Bestandteil, lässt sich aber nicht leicht sauber umsetzen. Besonders in RAG-Pipelines kann die Suchqualität über Erfolg oder Misserfolg des gesamten Prozesses entscheiden
  • Semantische Suche ist zwar im Trend, doch die traditionelle lexikalische Suche bleibt weiterhin das Rückgrat der Suche
  • Semantische Verfahren können die Ergebnisse verbessern, funktionieren aber am besten auf der Grundlage einer robusten textbasierten Suche

Aufbau einer Suchmaschine mit Postgres

  • Kombination aus drei Techniken:
    • Volltextsuche mit tsvector
    • Semantische Suche mit pgvector
    • Fuzzy Matching mit pg_trgm
  • Dieser Ansatz ist nicht in jeder Situation absolut der Beste, aber eine hervorragende Alternative zum Aufbau eines separaten Suchdienstes
  • Ein robuster Ausgangspunkt, der sich innerhalb einer bestehenden Postgres-Datenbank implementieren und skalieren lässt
  • Warum man Postgres für alles nutzen sollte: Einfach Postgres überall verwenden, PostgreSQL reicht aus, Nehmt einfach Postgres

FTS und semantische Suche umsetzen

  • Supabase bietet eine hervorragende Dokumentation zur Implementierung hybrider Suche, die hier als Ausgangspunkt dient
  • Entsprechend dem Guide wird FTS mit einem GIN-Index umgesetzt, die semantische Suche mit pgvector (auch bi-encoder dense retrieval genannt)
  • Nach persönlicher Erfahrung liefert die Wahl von Embeddings mit 1536 Dimensionen deutlich bessere Ergebnisse
  • Die Supabase-Funktion wird durch CTEs und Queries ersetzt, Parameter erhalten ein vorangestelltes $
  • Zum Zusammenführen der Ergebnisse wird hier RRF (Reciprocal Ranked Fusion) verwendet
  • Diese Methode stellt sicher, dass Einträge, die in mehreren Listen weit oben rangieren, auch in der finalen Liste weit oben erscheinen
  • Außerdem verhindert sie, dass Einträge, die nur in einigen Listen hoch und in anderen niedrig ranken, in der finalen Liste zu hoch eingestuft werden
  • Wenn der Rang im Nenner zur Berechnung des Scores verwendet wird, kann das niedrig platzierte Datensätze benachteiligen
  • Bemerkenswerte Punkte
    • $rrf_k: Damit der Score des erstplatzierten Eintrags nicht extrem hoch wird (weil durch den Rang geteilt wird), wird oft eine Konstante k zum Nenner addiert, um die Scores zu glätten
    • $_weight: Jedem Verfahren kann ein Gewicht zugewiesen werden. Das ist beim Feinjustieren der Ergebnisse sehr nützlich

Fuzzy Search umsetzen

  • Mit den bisherigen Methoden lässt sich zwar schon vieles abdecken, aber Tippfehler bei benannten Entitäten können sofort Probleme verursachen
  • Semantische Suche erfasst Ähnlichkeiten und beseitigt so einige dieser Probleme, hat aber Schwierigkeiten mit Namen, Akronymen und anderen Texten, die semantisch nicht ähnlich sind
  • Um das abzumildern, wird die Erweiterung pg_trgm eingeführt, die Fuzzy Search ermöglicht
    • Sie arbeitet mit Trigrammen. Trigramme zerlegen Wörter in Sequenzen aus drei Zeichen und sind deshalb für Fuzzy Search nützlich
    • Dadurch lassen sich ähnliche Wörter matchen, selbst wenn Tippfehler oder leichte Abweichungen enthalten sind
    • So teilen etwa "hello" und "helo" viele Trigramme und lassen sich deshalb in einer Fuzzy Search leichter matchen
  • Für die gewünschten Spalten wird ein neuer Index erstellt und anschließend in die gesamte Such-Query eingebunden
  • Die Erweiterung pg_trgm stellt den Operator % bereit, der Texte filtert, deren Ähnlichkeit größer ist als pg_trgm.similarity_threshold (Standardwert 0.3)
  • Es gibt auch mehrere andere nützliche Operatoren

Volltextsuche feinabstimmen

  • tsvector-Gewichtung anpassen: Reale Dokumente bestehen nicht nur aus einem Titel, sondern auch aus Inhalt
  • Auch bei mehreren Spalten wird nur eine Embedding-Spalte beibehalten
  • Persönlich wurde festgestellt, dass es leistungsmäßig keinen großen Unterschied macht, ob title und body in einem gemeinsamen Embedding liegen oder mehrere Embeddings gepflegt werden
  • Letztlich sollte title eine knappe Darstellung des Inhalts sein. Je nach Bedarf lohnt es sich, damit zu experimentieren
  • title ist voraussichtlich kurz und keyword-reich, während body länger ist und mehr Details enthält
  • Deshalb sollte abgestimmt werden, wie die einzelnen Volltextsuch-Spalten gegeneinander gewichtet werden
  • Je nach Position oder Wichtigkeit eines Wortes im Dokument kann Priorität vergeben werden
    • A-weight: am wichtigsten (z. B. Titel, Header). Standardwert 1.0
    • B-weight: wichtig (z. B. Dokumentanfang, Zusammenfassung). Standardwert 0.4
    • C-weight: Standardwichtigkeit (z. B. Fließtext). Standardwert 0.2
    • D-weight: am wenigsten wichtig (z. B. Fußnoten, Anmerkungen). Standardwert 0.1
  • Durch Anpassung der Gewichte an Dokumentstruktur und Anwendungsanforderungen lässt sich die Relevanz feinjustieren
  • Warum dem Titel mehr Gewicht gegeben wird
    • Weil der Titel das Hauptthema eines Dokuments in der Regel knapp ausdrückt
    • Nutzer überfliegen bei der Suche meist zuerst die Titel, daher passt ein Keyword-Treffer im Titel gewöhnlich besser zur Suchabsicht als ein Treffer im Fließtext

Anpassung nach Länge

  • Wer die Dokumentation zu ts_rank_cd liest, sieht, dass es Normalisierungsparameter gibt
    • Beide Ranking-Funktionen verwenden die Integer-Option normalization, um festzulegen, ob und wie sich die Dokumentlänge auf das Ranking auswirken soll. Die Integer-Option ist eine Bitmaske, da sie mehrere Verhaltensweisen steuert: Mit | können eine oder mehrere Verhaltensweisen angegeben werden (z. B. 2|4).

  • Mit diesen verschiedenen Optionen lässt sich Folgendes erreichen
    • Verzerrungen durch Dokumentlänge ausgleichen
    • Relevanz über unterschiedliche Dokumentmengen hinweg ausbalancieren
    • Ranking-Ergebnisse für eine konsistente Darstellung anpassen
  • Gute Ergebnisse lassen sich erzielen, wenn für den Titel 0 (keine Normalisierung) und für den Inhalt 1 (logarithmische Dokumentlänge) gesetzt wird
  • Auch hier gilt: Es lohnt sich, verschiedene Optionen auszuprobieren, um die beste für den eigenen Anwendungsfall zu finden

Re-Ranking mit Cross-Encodern

  • Viele Suchsysteme bestehen aus zwei Stufen
  • Dabei werden zunächst mit einem bi-encoder die ersten N Ergebnisse gesucht und diese dann mit einem cross-encoder erneut gegen die Such-Query verglichen und sortiert
    • bi-encoder: schnell und deshalb gut geeignet, um viele Dokumente zu durchsuchen
    • cross-encoder
      • langsamer, aber leistungsfähiger und deshalb gut geeignet, um gefundene Ergebnisse neu zu ranken
      • verarbeitet Query und Dokument gemeinsam und ermöglicht so ein differenzierteres Verständnis ihrer Beziehung
      • liefert eine bessere Ranking-Genauigkeit auf Kosten von Rechenzeit und Skalierbarkeit
  • Für diesen Zweck gibt es verschiedene Tools
  • Eines der besten ist Cohere Rerank
  • Eine weitere Möglichkeit ist, es selbst mit GPT von OpenAI aufzubauen
  • Cross-Encoder können die Genauigkeit von Suchergebnissen erhöhen, indem sie die Beziehung zwischen Query und Dokument besser verstehen
  • Wegen der hohen Rechenkosten sind sie bei der Skalierbarkeit jedoch eingeschränkt
  • Deshalb ist ein zweistufiger Ansatz effektiv: bi-encoder für die erste Suche, Cross-Encoder nur für die kleine Zahl der bereits gefundenen Dokumente

Wann man nach Alternativen suchen sollte

  • PostgreSQL ist für viele Suchszenarien eine gute Wahl, aber nicht ohne Grenzen
  • Das Fehlen fortgeschrittener Algorithmen wie BM25 kann sich bemerkbar machen, wenn mit unterschiedlich langen Dokumenten gearbeitet wird
  • Die PostgreSQL-Volltextsuche basiert auf TF-IDF und kann deshalb mit sehr langen Dokumenten und seltenen Begriffen in großen Sammlungen Schwierigkeiten haben
  • Bevor man nach einer alternativen Lösung sucht, sollte man unbedingt messen. Möglicherweise lohnt sich der Wechsel gar nicht

Fazit

  • Dieser Artikel behandelt vieles: von grundlegender Volltextsuche über Fuzzy Matching und semantische Suche bis hin zu fortgeschrittenen Verfahren wie Result-Boosting
  • Mit den starken Funktionen von Postgres lässt sich eine leistungsfähige und flexible Suchmaschine aufbauen, die an konkrete Anforderungen angepasst ist
  • Postgres ist vielleicht nicht das erste Tool, das einem für Suche einfällt, aber es bringt einen wirklich weit
  • Die Schlüssel zu einer großartigen Search Experience
    • kontinuierliche Iteration und Feinabstimmung
    • Man sollte keine Scheu haben, die besprochenen Debugging-Techniken zu nutzen, die Suchleistung zu verstehen und Gewichte sowie Parameter auf Basis von Nutzerfeedback und Nutzerverhalten anzupassen
  • PostgreSQL mag bei fortgeschrittenen Suchfunktionen Defizite haben, ist aber in den meisten Fällen leistungsfähig genug, um eine starke Suchmaschine aufzubauen
  • Bevor man alternative Lösungen sucht, ist es sinnvoll, zuerst die Möglichkeiten von Postgres maximal auszuschöpfen und die Leistung zu messen. Wenn das immer noch nicht reicht, kann man dann andere Lösungen in Betracht ziehen

2 Kommentare

 
eajrezz 2024-08-27

Ich frage mich, ob auch die koreanische Suche gut funktioniert.

 
xguru 2024-08-26

Auch das heutige Wochenthema war Postgres, und wie erwartet geht es schon wieder um Postgres. Offenbar erscheinen wirklich viele Artikel dazu, ganz entsprechend seiner Beliebtheit haha.
Zu BM25 siehe unten.

pg_bm25 - Full-Text-Sucherweiterung für Postgres, die Qualität auf Elastic-Niveau liefert
ParadeDB - PostgreSQL for Search