Timescale Cloud:性能、扩展、企业级

自托管产品

MST

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

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

数据库中的向量使用 vector 列存储在常规 PostgreSQL 表中。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。还有用于欧几里得距离和负内积的“操作”类

距离类型查询运算符索引操作类
余弦 / 角度<=>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 子句中使用与索引创建期间所用操作类匹配的距离运算符(<=><-><#>)。标准查询为

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。还有用于欧几里得距离和负内积的“操作”类

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

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

Pgvector ivfflat 有一个应该设置的 lists 索引参数。请参见下一节。

Pgvector 有一个 lists 参数,应按如下方式设置:对于行数少于一百万的数据集,使用 lists = 行数 / 1000。对于行数多于一百万的数据集,使用 lists = sqrt(行数)。通常建议至少有 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 子句中使用与索引创建期间所用操作类匹配的距离运算符(<=><-><#>)。标准查询为

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

关键词

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