Ungefähre nächste Nachbarn finden, Vektorindizes erstellen und Vektoreinbettungen abfragen

Auf dieser Seite wird beschrieben, wie Sie ungefähre nächste Nachbarn (Approximate Nearest Neighbors, ANN) finden, Vektorindexe erstellen und Vektoreinbettungen mit den folgenden ANN-Distanzfunktionen in Spanner abfragen:

  • APPROX_COSINE_DISTANCE
  • APPROX_EUCLIDEAN_DISTANCE
  • APPROX_DOT_PRODUCT

Bei einem kleinen Datensatz können Sie K-Nearest Neighbors (KNN) verwenden, um die genauen k-nächsten Vektoren zu finden. Mit zunehmender Größe des Datensatzes steigen jedoch auch die Latenz und die Kosten einer KNN-Suche. Mit ANN können Sie die ungefähren k-nächsten Nachbarn mit deutlich geringerer Latenz und geringeren Kosten finden.

Ungefähre k-nächste Nachbarn

Bei einer ANN-Suche sind die k zurückgegebenen Vektoren nicht die tatsächlichen k nächsten Nachbarn, da bei der ANN-Suche ungefähre Distanzen berechnet werden und möglicherweise nicht alle Vektoren im Datensatz berücksichtigt werden. Gelegentlich werden einige Vektoren zurückgegeben, die nicht zu den k nächsten Nachbarn gehören. Das wird als Recall-Verlust bezeichnet. Wie viel Trefferquotenverlust für Sie akzeptabel ist, hängt vom Anwendungsfall ab. In den meisten Fällen ist es jedoch ein akzeptabler Kompromiss, etwas Trefferquote im Gegenzug für eine verbesserte Datenbankleistung zu verlieren.

Weitere Informationen zu den ungefähren Distanzfunktionen von Spanner finden Sie unter:

Vektorindex

Cloud Spanner beschleunigt ANN-Vektorsuchen mithilfe eines speziellen Vektorindex. Dieser Index nutzt Scalable Nearest Neighbor (ScaNN) von Google Research, einen hocheffizienten Algorithmus für die Suche nach dem nächsten Nachbarn.

Der Vektorindex verwendet eine baumartige Struktur, um Daten zu partitionieren und schnellere Suchvorgänge zu ermöglichen. Spanner bietet sowohl zwei- als auch dreistufige Baumkonfigurationen:

  • Konfiguration mit zwei Ebenen: Blattknoten (num_leaves) enthalten Gruppen von eng verwandten Vektoren zusammen mit dem entsprechenden Zentroid. Die Stammebene besteht aus den Schwerpunkten aller Blattknoten.
  • Baumkonfiguration mit drei Ebenen: Ähnlich wie bei einem Baum mit zwei Ebenen wird eine zusätzliche Ebene (num_branches) eingeführt, aus der die Schwerpunkte der Blattknoten weiter unterteilt werden, um die Stammebene (num_leaves) zu bilden.

Spanner wählt einen Index für Sie aus. Wenn Sie jedoch wissen, dass ein bestimmter Index am besten funktioniert, können Sie mit dem FORCE_INDEX-Hinweis den am besten geeigneten Vektorindex für Ihren Anwendungsfall auswählen.

Weitere Informationen finden Sie unter VECTOR INDEX-Anweisungen.

Beschränkungen

Vektorindex erstellen

Zur Optimierung des Rückrufs und der Leistung eines Vektorindex empfehlen wir Folgendes:

  • Erstellen Sie den Vektorindex, nachdem die meisten Zeilen mit Einbettungen in Ihre Datenbank geschrieben wurden. Möglicherweise müssen Sie den Vektorindex auch regelmäßig neu erstellen, nachdem Sie neue Daten eingefügt haben. Weitere Informationen finden Sie unter Vektorindex neu erstellen.

  • Mit der Klausel STORING können Sie eine Kopie einer Spalte im Vektorindex speichern. Wenn ein Spaltenwert im Vektorindex gespeichert ist, führt Spanner das Filtern auf der Blattebene des Index aus, um die Abfrageleistung zu verbessern. Wir empfehlen, eine Spalte zu speichern, wenn sie in einer Filterbedingung verwendet wird. Weitere Informationen zur Verwendung von STORING in einem Index finden Sie unter Index für reine Indexscans erstellen.

Wenn Sie die Tabelle erstellen, muss die Einbettungsspalte ein Array des Datentyps FLOAT32 (empfohlen) oder FLOAT64 sein und die Annotation vector_length enthalten, die die Dimension der Vektoren angibt.

Mit der folgenden DDL-Anweisung wird eine Tabelle Documents mit einer Einbettungsspalte DocEmbedding mit einer Vektorlänge erstellt:

CREATE TABLE Documents (
  DocId INT64 NOT NULL,
  ...
  DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);

Nachdem Sie die Tabelle Documents mit Daten gefüllt haben, können Sie einen Vektorindex mit einem zweistufigen Baum und 1.000 Blattknoten für eine Tabelle Documents mit einer Einbettungsspalte DocEmbedding mit dem Kosinusabstand erstellen:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(DocEmbedding)
  STORING (WordCount)
  OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);

So erstellen Sie einen Vektorindex mit einem dreistufigen Baum und 1.000.000 Blattknoten:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(NullableDocEmbedding)
  STORING (WordCount)
  WHERE NullableDocEmbedding IS NOT NULL
  OPTIONS (distance_type = 'COSINE', tree_depth = 3, num_branches=1000, num_leaves = 1000000);

Wenn Ihre Spalte für Einbettungen in der Tabellendefinition nicht als NOT NULL gekennzeichnet ist, müssen Sie sie mit einer WHERE COLUMN_NAME IS NOT NULL-Klausel deklarieren:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(NullableDocEmbedding)
  STORING (WordCount)
  WHERE NullableDocEmbedding IS NOT NULL
  OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);

Vektorindex filtern

Sie können auch einen gefilterten Vektorindex erstellen, um die ähnlichsten Elemente in Ihrer Datenbank zu finden, die der Filterbedingung entsprechen. Bei einem gefilterten Vektorindex werden nur Zeilen indexiert, die die angegebenen Filterbedingungen erfüllen. Dadurch wird die Suchleistung verbessert.

Im folgenden Beispiel hat die Tabelle Documents eine Spalte namens Category. Bei der Vektorsuche soll die Kategorie „Tech“ indexiert werden. Daher erstellen wir eine generierte Spalte, die NULL ergibt, wenn die Kategoriebedingung nicht erfüllt ist.

CREATE TABLE Documents (
  DocId INT64 NOT NULL,
  Category STRING(MAX),
  NullIfFiltered BOOL AS (IF(Category = 'Tech', TRUE, NULL)) HIDDEN,
  DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);

Anschließend erstellen wir einen Vektorindex mit einem Filter. Im TechDocEmbeddingIndex-Vektorindex werden nur Dokumente in der Kategorie „Tech“ indexiert.

CREATE VECTOR INDEX TechDocEmbeddingIndex
  ON Documents(DocEmbedding)
  STORING(NullIfFiltered)
  WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
  OPTIONS (...);

Wenn Spanner die folgende Abfrage ausführt, die Filter enthält, die mit TechDocEmbeddingIndex übereinstimmen, wird TechDocEmbeddingIndex automatisch ausgewählt und beschleunigt. Mit der Abfrage werden nur Dokumente in der Kategorie „Tech“ durchsucht. Sie können auch {@FORCE_INDEX=TechDocEmbeddingIndex} verwenden, um Spanner zu zwingen, TechDocEmbeddingIndex explizit zu verwenden.

SELECT *
FROM Documents
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
ORDER BY APPROX_(....)
LIMIT 10;

Vektoreinbettungen abfragen

Wenn Sie einen Vektorindex abfragen möchten, verwenden Sie eine der drei Näherungsfunktionen für die Distanz:

  • APPROX_COSINE_DISTANCE
  • APPROX_EUCLIDEAN_DISTANCE
  • APPROX_DOT_PRODUCT

Für die Verwendung der Näherungsfunktionen für die Distanz gelten die folgenden Einschränkungen:

  • Die Funktion für die ungefähre Distanz muss die Distanz zwischen einer Spalte mit Einbettungen und einem konstanten Ausdruck (z. B. einem Parameter oder einem Literal) berechnen.
  • Die Ausgabe der Funktion für die ungefähre Distanz muss in einer ORDER BY-Klausel als einziger Sortierschlüssel verwendet werden und nach der ORDER BY muss eine LIMIT angegeben werden.
  • In der Abfrage müssen Zeilen, die nicht indexiert sind, explizit herausgefiltert werden. In den meisten Fällen muss die Abfrage eine WHERE <column_name> IS NOT NULL-Klausel enthalten, die der Vektorindexdefinition entspricht, es sei denn, die Spalte ist in der Tabellendefinition bereits als NOT NULL gekennzeichnet.

Eine detaillierte Liste der Einschränkungen finden Sie auf der Referenzseite zur Funktion für die ungefähre Distanz.

Beispiele

So suchen Sie nach den 100 Vektoren, die [1.0, 2.0, 3.0] am nächsten sind:

SELECT DocId
FROM Documents
WHERE WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
  ARRAY<FLOAT32>[1.0, 2.0, 3.0], DocEmbedding,
  options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100

Wenn die Spalte für Einbettungen Nullwerte enthalten kann:

SELECT DocId
FROM Documents
WHERE NullableDocEmbedding IS NOT NULL AND WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
  ARRAY<FLOAT32>[1.0, 2.0, 3.0], NullableDocEmbedding,
  options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100

Best Practices

Mit den folgenden Best Practices können Sie Ihre Vektorindexe optimieren und die Suchergebnisse verbessern.

Optionen für die Vektorsuche anpassen

Der optimale Wert für die Vektorsuche hängt vom Anwendungsfall, dem Vektordatensatz und den Anfragevektoren ab. Möglicherweise müssen Sie iterative Anpassungen vornehmen, um die besten Werte für Ihre spezifische Arbeitslast zu finden.

Hier sind einige hilfreiche Richtlinien für die Auswahl geeigneter Werte:

  • tree_depth (Baumebene): Wenn die zu indexierende Tabelle weniger als 10 Millionen Zeilen hat, verwenden Sie für tree_depth den Wert 2. Andernfalls unterstützt ein tree_depth von 3 Tabellen mit bis zu etwa 10 Milliarden Zeilen.

  • num_leaves: Verwenden Sie die Quadratwurzel der Anzahl der Zeilen im Dataset. Ein größerer Wert kann die Build-Zeit des Vektorindex verlängern. Vermeiden Sie es, num_leaves größer als table_row_count/1000 festzulegen, da dies zu zu kleinen Blättern und einer schlechten Leistung führt.

  • num_leaves_to_search: Mit dieser Option wird angegeben, wie viele Blattknoten des Index durchsucht werden. Durch Erhöhen von num_leaves_to_search wird der Recall verbessert, aber auch die Latenz und die Kosten steigen. Wir empfehlen, für num_leaves_to_search eine Zahl zu verwenden, die 1% der Gesamtzahl der Blätter in der CREATE VECTOR INDEX-Anweisung entspricht. Wenn Sie eine Filterklausel verwenden, erhöhen Sie diesen Wert, um die Suche auszuweiten.

Wenn ein akzeptabler Recall erreicht wird, die Kosten für Abfragen jedoch zu hoch sind, was zu einer niedrigen maximalen QPS führt, versuchen Sie, num_leaves zu erhöhen. Gehen Sie dazu so vor:

  1. Setzen Sie num_leaves auf ein Vielfaches k des ursprünglichen Werts (z. B. 2 * sqrt(table_row_count)).
  2. Legen Sie num_leaves_to_search als das k-fache des ursprünglichen Werts fest.
  3. Reduzieren Sie num_leaves_to_search, um Kosten und QPS zu verbessern und gleichzeitig den Recall beizubehalten.

Erinnerung verbessern

Es gibt mehrere Möglichkeiten, wie sich der Recall verschlechtern kann, z. B.:

  • num_leaves_to_search ist zu klein: Möglicherweise ist es schwieriger, die nächsten Nachbarn für einige Anfragevektoren zu finden. Wenn Sie num_leaves_to_search erhöhen, um mehr Blätter zu durchsuchen, kann die Trefferquote verbessert werden. Bei den letzten Anfragen sind möglicherweise mehr dieser anspruchsvollen Vektoren enthalten.

  • Vektorindex muss neu erstellt werden: Die Baumstruktur des Vektorindex wird zum Zeitpunkt der Erstellung für das Dataset optimiert und ist danach statisch. Wenn nach dem Erstellen des ursprünglichen Vektorindex deutlich unterschiedliche Vektoren hinzugefügt werden, ist die Baumstruktur möglicherweise suboptimal, was zu einem schlechteren Recall führt.

Vektorindex neu erstellen

So erstellen Sie Ihren Vektorindex ohne Ausfallzeiten neu:

  1. Erstellen Sie einen neuen Vektorindex für dieselbe Einbettungsspalte wie den aktuellen Vektorindex und aktualisieren Sie die Parameter (z. B. OPTIONS) entsprechend.
  2. Nachdem der Index erstellt wurde, verwenden Sie den FORCE_INDEX-Hinweis, um auf den neuen Index zu verweisen und die Vektorsuchanfrage zu aktualisieren. So wird sichergestellt, dass für die Abfrage der neue Vektorindex verwendet wird. Möglicherweise müssen Sie num_leaves_to_search in Ihrer neuen Abfrage neu abstimmen.
  3. Löschen Sie den veralteten Vektorindex.

Nächste Schritte