在 Spanner 中查找向量嵌入并为其编制索引的近似最近邻

本页面介绍了如何创建矢量索引和查询矢量嵌入,方法是 分别使用近似余弦距离、近似欧几里得距离和 近似点积矢量函数。您可以使用这些 函数来查找 Spanner 中的近似最近邻 (ANN)。 当数据集较小时,您可以使用 K 最近邻 (KNN) 找出精确的 k 最近向量。然而,随着数据集的不断增多,延迟时间 KNN 搜索的费用也会增加。您可以使用 ANN 找出 K 最近邻,以显著缩短延迟时间和降低费用。

k 最近邻项

在 ANN 搜索中,k 返回的向量不是真正的顶级 k 最近点 相邻。有时,某些向量不在顶端 k 最近值范围内 相邻项。这称为召回率损失。召回率损失是多少 取决于应用场景,但在大多数情况下, 通过召回率提升数据库性能是可以接受的 权衡。

如需详细了解 Spanner 近似距离函数, 请参阅:

向量索引

Spanner 使用 向量索引。此索引利用了 Google Research 的“可扩展最近搜索”功能 邻居 (ScaNN), 一种高效的最近邻算法。

向量索引使用树状结构将数据分区并方便 加快搜索速度。Spanner 提供两级和三级 树配置:

  • 两级树配置:叶节点 (num_leaves) 包含 紧密相关的向量及其对应的形心。根 由所有叶节点的形心组成。
  • 三级树配置:在概念上与两级树类似, 引入一个额外的分支层 (num_branches),从该分支节点 形心进一步分区,形成根级别 (num_leaves)。

此外,您必须使用特定距离指标构建矢量索引。 您可以通过设置 将 distance_type 设置为 COSINEDOT_PRODUCTEUCLIDEAN 之一。

如需了解详情,请参阅 VECTOR INDEX 语句

限制

Spanner 矢量索引具有以下限制:

  • 不支持 ALTER VECTOR INDEX

创建向量索引

为了以最佳方式优化矢量索引以获得良好的召回率和效果,我们建议 在大多数包含嵌入的行之后,创建向量索引。 写入数据库您可能还需要定期 重新构建矢量索引。如需了解详情,请参阅 重新构建向量索引

要创建包含两层树和 1,000 个叶节点的向量索引, 包含使用余弦的嵌入列 DocEmbeddingDocuments 表 距离:

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

要创建包含三级树和 1000000 个叶节点的向量索引,请执行以下操作:

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

如果嵌入列可为 null,则必须使用 WHERE column_name IS NOT NULL 子句:

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

查询向量嵌入

如需查询矢量索引,请使用以下三种近似距离函数之一:

  • APPROX_COSINE_DISTANCE
  • APPROX_EUCLIDEAN_DISTANCE
  • APPROX_DOT_PRODUCT

使用近似距离函数时的限制包括 以下:

  • 您必须提供查询提示才能使用向量索引。
  • 您必须使用常量表达式作为距离函数的一个参数 (例如,形参或字面量)。
  • 使用近似距离函数的查询或子查询必须 采用特定形式:距离函数必须是唯一的 ORDER BY 键, 并指定上限

有关限制的详细列表,请参阅 近似距离函数参考页面

示例

如需搜索最接近 [1.0, 2.0, 3.0] 的 100 个向量,请执行以下操作:

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

如果嵌入列可为 null:

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

最佳做法

请遵循以下最佳实践来优化向量索引并改进查询 结果。

调整矢量搜索选项

最佳矢量搜索值取决于应用场景,即矢量 以及查询矢量。您可能需要执行迭代调整, 为您的特定工作负载找到最佳值。

在选择适当的值时,请遵循以下有用的准则:

  • tree_depth(树级别):如果要编入索引的表少于 10 个 百万行,使用的 tree_depth2。否则,tree_depth3 支持的表格数量上限约为 100 亿行。

  • num_leaves:使用数据集中行数的平方根。答 值越大,向量索引的构建时间就越长。避免设置 num_leavestable_row_count/1000大,因为这会导致叶子过小 性能不佳。

  • num_leaves_to_search:此选项指定索引的叶节点数量 。增加num_leaves_to_search可以提高召回率, 延迟时间和费用增加我们建议使用占总数的 1% 的数字 CREATE VECTOR INDEX 语句中定义为值的叶数 价格为 num_leaves_to_search。如果您使用过滤条件子句,请将 以扩大搜索范围。

如果实现了可接受的召回率,但查询费用过高, 导致 QPS 上限较低,请尝试按以下方法提高 num_leaves 步骤:

  1. num_leaves 设置为其原始值的一定倍数(例如, 2 * sqrt(table_row_count)).
  2. num_leaves_to_search 设置为其原始值的倍数 k。
  3. 尝试减少 num_leaves_to_search 以降低费用和提升 QPS 同时保持召回率。

提高回想度

召回率恶化有多种可能性,包括:

  • num_leaves_to_search 太小:您可能会发现 为某些查询向量查找最近邻,因此增加 num_leaves_to_search搜索更多叶子有助于提高回想度。近期对话 查询可能已转而包含更多此类具有挑战性的向量。

  • 向量索引需要重新构建:向量索引的树结构为 在创建数据集时针对数据集进行优化,此后将处于静态。 因此,如果在创建数据集后添加明显不同的向量, 初始向量索引,那么树结构可能就不是最佳的,从而导致 回想度较低。

重新构建向量索引

要在不停机的情况下重新构建矢量索引,请执行以下操作:

  1. 在与当前向量相同的嵌入列上创建新的向量索引 索引,并根据需要更新参数(例如 OPTIONS)。
  2. 索引创建完成后,更改 FORCE_INDEX 提示 指向新索引,以更新矢量搜索查询。这样可以确保 查询将使用新的矢量索引。(你可能还需要重新调优 num_leaves_to_search)。
  3. 删除过时的向量索引。

后续步骤