ES 5.x Bulk update重复的文档id性能低下

ES 5.x Bulk update重复的文档id性能低下现在很多公司 包括我们自己 将 ES 用作数据库数据的索引 将多个数据库的数据同步到 ES 是非常常见的应用场景 所以感觉这个问题可能会困扰不止一个用户 而官方的文档也没有对 update 的底层机制及局限做特别说明 特将该问题的讨论和结论整理成文 供社区用户参考 问题描述在 ES5 x 里通过 bulkupdate 将数据从数据库同步到 ES 如果短时间更新的一批数据里存在相同的文档 ID 例如一个 bulk

现在很多公司(包括我们自己)将ES用作数据库数据的索引,将多个数据库的数据同步到ES是非常常见的应用场景。所以感觉这个问题可能会困扰不止一个用户,而官方的文档也没有对update的底层机制及局限做特别说明,特将该问题的讨论和结论整理成文,供社区用户参考。


问题描述

在ES5.x里通过bulk update将数据从数据库同步到ES,如果短时间更新的一批数据里存在相同的文档ID,例如一个bulk update里大量写入下面类型的数据:

 {id:1,name:aaa}  {id:1,name:bbb} {id:1,name:ccc} {id:2,name:aaa} {id:2,name:bbb} {id:2,name:ccc} .......

则更新的速度非常慢。  而在ES 1.x和2.x里同样的操作快得多


根源追溯

update操作是分为两个步骤进行,即先根据文档ID做一次GET,得到最新版本的文档,然后在内存里做好更新后,再写回去。问题就出在这个GET操作上面。

core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java 这个类里面,get函数会根据一个realtime参数(默认是true),决定如何获取原始文档。 

public GetResult get(Get get, Function 
    
      searcherFactory, LongConsumer onRefresh) throws EngineException { assert Objects.equals(get.uid().field(), uidField) : get.uid().field(); try (ReleasableLock lock = readLock.acquire()) { ensureOpen(); if (get.realtime()) { VersionValue versionValue = versionMap.getUnderLock(get.uid()); if (versionValue != null) { if (versionValue.isDelete()) { return GetResult.NOT_EXISTS; } if (get.versionType().isVersionConflictForReads(versionValue.getVersion(), get.version())) { throw new VersionConflictEngineException(shardId, get.type(), get.id(), get.versionType().explainConflictForReads(versionValue.getVersion(), get.version())); } long time = System.nanoTime(); refresh("realtime_get"); onRefresh.accept(System.nanoTime() - time); } } // no version, get the version from the index, we know that we refresh on flush return getFromSearcher(get, searcherFactory); } 
    

可以看到realtime参数决定了是否以实时的方式获取数据。 如果设置为false,意味着不关心实时性,此时直接从searcher对象里面拿数据。因为searcher只能访问refresh过的数据,那些刚写入到indexing writter buffer里,还未经历过refresh的数据不会被访问到,故而该读取方式是准实时(Near Real Time)。 而这个realtime参数默认设置是true,说明需要以实时的方式访问数据,也就是说writter buffer里未经refresh的数据也要能被检索到,如何保证这块数据也能被实时访问呢?

从代码里可以看到,其中存在一个refresh("realtime_get") 的函数调用。这个函数调用会检查,GET的doc id是否都是可以被搜索到。 如果已经写入了但无法搜索到,也就是刚刚写入到writter buffer里还未refresh这种情况,就会强制执行一次refresh操作,让数据对searcher可见,保证getFromSearcher调用拿的是完全实时的数据。

实际上测试下来,正是这样的结果: 在关闭索引的自动刷新的情况下(设置refresh_interval: -1,只写入一条文档,然后对该文档ID执行一个GET操作,就会看到有一个新的segment生成。 说明GET的过程触发了refresh。

查了下文档,如果仅仅是做GET API调用,这个实时性可以人为控制,只需要在url里带可选参数realtime=[true/|false]。 参考: reference/5.6/docs-get.html#realtime。

然而,不幸的是,update API的文档和源码都没有提供一个禁用实时性的参数。 update对GET的调用,传入的realtime参数是在代码里写死为true的,意味着update的时候,必须强制执行一次realtime GET.

为什么是这样的代码逻辑,仔细想一下就也就了然了。因为update允许对文档做部分字段更新,如果有2个请求分别更新了同一个文档的不同字段, 可能先更新的数据还在writter buffer里,没来得及refresh,因而对searcher不可见。如果后续更新不做一次refresh,前面的更新可能就丢失了。 

另外一个问题,为啥5.x之前的版本没有这个性能问题?  看了下2.4的GET方法源码,其的确没有采用refresh的方式来保障数据的实时性,而是通过访问translog来达到同样的目的。官方在这个变更里pull#20102将机制从访问translog改为了refresh。理由是之前ES里有很多地方利用translog来维护数据的位置,使得很多操作变得很慢,去掉对translog的依赖可以全面提高性能。

很遗憾,这个更改对于短时间反复大量更新相同doc id的操作,会因为过于频繁的强制refresh,短时间生成很多小segment,继而不断触发segment合并,产生显著的性能损耗。 从上面链接里的讨论看,官方认为,在提升大多数应用场景性能的前提下,对于这种较少见的场景下的性能损失是值得付出的。所以,建议从应用层面去解决。

因此,如果实际应用场景里遇到类似的数据更新问题, 只能是优化应用数据架构,在应用层面合并相同doc id的数据更新后再写入ES,或者只能使用ES 2.x这样的老版本了。

注意:此问题在es6.3以上的版本已经解决

文章来源:https://elasticsearch.cn/article/273

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/227380.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午9:27
下一篇 2026年3月16日 下午9:27


相关推荐

  • 2019年长沙前端技术分享大会圆满成功

    做一个积极的人编码、改bug、提升自己我有一个乐园,面向编程,春暖花开!本文首发: 唐胡子俱乐部,授权发布!摘要长沙百名互联网前端程序员齐聚长沙互联网活动基地(唐胡子俱乐部)。主办单位:唐胡子俱乐部支持单位:芒果TV,拓维,湘邮,58到家,御泥坊,兴盛优选,中软国际,长海科技,长沙联通时 间:2019年5月19日—————————-…

    2022年2月28日
    402
  • “Word在试图打开文件时遇到错误”的解决方法[通俗易懂]

    “Word在试图打开文件时遇到错误”的解决方法[通俗易懂] 大家都应该知道“.DOCX”格式只有Word2007或以上版本才可以打开,Word2003是无法打开的!正好我电脑上03和07都有,所以就答应了。接收文件打开后既然提示“Word在试图打开文件时遇到错误。请尝试下列方法”(如下图)。  还好,本人使用Office办公软件已经很多年了,各方面问题都遇见过,这点小难题难不到我的,三下两下就被我搞定了。相信遇到“W…

    2022年5月1日
    89
  • Effective C++ 条款24

    Effective C++ 条款24

    2022年1月19日
    69
  • Claude Code使用指南

    Claude Code使用指南

    2026年3月16日
    2
  • 远线程注入

    远线程注入OpenProcess函数打开现有的本地进程对象。HANDLEWINAPIOpenProcess(_In_DWORDdwDesiredAccess,_In_BOOLb

    2021年12月13日
    48
  • linux查看tomcat版本信息,linux下tomcat版本查看

    linux查看tomcat版本信息,linux下tomcat版本查看本文收集整理关于 linux 下 tomcat 版本查看的相关议题 使用内容导航快速到达 内容导航 Q1 如何查看 tomcat 版本 linuxa 进入 tomcat 安装目录中的 bin 目录 这个文件目录中有 catalina bat 以及 version bat 文件 b 在文件目录地址栏中输入 cmd 命令 c 在命令命令提示符中输入 catalinavers 或者 version 便可以查到当

    2026年3月26日
    1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号