语义搜索和RAG应用的流行让向量检索成为后端服务的标配能力。提到向量数据库,很多人会想到Pinecone、Milvus这类专用产品,但如果你的团队已经在用OpenSearch做日志分析或全文检索,其实完全不需要额外引入新组件——OpenSearch自带的k-NN插件就提供了成熟的向量检索能力,支持HNSW、IVF等主流索引算法,单分片可承载百万级向量。本文以Node.js为开发语言,从零开始讲解如何接入OpenSearch的向量插件,覆盖建索引、写数据、查向量三个核心环节。

一、准备工作:安装OpenSearch与k-NN插件
OpenSearch的官方发行版默认已经打包了k-NN插件,无需单独安装。如果是Docker部署,直接拉取官方镜像即可,一条命令就能跑起来:
docker pull opensearchproject/opensearch:latest docker run -d -p 9200:9200 -p 9600:9600 \ -e "discovery.type=single-node" \ -e "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" \ -e "DISABLE_SECURITY_PLUGIN=true" \ opensearchproject/opensearch:latest
这里设置了DISABLE_SECURITY_PLUGIN=true来关闭安全认证,方便本地开发调试。生产环境务必开启HTTPS和认证,否则任何人都能读写你的数据。容器启动后,访问http://127.0.0.1:9200能看到集群信息就说明服务正常。
验证插件是否可用也很简单,请求_plugins/_knn接口即可。确认无误后,Node.js侧推荐使用官方客户端@opensearch-project/opensearch,它对向量检索有完整的类型支持:
npm install @opensearch-project/opensearch
注意客户端与服务端的大版本要匹配,2.x版本的客户端配合2.x版本的OpenSearch,否则可能出现API不兼容的问题。
二、创建带向量字段的索引
向量检索的第一步是定义一个包含knn_vector类型字段的索引。下面这段代码演示了如何创建一个存储文档及其embedding的索引:
const { Client } = require('@opensearch-project/opensearch');
const client = new Client({
node: 'http://127.0.0.1:9200',
});
async function createVectorIndex() {
const indexName = 'products';
await client.indices.create({
index: indexName,
body: {
settings: {
index: {
knn: true, // 开启k-NN功能
},
},
mappings: {
properties: {
title: { type: 'text' },
category: { type: 'keyword' },
embedding: {
type: 'knn_vector',
dimension: 768,
method: {
name: 'hnsw',
space_type: 'cosinesimil',
engine: 'nmslib',
parameters: {
ef_construction: 128,
m: 24,
},
},
},
},
},
},
});
console.log('索引创建成功');
}
createVectorIndex().catch(console.error);
几个关键参数值得展开说明。dimension必须与你使用的embedding模型输出维度严格一致,比如用bge-base模型就是768维,用OpenAI的text-embedding-3-small则是1536维,维度不匹配写入时会直接报错。
space_type决定了相似度的度量方式,常用的有cosinesimil(余弦相似度)、l2(欧氏距离)和innerproduct(内积)。文本语义搜索场景绝大多数情况下选余弦相似度,因为它对向量的模长不敏感,只关注方向差异。需要注意的是,OpenSearch 2.16之前的版本要求余弦相似度必须先对向量归一化,新版本已经自动处理了。
ef_construction和m是HNSW算法的核心参数。简单说,m控制图中每个节点的连接数,ef_construction控制建图时候选队列的长度。这两个值越大,检索精度越高,但索引体积和构建时间也会增加。一般m取16到48之间,ef_construction取100到200之间,可以从m=24、ef_construction=128起步,再根据召回率测试结果调整。
三、写入向量数据与执行检索
索引建好后就可以写入数据了。实际项目中向量通常由独立的embedding服务生成,这里为了演示直接用随机向量代替:
async function bulkInsert() {
const docs = [];
const products = [
{ title: '无线蓝牙耳机', category: '数码' },
{ title: '机械键盘', category: '数码' },
{ title: '保温杯', category: '家居' },
];
for (const p of products) {
docs.push({ index: { _index: 'products' } });
docs.push({
title: p.title,
category: p.category,
embedding: Array.from({ length: 768 }, () => Math.random()),
});
}
const resp = await client.bulk({ body: docs });
if (resp.body.errors) {
console.error('部分文档写入失败', resp.body.items);
} else {
console.log('批量写入完成');
}
}
bulkInsert().catch(console.error);
数据量大的情况下强烈建议用bulk接口批量写入,单批控制在几百到几千条比较合适,比逐条index快一个数量级以上。写入后注意等待索引刷新,或者手动调用refresh,否则刚写入的数据可能搜不到。
检索环节使用k-NN查询,传入查询向量并指定返回的近邻数量k:
async function vectorSearch(queryVector, k = 5) {
const resp = await client.search({
index: 'products',
body: {
size: k,
query: {
knn: {
embedding: {
vector: queryVector,
k: k,
},
},
},
},
});
return resp.body.hits.hits.map((hit) => ({
id: hit._id,
score: hit._score,
title: hit._source.title,
}));
}
const qv = Array.from({ length: 768 }, () => Math.random());
vectorSearch(qv).then((results) => console.log(results));
返回结果中的_score就是相似度得分。如果只是想初步验证功能,还可以加一个method_parameters里的ef_search参数来权衡查询精度和速度,这个值默认是512,线上可以适当调小换取更低延迟。
四、进阶技巧:过滤查询与性能优化
真实业务中很少做纯向量检索,通常要结合业务条件过滤,比如只在某个类目下搜相似商品。k-NN查询支持filter参数,它会在向量检索阶段就应用过滤条件,效率比后置过滤高得多:
const resp = await client.search({
index: 'products',
body: {
size: 5,
query: {
knn: {
embedding: {
vector: qv,
k: 5,
filter: {
term: { category: '数码' },
},
},
},
},
},
});
性能方面有几点经验可以参考。第一,控制单个分片的向量数量,官方建议每个分片不超过一千万级向量,数据量大时通过增加分片数横向扩展。第二,向量字段是不更新的,修改文档中的向量等于删除重建,业务上要避免频繁改写向量。第三,Node.js侧做好连接复用,全局维护一个client实例,不要每次请求都新建,否则握手开销会明显拖慢接口响应。
如果对召回率要求极高,可以配置两种索引方式做混合召回:先走精确检索(exact search)保证效果,数据规模上来后再切换到近似检索。OpenSearch还支持Lucene引擎的向量字段,内存占用更低,适合资源紧张的中小规模场景,只需把engine参数改成lucene即可。
总结
用OpenSearch的k-NN插件在Node.js中实现向量检索,整个链路并不复杂:创建索引时声明knn_vector字段并选好算法参数,写入时批量灌入向量,查询时用k-NN查询获取近邻结果。它最大的优势在于和现有的全文检索能力天然融合,你可以轻松实现向量召回加关键词排序的混合搜索,这是很多专用向量数据库反而不具备的能力。对于已经有OpenSearch基础设施的团队来说,这是落地语义搜索最省事的路径。
OpenSearchk-NN向量检索Node.js修改时间:2026-09-08 16:31:21