- 后端
- 搜索引擎
【免费下载链接】elasticsearch-js
Official Elasticsearch client library for Node.js
本指南围绕 test/ingest/README.md 与其核心脚本 test/ingest/base64.mjs 展开,讲解如何对「dense_vector 浮点向量批量写入」进行基准测试:分别以原始 float32 数组与 base64 编码字符串两种形态灌入 2 万条 OpenAI 风格向量文档,量化Serializer.encodeFloat32Vector在真实 bulk 场景下的收益。读完本文,你将掌握该测试的运行方法、脚本内部流程、dense_vector 映射要点,以及将 base64 向量编码落地到生产代码的正确姿势。
一、测试定位:它在验证什么
elasticsearch-js 官方仓库的test/ingest目录下只有两个文件——一份仅 7 行的 README 和一个约 170 行的.mjs脚本。README 的全部内容即三条运行步骤:
- 安装依赖:
npm install - 设置环境变量
ES_URL、ES_USERNAME和ES_PASSWORD - 运行脚本:
node test/ingest/base64.mjs
这个看似「简单得不像测试」的入口,背后是一个完整的向量写入压测实验:它从 Elasticsearch 官方 rally 数据集下载 1000 条真实向量文档,通过无限循环放大到 2 万条,分别以两种载荷形态(原始 float32 数组 vs base64 字符串)执行 bulk 写入,对比不同分块大小下的耗时均值。
从 index.js 的导出结构可以看到,Serializer类来源于@elastic/transport(当前仓库package.json声明依赖"@elastic/transport": "^9.4.0"),base64 向量编码正是该序列化器的能力之一。因此这个基准测试实质上是围绕官方推荐的「向量高效传输」方案做量化验证。
二、运行前置条件与依赖说明
运行前需要满足三件事,缺一不可:
| 条件 | 说明 |
|---|---|
| 可访问的 Elasticsearch 实例 | 需支持 dense_vector 与 base64 向量摄入(详见下文版本说明) |
| 环境变量 | ES_URL(节点地址)、ES_USERNAME、ES_PASSWORD(Basic 认证凭据) |
| 依赖安装 | 在仓库根目录执行npm install,脚本运行时会用到inly(bzip2 解压) |
客户端初始化代码位于 base64.mjs:
import { Client } from '../../index.js' import { Serializer } from '@elastic/transport' const client = new Client({ node: process.env.ES_URL, auth: { username: process.env.ES_USERNAME, password: process.env.ES_PASSWORD, }, compression: false })注意compression: false——基准测试刻意关闭 HTTP 压缩,以保证两种载荷的对比只反映编码方式本身带来的传输体积差异,不被 gzip 二次压缩干扰。这是性能实验设计中的一个关键细节。
三、脚本执行流程:五阶段拆解
base64.mjs从run()启动,整体分五个阶段:
- 清理残留索引:先调用
client.indices.delete({ index: 'b64-test', allow_no_indices: true }),防止上次中断运行遗留的半成品索引影响测试(base64.mjs)。 - 获取数据集:
fetchDataSet()从https://rally-tracks.elastic.co/openai_vector/open_ai_corpus-initial-indexing-1k.json.bz2下载约 1MB 的 bzip2 归档,解压得到 1000 条 NDJSON 向量文档;文件已存在则跳过下载与解压(base64.mjs)。 - 放大数据集:
loopDataSet()使用生成器无限循环遍历 1000 条数据,直到产出dataset_size = 20000条记录为止——用 1k 真实向量模拟 2 万条写入量,兼顾数据真实性与执行时长(base64.mjs)。 - 索引创建与测量:对每个 chunkSize 分别执行 float32 与 base64 两组写入,各测 3 次取平均(详见第六节)。
- 输出 JSON 结果:控制台打印结构化的测量数组(base64.mjs)。
四、dense_vector 索引映射详解
每次测试都会先创建索引b64-test,映射定义在 base64.mjs:
const indexSettings = { index: indexName, wait_for_active_shards: 'all', mappings: { properties: { docid: { type: 'keyword' }, emb: { dims: 1536, index: true, index_options: { type: 'flat' }, similarity: 'cosine', type: 'dense_vector' }, text: { fields: { keyword: { ignore_above: 256, type: 'keyword' } }, type: 'text' }, title: { type: 'text' } } } }映射要点解读:
wait_for_active_shards: 'all':创建索引时等待所有分片就绪,确保写入发生在完整可用的集群状态上,避免分片分配抖动污染耗时数据。emb字段:dense_vector类型,dims: 1536与 OpenAI 文本嵌入向量的维度一致(数据源即 openai 语料);index: true表示该向量参与 ANN 检索;index_options: { type: 'flat' }使用暴力精确检索而非 HNSW 近似索引;similarity: 'cosine'采用余弦相似度度量。text字段:标准text类型同时带keyword子字段,ignore_above: 256限制超长文本不写入 keyword 索引。- 每条文档在写入时通过
loopDataSet重复利用,docid关键字字段承载唯一标识。
向量维度是 base64 收益的关键变量:1536 维 float32 数组原始形态约 6KB(1536 × 4 字节),而 base64 编码后每 3 字节变 4 字符,净传输体积约为原始二进制数据经 base64 膨胀后的结果——这就是该测试要实测的传输开销差异。
五、base64 向量编码原理:encodeFloat32Vector 的底层行为
脚本核心在第 122 行:
if (base64) doc.emb = serializer.encodeFloat32Vector(doc.emb)Serializer实例在此处直接承担向量编码。结合 docs/reference/advanced-config.md 的 API 说明,@elastic/transport的Serializer提供两个专门方法:
encodeFloat32Vector(floats: number[]): string;— 将 float32 数组编码为 base64 字符串,用于高效向量摄入;decodeFloat32Vector(base64: string): number[];— 将 base64 字符串解码回 float32 数组。
编码规则为IEEE-754 单精度浮点、big-endian(大端)字节序,随后做 base64 编码。也就是说,服务端拿到的是紧凑的二进制载荷,客户端不再以 JSON 数组形式逐个传输数字。这在 1536 维向量场景下显著降低了请求体体积与 JSON 序列化开销。
版本前提:根据 docs/reference/bulk_examples.md 的明确注释,Elasticsearch 对 base64 编码 float32 数组的摄入支持自9.3版本起可用。若目标集群低于该版本,encodeFloat32Vector编码后的数据无法被正确解析。
六、bulk 写入分块与计时逻辑
index()函数(base64.mjs)实现单轮测试:
async function index (chunkSize, base64 = false) { const data = await fetchDataSet() const serializer = new Serializer() let chunk = [] await client.indices.create(indexSettings) await client.indices.refresh({ index: indexName }) const start = Date.now() for (const doc of loopDataSet(data)) { if (base64) doc.emb = serializer.encodeFloat32Vector(doc.emb) chunk.push(doc) if (chunk.length >= chunkSize) { const operations = chunk.flatMap(doc => [{ index: { _index: indexName } }, doc]) await client.bulk({ operations }) chunk = [] } } const duration = Date.now() - start await client.indices.delete({ index: indexName }) return duration }要点:
- 分块批量:
chunk.flatMap将每条文档展开为「动作元数据 + 文档体」两行结构,构成client.bulk({ operations })的载荷——这正是官方 bulk 辅助 API 推荐的 operations 格式。 - 计时段:
Date.now()起止于全部 bulk 写入之间,索引创建与清理不计入耗时。 - 分块大小变量:外层遍历
[100, 250, 500, 1000]四档 chunkSize,每档对 float32 与 base64 各执行 3 次完整写入,再取算术平均(base64.mjs):
for (const _ of [1, 2, 3]) { float32Duration.push(await index(chunk_size)) base64Duration.push(await index(chunk_size, true)) } measurement.float32 = { duration: float32Duration.reduce((a, b) => a + b, 0) / float32Duration.length } measurement.base64 = { duration: base64Duration.reduce((a, b) => a + b, 0) / base64Duration.length }3 次取均值能平滑网络抖动与 GC 停顿;每轮结束后删除索引,保证各测量之间数据规模一致。最终输出形如:
[ { "dataset_size": 20000, "chunk_size": 100, "float32": { "duration": 12345.6 }, "base64": { "duration": 9876.5 } } ]七、从基准到生产:bulk 写入 base64 向量的标准姿势
基准测试之外,仓库在 docs/reference/bulk_examples.md 提供了一份可直接落地的生产示例,核心差异在于通过client.serializer直接访问编码器:
const { Client } = require('@elastic/elasticsearch') const client = new Client({ cloud: { id: '<cloud-id>' }, auth: { apiKey: 'base64EncodedKey' } }) async function run () { await client.indices.create({ index: 'my-vectors', mappings: { properties: { title: { type: 'text' }, embedding: { type: 'dense_vector', dims: 3 } } } }, { ignore: [400] }) const documents = [ { title: 'Document 1', embedding: [0.1, 0.2, 0.3] }, { title: 'Document 2', embedding: [0.4, 0.5, 0.6] }, { title: 'Document 3', embedding: [0.7, 0.8, 0.9] } ] const operations = documents.flatMap(doc => [ { index: { _index: 'my-vectors' } }, { title: doc.title, embedding: client.serializer.encodeFloat32Vector(doc.embedding) } ]) const bulkResponse = await client.bulk({ refresh: true, operations }) if (bulkResponse.errors) { console.log('Bulk ingestion had errors') } else { console.log(`Indexed ${documents.length} documents with encoded vectors`) } } run().catch(console.log)与基准脚本相比,生产示例有两点值得注意:一是用cloud.id+ API Key 代替node+ 用户名密码(basic-config.md 中两种认证方式均有介绍);二是client.serializer与new Serializer()均可调用编码方法,二者等价——Serializer作为可注入构造项也支持自定义继承,见 advanced-config.md 的MySerializer extends Serializer写法。
八、实验结论与使用边界
从脚本设计可以得出的明确结论有:
- 编码形态确实减少传输开销:base64 字符串代替 1536 维 JSON 数组,请求体显著变小,这正是
encodeFloat32Vector存在的意义(docs/reference/advanced-config.md 将其描述为 "efficient vector ingestion")。 - chunkSize 影响总耗时:脚本通过四档分块对比,可观察不同批量大小下序列化与网络往返的开销平衡点;具体数值取决于集群规格、网络与数据集,README 未给出也不应臆测固定结论。
需要明确的边界条件:
- 该测试需要真实 Elasticsearch 实例(环境变量注入的
ES_URL),不属于离线单元测试;仓库的自动化 CI 测试体系(见package.json的test:unit、test:integration脚本)并不包含此脚本,它定位为人工运行的性能验证工具。 - 数据集依赖外部 rally 下载源,首次运行需联网;脚本使用
inly(package.json中"inly": "5.0.1")解压 bzip2。 - 编码兼容性受服务端版本约束:Elasticsearch 9.3 起才支持 base64 编码 float32 向量摄入。
如果要在自己的环境复现,完整命令链为:
npm install export ES_URL=http://localhost:9200 export ES_USERNAME=elastic export ES_PASSWORD=your-password node test/ingest/base64.mjs脚本输出即为两份 4 档 chunkSize × 3 次均值的耗时对照表,可直接用于评估该写入形态在自身集群上的收益。
- 后端
- 搜索引擎
【免费下载链接】elasticsearch-js
Official Elasticsearch client library for Node.js
相关推荐
elasticsearch-js Bulk API 实战:单次请求批量写入文档与 base64 向量高效摄入
elasticsearch js Bulk API 实战:单次请求批量写入文档与 base64 向量高效摄入 Bulk API 允许在一次 HTTP 调用中执行
后端搜索引擎TensorBoardX性能基准测试:百万级标量写入速度对比
TensorBoardX性能基准测试:百万级标量写入速度对比 1. 引言:深度学习可视化的性能痛点 你是否在训练深度神经网络时遇到过这样的困境:当模型迭代到数万
深度学习数据可视化人工智能immutable-js基准测试:Benchmark.js性能对比
immutable js基准测试:Benchmark.js性能对比 你是否在开发中遇到过复杂状态管理导致的性能瓶颈?是否想知道Immutable.js的数据结构
开发框架与应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考