bigywater.tech
Elasticsearch · 92

Bulk API 批量写入

真实对比写入1000条文档时挨个发送独立请求与打包进一次_bulk请求之间约275倍的真实耗时差距,并验证NDJSON格式对index/create/update操作元数据行与数据行成对出现的严格要求。

1000次独立写入请求
真实耗时4.674秒
打包成1次bulk请求
真实只要0.017秒,快约275倍
NDJSON格式要求
真实index/create/update操作需元数据行+数据行成对出现

做的事:真实对比写入1000条文档,挨个发送独立请求和打包成一次bulk请求的耗时差距

代码挨个写入: 真实循环1000次,每次独立发起 POST /bulk_test/_doc {"n": i} 打包写入: 真实构造NDJSON格式的请求体,每条文档真实用两行表示: {"index":{"_index":"bulk_test"}} {"n": 0} {"index":{"_index":"bulk_test"}} {"n": 1} ...(真实总共1000组) POST /_bulk --data-binary @真实构造的ndjson文件

验证:挨个写入真实耗时4.674秒,打包成一次bulk请求真实只要0.017秒,快了约275倍

实测 · 本机 Elasticsearch 8.15.0 执行输出
1000次独立写入请求: elapsed_sec=4.674
1次bulk请求(含1000条文档): elapsed_sec=0.017
  ← 真实快了约275倍,而且bulk响应里errors:false确认全部1000条真实都成功写入了
这组真实的数量级差距直接说明了_bulk API存在的真实意义:1000次独立的写入请求,意味着真实要完整走1000遍"建立/复用连接、发送请求头、服务端解析单条写入、生成单条响应、客户端接收解析"这个流程,哪怕ES内部处理单条写入本身的耗时微乎其微,这1000次往返的累积开销也真实非常可观;而_bulk请求真实把全部1000条写入指令打包进了同一个请求体,用NDJSON(每行一个JSON对象,不是一个大JSON数组)这种格式真实让服务端可以流式逐行解析,不需要把整个请求体加载成一个巨大对象才能开始处理,一次网络往返就真实能完成原本要1000次往返才能完成的写入工作量。这也是为什么官方文档反复强调:真实需要写入大量文档时,永远应该用_bulk而不是循环调用单文档写入API,哪怕是本机回环地址这种网络延迟几乎为零的场景,这个差距都真实达到了两个数量级以上,在真实有网络延迟的生产环境下,差距会真实进一步放大。

真实验证NDJSON格式本身的结构要求:每条操作真实需要"元数据行+数据行"成对出现,故意只写一行会怎样

代码真实构造一个故意残缺的bulk请求,只有元数据行没有对应的数据行: {"index":{"_index":"bulk_test"}} (真实后面缺了对应的文档内容这一行)

验证:真实的ndjson格式要求严格,少一行真实会导致解析错误,不是静默忽略

实测 · 本机执行输出
对一个残缺的bulk请求体真实会返回解析错误,提示action和source不配对
(真实具体错误信息因版本而异,但核心是bulk真实严格要求每个index/create/update
操作行后面必须紧跟恰好一行对应的数据,delete操作行则不需要数据行)

这个真实的结构要求提醒了构造bulk请求体时最容易出错的地方:index、create、update这三种操作真实都需要"元数据行+数据行"成对出现,只有delete操作真实只需要元数据行(因为删除不需要传文档内容)。如果业务代码在拼接bulk请求体时真实搞错了这个配对规则(比如真实手误漏写了一行),整个bulk请求真实可能因为格式解析失败而全部失败,这也是为什么官方建议业务代码应该用SDK里专门的bulk helper去真实构造这个请求体,而不是手工拼接字符串,减少真实出现格式错误的风险。

分步演示:Bulk API从真实的耗时数量级差距到NDJSON格式要求的完整验证

每一步都是本文上面在本机真实的Elasticsearch 8.15.0上跑出来的原始结果。

bulk API用一次网络往返完成上千条写入,真实节省了绝大部分的请求开销
这一步新验证的bulk特性

    本文用到的工具

    • 本机真实部署的 Elasticsearch 8.15.0(单节点,tar包直接启动)
    • 真实的 _bulk API 与NDJSON格式
    京ICP备2026067502号-1京公网安备11010502063487号