跳到主要内容

CockroachDB

CockroachDB 是一个分布式 SQL 数据库, 兼容 PostgreSQL 协议。自 v24.2 起内置原生 VECTOR 列类型,自 v25.2 起提供名为 C-SPANN 的分布式近似最近邻 索引。langchain4j-community-cockroachdb 模块将两者与 LangChain4j 集成,提供:

  • 向量 EmbeddingStore<TextSegment>CockroachDbEmbeddingStore
  • ChatMemoryStoreCockroachDbChatMemoryStore

该 Java 模块在存在对应 Java 等价实现的地方,镜像官方 Python langchain-cockroachdb 库的功能集。

版本要求

功能最低 CockroachDB 版本
VECTOR(n) 列类型v24.2
CREATE VECTOR INDEX(C-SPANN)v25.2
通过 ttl_expiration_expression 实现行级 TTLv23.1

在 CockroachDB v25.2 上,向量索引由集群设置控制。在使用 CSpannIndex 创建 store 之前,请先在每个集群启用一次:

SET CLUSTER SETTING feature.vector_index.enabled = true;

Maven 依赖

备注

由于 CockroachDB 支持属于 langchain4j-community,将从版本 1.18.1-beta28 或更高版本起可用。

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-cockroachdb</artifactId>
<version>1.18.1-beta28</version>
</dependency>

如果导入 Community BOM,则可省略版本号。

API

该模块公开四个公共类:

CockroachDbEngine

封装 HikariCP DataSource 并处理连接池。可通过单独的 host/port/database/username/password 字段构建,通过完整 连接字符串构建(Python 风格的 cockroachdb:// scheme 会自动重写为 jdbc:postgresql://),或通过 CockroachDbEngine.from(dataSource) 使用已有的 DataSource

CockroachDbSchema

封装 embedding 表布局:表名和列名、向量 维度、距离度量、可选的多租户命名空间列、 所选向量索引策略,以及可选的生成式 tsvector 列,用于 未来的混合搜索。

CockroachDbEmbeddingStore

针对原生 CockroachDB VECTOR 列实现 LangChain4j 的 EmbeddingStore<TextSegment>。支持批量插入、JSONB 元数据 过滤、按 id / 按 Filter / 批量删除、可选的命名空间作用域, 以及可选的每查询 vector_search_beam_size 调优(用于 C-SPANN)。

CockroachDbChatMemoryStore

实现 LangChain4j 的 ChatMemoryStore。将序列化的聊天消息 持久化到 JSONB 列中,按显式插入索引排序,并支持可选的 行级 TTL。

连接

CockroachDbEngine 封装了 HikariDataSource。你可以从 连接字符串或单独字段构建。

import dev.langchain4j.community.store.embedding.cockroachdb.CockroachDbEngine;

CockroachDbEngine engine = CockroachDbEngine.builder()
.host("localhost")
.port(26257)
.database("defaultdb")
.username("root")
.password("")
.sslMode("disable")
.build();

构建器也接受完整连接字符串。Python 风格的 cockroachdb:// scheme 会自动重写为 jdbc:postgresql://, 因此你可以粘贴与 Python 库相同的 URL:

CockroachDbEngine engine = CockroachDbEngine.fromConnectionString(
"cockroachdb://root@localhost:26257/defaultdb?sslmode=disable");

如果你已有 DataSource,请使用 CockroachDbEngine.from(dataSource)

向量存储

最小的向量 store 使用顺序扫描(NoIndex),适合 小数据集和测试:

import dev.langchain4j.community.store.embedding.cockroachdb.CockroachDbEmbeddingStore;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.allminilml6v2q.AllMiniLmL6V2QuantizedEmbeddingModel;

EmbeddingModel model = new AllMiniLmL6V2QuantizedEmbeddingModel();

CockroachDbEmbeddingStore store = CockroachDbEmbeddingStore.builder()
.engine(engine)
.dimension(model.dimension())
.tableName("embeddings")
.build();

TextSegment segment = TextSegment.from("Cockroaches are surprisingly resilient.");
Embedding embedding = model.embed(segment).content();
store.add(embedding, segment);

对于 CockroachDB v25.2+ 上的生产工作负载,添加 C-SPANN 向量索引:

import dev.langchain4j.community.store.embedding.cockroachdb.index.CSpannIndex;

CockroachDbEmbeddingStore store = CockroachDbEmbeddingStore.builder()
.engine(engine)
.dimension(model.dimension())
.vectorIndex(CSpannIndex.builder()
.minPartitionSize(16)
.maxPartitionSize(128)
.build())
.build();

为索引生成的 DDL 为:

CREATE VECTOR INDEX IF NOT EXISTS embeddings_embedding_vector_idx
ON public.embeddings (embedding)
WITH (min_partition_size = 16, max_partition_size = 128);

C-SPANN 根据查询运算符选择距离函数(<=> 表示余弦, <-> 表示 L2,<#> 表示内积),因此 MetricType 在查询 时于 store 上选择,而不绑定到索引。

搜索

EmbeddingSearchRequest 的用法与其他任何 LangChain4j store 相同:

import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingSearchResult;

EmbeddingSearchResult<TextSegment> result = store.search(
EmbeddingSearchRequest.builder()
.queryEmbedding(model.embed("resilience").content())
.maxResults(5)
.minScore(0.6)
.build());

result.matches().forEach(m ->
System.out.printf("%s (%.3f) %s%n", m.embeddingId(), m.score(), m.embedded().text()));

在查询时调优 C-SPANN

CockroachDB 暴露会话变量 vector_search_beam_size,用于 控制召回率/延迟的权衡。在 store 构建器上设置它,以便用 SET LOCAL 在事务中限定该设置的作用域,从而包装每次搜索:

CockroachDbEmbeddingStore store = CockroachDbEmbeddingStore.builder()
.engine(engine)
.dimension(model.dimension())
.vectorIndex(CSpannIndex.builder().build())
.searchBeamSize(32)
.build();

更高的值以延迟换取召回率。如果留空不设置,默认 beam 大小 由 CockroachDB 决定。

元数据过滤

元数据存储在 JSONB 列中,并在查询时使用 LangChain4j Filter 表达式进行过滤:

import dev.langchain4j.store.embedding.filter.MetadataFilterBuilder;

EmbeddingSearchResult<TextSegment> result = store.search(
EmbeddingSearchRequest.builder()
.queryEmbedding(query)
.maxResults(10)
.filter(MetadataFilterBuilder.metadataKey("category").isEqualTo("biology")
.and(MetadataFilterBuilder.metadataKey("year").isGreaterThan(2020)))
.build());

比较过滤(>>=<<=)将 JSONB 值转换为 numeric。 字符串相等性比较 JSON 文本。过滤键只能包含 字母数字字符、点、下划线或连字符。

使用命名空间列实现多租户

要按租户限定行范围,请向 schema 添加 namespaceColumn,并在每个 store 实例上配置命名空间值。该列会作为前缀添加到 C-SPANN 索引中,使按租户的查询保持快速:

CockroachDbEmbeddingStore tenantA = CockroachDbEmbeddingStore.builder()
.engine(engine)
.dimension(model.dimension())
.namespaceColumn("tenant_id")
.namespace("acme")
.vectorIndex(CSpannIndex.builder().build())
.build();

生成的索引变为 CREATE VECTOR INDEX ... ON embeddings (tenant_id, embedding), 通过此 store 执行的每次读/写都会过滤为 tenant_id = 'acme'

可选全文列

如果打算稍后将向量搜索与全文搜索结合,可在 创建表时启用生成式 tsvector 列。同时会创建 GIN 索引:

CockroachDbEmbeddingStore store = CockroachDbEmbeddingStore.builder()
.engine(engine)
.dimension(model.dimension())
.createTsvectorColumn(true)
.build();

混合(向量 + FTS)查询执行尚未实现;创建该列是为了 供应用代码或未来版本使用。

聊天记忆

CockroachDbChatMemoryStore 实现 ChatMemoryStore,并将 序列化的聊天消息按插入时间排序持久化到 JSONB 列中:

import dev.langchain4j.community.store.memory.chat.cockroachdb.CockroachDbChatMemoryStore;

CockroachDbChatMemoryStore memory = CockroachDbChatMemoryStore.builder()
.engine(engine)
.tableName("chat_memory")
.build();

schema 为:

CREATE TABLE chat_memory (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id TEXT NOT NULL,
message JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX chat_memory_session_idx ON chat_memory (session_id, created_at);

updateMessages 在事务内替换整个会话,因此 部分写入对读者不可见。

行级 TTL

CockroachDB 可以自动使行过期。传入 ttl 时长以在 聊天记忆表上启用 行级 TTL

import java.time.Duration;

CockroachDbChatMemoryStore memory = CockroachDbChatMemoryStore.builder()
.engine(engine)
.tableName("chat_memory")
.ttl(Duration.ofDays(7))
.ttlJobCron("@daily")
.build();

schema 设置会发出:

ALTER TABLE chat_memory SET (
ttl_expiration_expression = $$(created_at + '7 days')$$,
ttl_job_cron = '@daily'
);

要在现有表上禁用 TTL:

memory.disableTtl();

重试

当事务在默认 SERIALIZABLE 隔离下必须重试时,CockroachDB 返回 SQLSTATE 40001。store 将每个工作单元包装在 带指数退避和抖动的重试循环中(默认 5 次尝试, 从 100 ms 开始,最多加倍到 10 秒)。无需额外 配置。

连接字符串格式

以下形式均可被 CockroachDbEngine.fromConnectionString 接受:

形式示例
Python 风格cockroachdb://root@localhost:26257/defaultdb?sslmode=disable
psycopg 风格cockroachdb+psycopg://user:pw@host:26257/db
libpq 风格postgresql://user@host:26257/db
JDBC 风格jdbc:postgresql://localhost:26257/defaultdb

对于 CockroachDB Cloud,请使用集群控制台中的连接字符串, 通常为:

cockroachdb://USER:PASSWORD@HOST:26257/DATABASE?sslmode=verify-full

参数摘要

CockroachDbEngine 参数

参数说明默认值必需/可选
hostCockroachDB 服务器主机名localhost必需(若无 connectionString
portCockroachDB 服务器端口号26257必需(若无 connectionString
database要连接的数据库defaultdb必需(若无 connectionString
username认证用户名root必需
password认证密码""(空)可选
schema默认 schema 名称public可选
sslModeSSL 模式(disablerequireverify-full 等)disable可选
maxPoolSizeHikariCP 最大池大小10可选
minPoolSize最小空闲连接数5可选
connectionTimeoutMs连接超时(毫秒)10000可选
idleTimeoutMs空闲超时(毫秒)300000可选
maxLifetimeMs最大连接生命周期(毫秒)3600000可选
connectionString完整 URL;设置时覆盖单独的 host/port/dbnull可选

CockroachDbEmbeddingStore 参数

参数说明默认值必需/可选
engineCockroachDbEngine 实例必需
dimensionEmbedding 向量维度必需
tableNameEmbedding 表名embeddings可选
schemaName数据库 schema 名称public可选
metricType距离度量:COSINEEUCLIDEANDOT_PRODUCTCOSINE可选
vectorIndexCSpannIndexNoIndexNoIndex(顺序扫描)可选
namespaceColumn多租户的租户列名null(禁用)可选
namespace每次读和写应用的租户值null可选,需要 namespaceColumn
searchBeamSize每查询的 vector_search_beam_size 会话变量null(CockroachDB 默认)可选
createTableIfNotExists构建时创建表true可选
createTsvectorColumn添加生成式 tsvector 列 + GIN 索引false可选

CSpannIndex 参数(CockroachDB v25.2+)

参数说明默认值必需/可选
name自定义索引名{table}_{column}_vector_idx可选
minPartitionSize最小分区大小(通过 WITH 发出)CockroachDB 默认可选
maxPartitionSize最大分区大小(通过 WITH 发出)CockroachDB 默认可选

CockroachDbChatMemoryStore 参数

参数说明默认值必需/可选
engineCockroachDbEngine 实例必需
tableName聊天历史表名message_store可选
schemaName数据库 schema 名称public可选
ttl行级 TTL 时长;设置时启用 CockroachDB TTLnull(禁用)可选
ttlJobCronTTL 任务计划@daily可选,需要 ttl
createTableIfNotExists构建时创建表true可选

示例

一个最小的端到端 RAG 演示:启动 CockroachDB Testcontainer, 索引两个文本片段,并运行相似度搜索:

import dev.langchain4j.community.store.embedding.cockroachdb.CockroachDbEmbeddingStore;
import dev.langchain4j.community.store.embedding.cockroachdb.CockroachDbEngine;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingStore;
import java.util.List;
import org.testcontainers.containers.CockroachContainer;

public class CockroachDbEmbeddingStoreExample {

public static void main(String[] args) {
try (CockroachContainer cockroach = new CockroachContainer("cockroachdb/cockroach:latest-v25.2")) {
cockroach.start();

CockroachDbEngine engine = CockroachDbEngine.builder()
.connectionString(cockroach.getJdbcUrl())
.username(cockroach.getUsername())
.password(cockroach.getPassword())
.build();

EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();

EmbeddingStore<TextSegment> embeddingStore = CockroachDbEmbeddingStore.builder()
.engine(engine)
.dimension(embeddingModel.dimension())
.tableName("demo_embeddings")
.build();

TextSegment segment1 = TextSegment.from("I like football.");
Embedding embedding1 = embeddingModel.embed(segment1).content();
embeddingStore.add(embedding1, segment1);

TextSegment segment2 = TextSegment.from("The weather is good today.");
Embedding embedding2 = embeddingModel.embed(segment2).content();
embeddingStore.add(embedding2, segment2);

Embedding queryEmbedding = embeddingModel.embed("What is your favourite sport?").content();
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(1)
.build();

List<EmbeddingMatch<TextSegment>> matches = embeddingStore.search(request).matches();
EmbeddingMatch<TextSegment> match = matches.get(0);

System.out.println(match.score()); // ~0.81
System.out.println(match.embedded().text()); // I like football.

engine.close();
}
}
}

该示例使用默认的顺序扫描索引,因此可在任何 CockroachDB v24.2 或更高版本上运行,无需额外集群设置。要在 v25.2 或更高版本上切换到 C-SPANN 分布式 ANN 索引,请在每个 集群启用一次功能标志,并通过 .vectorIndex(...)CSpannIndex.builder().build() 传给 store:

SET CLUSTER SETTING feature.vector_index.enabled = true;

更完整的可运行版本位于 langchain4j-examples/cockroachdb-example

已知限制

  • C-SPANN 向量索引需要 CockroachDB v25.2 或更高版本,并且必须启用 feature.vector_index.enabled 集群设置。
  • 向量值以文本形式发送,并使用 ?::vector 进行转换,因为 CockroachDB 的 pgwire 层不接受 VECTOR 类型的二进制格式。
  • 混合(向量 + 全文)查询执行尚未实现。可通过 createTsvectorColumn 创建 tsvector 列和 GIN 索引, 供应用代码或未来版本使用。
  • Python langchain-cockroachdb 库还提供 LangGraph checkpointer(CockroachDBSaverAsyncCockroachDBSaver)。 Java 等价实现位于第三方 langgraph4j 项目中,名为 langgraph4j-cockroachdb-saver。langgraph4j 的 checkpoint 契约没有异步 API,因此只提供同步的 CockroachDBSaver; JDK 21 或更高版本的调用者可以从虚拟线程调用它以实现 非阻塞并发。