Python调用ES delete_by_query触发409 ConflictError版本冲突
Elasticsearch delete_by_query 触发409 ConflictError 解决方法
报错核心成因
从报错携带的返回字段可直接定位问题:本次任务共扫描到527871条匹配目标时间范围的文档,但因累计产生1000个版本冲突、成功删除数为0,被ES直接返回409状态码的ConflictError。
delete_by_query默认执行规则为:
- 对匹配文档做逐批快照式扫描,记录每一条文档的当前版本号
- 执行删除操作时,会校验文档实际版本与扫描时记录的版本是否完全匹配,版本不一致即判定为版本冲突
- 默认遇到版本冲突时立即中断任务,不做自动重试,当单批次累计冲突数达到处理上限时,直接抛出错误
- 当前代码使用默认单批次处理大小1000,刚好在第一批扫描处理时就积累了1000个版本冲突,直接触发中断,因此实际删除数为0
这类版本冲突在实时写入的索引中非常常见,通常是因为delete_by_query扫描文档、尚未执行到对应删除步骤时,刚好有其他写入请求更新或删除了这批文档,导致文档版本与扫描时的快照版本不一致。
修复方案
- 调整任务中断逻辑:调用delete_by_query时增加
conflicts="proceed"参数,ES会在遇到版本冲突时跳过对应文档、继续执行剩余删除任务,不会因为少量冲突直接中断整个批量删除流程 - 优化批量处理参数:
- 调大单批次处理量,设置
scroll_size=10000,减少批次切换频率,缩小扫描和实际删除的时间窗口,降低出现版本差异的概率 - 开启自动分片并行处理,设置
slices="auto",让ES根据索引的分片数自动并行拆分删除任务,提升整体处理速度
- 调大单批次处理量,设置
- 简化冗余查询结构:原代码中用
bool.should包裹单条range查询属于冗余结构,直接编写range查询语句即可,减少查询解析的额外开销
修正后的代码示例
from elasticsearch import Elasticsearch ES_HOST = {"host" : "localhost", "port" : 9200} INDEX_NAME = "替换为实际操作的索引名" start_datetime = "2020-12-23 00:00:00" end_datetime = "2020-12-24 00:00:00" es = Elasticsearch(hosts = [ES_HOST]) es.delete_by_query( index = INDEX_NAME, # 核心调整:遇到版本冲突跳过,不中断任务 conflicts="proceed", # 优化处理性能参数 scroll_size=10000, slices="auto", body = { "query": { "range": { "recorddate": { "gte": start_datetime, "lt": end_datetime } } } } )
操作注意事项
- 开启
conflicts=proceed后,任务最终返回的结果中会统计实际的版本冲突数、成功删除数,可根据返回结果判断是否需要补跑剩余冲突文档 - 大批量删除时建议根据集群负载调整
slices的数值,过高的并发度可能挤占集群正常业务的资源 - 删除操作完成后可根据业务需求设置
refresh="wait_for",确保删除结果在查询前完成索引刷新
内容的提问来源于stack exchange,提问作者Alaric
相关产品推荐
相关产品推荐

