如果尚未安装,请在您的 Timescale 数据库上安装 vectorvectorscale 扩展。

CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS vectorscale;

数据库内部的向量存储在常规 PostgreSQL 表中,使用 vector 列。vector 列类型由 pgvector 扩展提供。存储向量的常见方法是与它们嵌入的数据一起存储。例如,要存储文档的嵌入,常见的表结构是

CREATE TABLE IF NOT EXISTS document_embedding (
id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
document_id BIGINT FOREIGN KEY(document.id)
metadata JSONB,
contents TEXT,
embedding VECTOR(1536)
)

此表包含一个主键,一个指向文档表的外键,一些元数据,正在嵌入的文本(在 contents 列中)和嵌入向量。

您可能会问,为什么不只是在文档表中添加一个嵌入列?答案是,嵌入可以编码的文本长度有限制,因此完整文档及其嵌入之间需要存在一对多的关系。

上面的表格只是一个说明,完全可以有一个没有外键和/或没有元数据列的表。重要的是要有一列包含正在嵌入的数据,并在同一行中包含向量,这样您就可以为给定的相似性搜索查询返回原始数据

向量类型可以指定可选的维度数(在上面的示例中为 1,538)。如果指定,它将强制约束列中的所有向量都具有该维度数。也可以使用纯 VECTOR 列(不指定维度数),并允许可变数量的维度。

规范查询是

SELECT *
FROM document_embedding
ORDER BY embedding <=> $1
LIMIT 10

它返回距离最小的 10 行。此处使用的距离函数是余弦距离(通过使用 <=> 运算符指定)。还有其他距离函数可用,请参阅讨论

可用的距离类型及其运算符是

距离类型运算符
余弦/角度<=>
欧几里得<->
负内积<#>
注意

如果您正在使用索引,则需要确保索引创建中使用的距离函数与查询期间使用的距离函数相同(见下文)。这很重要,因为如果您使用一个距离函数创建索引,但使用另一个距离函数进行查询,则您的索引不能用于加速查询。

索引有助于加速基本形式的相似性查询

SELECT *
FROM document_embedding
ORDER BY embedding <=> $1
LIMIT 10

关键部分是 ORDER BY 包含针对常量或伪常量的距离度量。

请注意,如果执行没有索引的查询,您始终会获得精确的结果,但查询速度很慢(它必须读取您为每个查询存储的所有数据)。使用索引,您的查询速度会快一个数量级,但结果是近似的(因为没有已知的精确索引技术,有关详细信息,请参阅此处)。

尽管如此,还是有出色的近似算法。Timescale 平台上提供 3 种不同的索引算法:StreamingDiskANN、HNSW 和 ivfflat。以下是这些算法之间的权衡

算法构建速度查询速度更新后是否需要重建
StreamingDiskANN快速最快
HNSW快速快速
ivfflat最快最慢

您可以在博客中查看基准测试

对于大多数用例,建议使用 StreamingDiskANN 索引。

这些索引中的每一个都有一组构建时选项,用于控制创建索引时的速度/精度权衡,以及一个额外的查询时选项,用于控制特定查询期间的精度。

您可以在下面查看每个索引的详细信息。

StreamingDiskANN 索引是一种基于图的算法,其灵感来自 DiskANN 算法。您可以在我们如何使 PostgreSQL 与 Pinecone 一样快地处理向量数据中阅读更多相关信息。

要在表 document_embedding 上创建一个名为 document_embedding_idx 的索引,该表具有一个名为 embedding 的向量列,并使用余弦距离度量,请运行

CREATE INDEX document_embedding_cos_idx ON document_embedding
USING diskann (embedding vector_cosine_ops);

由于此索引使用余弦距离,因此您应该在查询中使用 <=> 运算符。StreamingDiskANN 也支持 L2 距离

CREATE INDEX document_embedding_l2_idx ON document_embedding
USING diskann (embedding vector_l2_ops);

对于 L2 距离,请在查询中使用 <-> 运算符。

这些示例使用未列出的所有参数的智能默认值创建索引。这些应该是大多数情况下的正确值。但是,如果您想深入了解,请参阅以下可用参数。

这些参数可以在创建索引时设置。

参数名称描述默认值
storage_layoutmemory_optimized,它使用 SBQ 来压缩向量数据,或者 plain,它存储未压缩的数据memory_optimized
num_neighbors设置每个节点的最大邻居数。较高的值会提高精度,但会使图遍历速度变慢。50
search_list_size这是构造期间使用的贪婪搜索算法中使用的 S 参数。较高的值可以提高图形质量,但会降低索引构建速度。100
max_alpha是算法中的 alpha 参数。较高的值可以提高图形质量,但会降低索引构建速度。1.2
num_dimensions要索引的维度数。默认情况下,索引所有维度。但是您也可以索引较少的维度,以利用 Matryoshka 嵌入0(所有维度)
num_bits_per_dimension使用 SBQ 时用于编码每个维度的位数对于小于 900 维,为 2,否则为 1

以下是如何设置 num_neighbors 参数的示例

CREATE INDEX document_embedding_idx ON document_embedding
USING diskann (embedding) WITH(num_neighbors=50);

您还可以设置两个参数来控制查询时的精度与查询速度之间的权衡。我们建议调整 diskann.query_rescore 以微调精度。

参数名称描述默认值
diskann.query_search_list_size在图搜索期间考虑的其他候选对象的数量。100
diskann.query_rescore重新评分的元素数量(0 表示禁用重新评分)50

您可以使用 SET 在执行查询之前设置值。例如

SET diskann.query_rescore = 400;

请注意,SET 命令 从执行点开始应用于整个会话(数据库连接)。您可以使用事务本地变体 LOCAL,它将在事务结束后重置

BEGIN;
SET LOCAL diskann.query_search_list_size= 10;
SELECT * FROM document_embedding ORDER BY embedding <=> $1 LIMIT 10
COMMIT;

您需要在 ORDER BY 子句中使用余弦距离嵌入度量 (<=>)。规范查询将是

SELECT *
FROM document_embedding
ORDER BY embedding <=> $1
LIMIT 10

Pgvector 提供了一种基于图的索引算法,该算法基于流行的 HNSW 算法

要在表 document_embedding 上创建一个名为 document_embedding_idx 的索引,该表具有一个名为 embedding 的向量列,请运行

CREATE INDEX document_embedding_idx ON document_embedding
USING hnsw(embedding vector_cosine_ops);

由于 vector_cosine_ops,此命令为余弦距离查询创建索引。还有用于欧几里得距离和负内积的“ops”类

距离类型查询运算符索引 ops 类
余弦/角度<=>vector_cosine_ops
欧几里得/ L2<->vector_ip_ops
负内积<#>vector_l2_ops

Pgvector HNSW 还包括几个索引构建时和查询时参数。

这些参数可以在索引构建时设置

参数名称描述默认值
m表示每层的最大连接数。将这些连接视为为每个节点在图构建期间创建的边。增加 m 会提高精度,但也会增加索引构建时间和大小。16
ef_construction表示用于构造图的动态候选列表的大小。它会影响索引质量和构建速度之间的权衡。增加 ef_construction 可以在延长索引构建时间的情况下实现更准确的搜索结果。64

以下是如何设置 m 参数的示例

CREATE INDEX document_embedding_idx ON document_embedding
USING hnsw(embedding vector_cosine_ops) WITH (m = 20);

您还可以设置一个参数来控制查询时的精度与查询速度之间的权衡。该参数称为 hnsw.ef_search。此参数指定搜索期间使用的动态候选列表的大小。默认为 40。较高的值可以提高查询精度,同时使查询速度变慢。

您可以通过运行以下命令来设置值

SET hnsw.ef_search = 100;

在执行查询之前,请注意 SET 命令 从执行点开始应用于整个会话(数据库连接)。您可以使用事务本地变体 LOCAL

BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT * FROM document_embedding ORDER BY embedding <=> $1 LIMIT 10
COMMIT;

您需要在 ORDER BY 子句中使用与您在索引创建期间使用的 ops 类匹配的距离运算符(<=><-><#>)。规范查询将是

SELECT *
FROM document_embedding
ORDER BY embedding <=> $1
LIMIT 10

Pgvector 提供了一种基于聚类的索引算法。博客文章详细描述了它的工作原理。它提供最快的索引构建速度,但查询速度在所有索引算法中最慢。

要在表 document_embedding 上创建一个名为 document_embedding_idx 的索引,该表具有一个名为 embedding 的向量列,请运行

CREATE INDEX document_embedding_idx ON document_embedding
USING ivfflat(embedding vector_cosine_ops) WITH (lists = 100);

由于 vector_cosine_ops,此命令为余弦距离查询创建索引。还有用于欧几里得距离和负内积的“ops”类

距离类型查询运算符索引 ops 类
余弦/角度<=>vector_cosine_ops
欧几里得/ L2<->vector_ip_ops
负内积<#>vector_l2_ops

注意:ivfflat 永远不应在空表上创建,因为它需要聚类数据,而这仅在首次创建索引时发生,而不是在新行插入或修改时发生。此外,如果您的表经历大量修改,则需要偶尔重建此索引以保持良好的精度。有关详细信息,请参阅博客文章

Pgvector ivfflat 有一个 lists 索引参数,应进行设置。请参阅下一节。

Pgvector 有一个 lists 参数,应按如下方式设置:对于少于一百万行的数据集,请使用 lists = rows / 1000。对于超过一百万行的数据集,请使用 lists = sqrt(rows)。通常建议至少有 10 个聚类。

您可以使用以下代码来简化创建 ivfflat 索引

def create_ivfflat_index(conn, table_name, column_name, query_operator="<=>"):
index_method = "invalid"
if query_operator == "<->":
index_method = "vector_l2_ops"
elif query_operator == "<#>":
index_method = "vector_ip_ops"
elif query_operator == "<=>":
index_method = "vector_cosine_ops"
else:
raise ValueError(f"unrecognized operator {query_operator}")
with conn.cursor() as cur:
cur.execute(f"SELECT COUNT(*) as cnt FROM {table_name};")
num_records = cur.fetchone()[0]
num_lists = num_records / 1000
if num_lists < 10:
num_lists = 10
if num_records > 1000000:
num_lists = math.sqrt(num_records)
cur.execute(f'CREATE INDEX ON {table_name} USING ivfflat ({column_name} {index_method}) WITH (lists = {num_lists});')
conn.commit()

您还可以设置一个参数来控制查询时的精度与查询速度之间的权衡。该参数称为 ivfflat.probes。此参数指定查询期间搜索的聚类数。建议将此参数设置为 sqrt(lists),其中 lists 是上面在索引创建期间使用的参数。较高的值可以提高查询精度,同时使查询速度变慢。

您可以通过运行以下命令来设置值

SET ivfflat.probes = 100;

在执行查询之前,请注意 SET 命令 从执行点开始应用于整个会话(数据库连接)。您可以使用事务本地变体 LOCAL

BEGIN;
SET LOCAL ivfflat.probes = 100;
SELECT * FROM document_embedding ORDER BY embedding <=> $1 LIMIT 10
COMMIT;

您需要在 ORDER BY 子句中使用与您在索引创建期间使用的 ops 类匹配的距离运算符(<=><-><#>)。规范查询将是

SELECT *
FROM document_embedding
ORDER BY embedding <=> $1
LIMIT 10

关键词

在此页面上发现问题?报告问题 或 在 GitHub 上编辑此页