目前在绝对多数公司在使用 ElasticSearch 将其当做数据库使用,将多个数据库中的数据同步到 ElasticSearch 索引是非常常见的应用场景。那么自然而然就会涉及到数据频繁的新增和更新,而官方的文档并没有对 update 的底层机制做特别说明,而当我们从 2.x 版本升级到 5.x 发现反而比之前的性能差很多,那这到底是怎么回事呢?

问题描述

  在 ElasticSearch5.x 里通过 bulk update 将数据从数据库同步到 ElasticSearch,如果短时间更新的一批数据里存在相同的 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}
.......

  则更新的速度会变的非常的慢。而在 ElasticSearch1.x 和 ElasticSearch2.x 里同样的操作会快的多。

根源追溯

  update 操作时分为两个步骤进行的,即先根据文档 id 做一次 GET 操作,得到旧版本的文档,然后在其基础上做更新再写回去,问题就出在这个 GET 上面。

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

public GetResult get(Get get, Function<String, Searcher> 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 参数决定了 GET 到的数据的实时性。如果设置为 false,则直接从 searcher 里面拿,而 searcher 只能访问 refresh 过的数据,意味着刚写入的数据由于存在于 index writer buffer 里还未 refresh,暂时无法搜索到,所以这种方式拿到的数据是准实时的。而默认的 realtime=true,则决定了获取到的数据必须是实时的,也就是说 index writer buffer 里的数据也要能被检索到。从代码里可以看到,其中存在一个 refresh("realtime_get"); 的函数调用,这个函数会检查 GET 的 doc id 是否都是可以被搜索到的。如果已经写入了但是无法搜索到,也就是刚刚写入到 index writer buffer 里还未 refresh 这种情况,就会强制执行一次 refresh 操作,让数据对 searcher 可见,保证最后的 getFromSearcher 方法调用拿的是完全实时的数据。

  实际上测试下来,也是这样的,关闭自动刷新,写入一条文档,然后对该文档 id 执行一次 GET 操作,就会看到有一个新的 segment 生成。

  查了下文档,GET API 调用时,可以选择实时或者非实时,只需要在 url 里带上可选参数 realtime=[true/false]。

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

  至于为什么 update 一定要需要实时 GET,想一下其实也可以理解的。因为 update 允许对文档进行局部更新,如果有两个请求分别更新了同一个文档的不同字段,可能先更新的数据还在 index writer buffer 里,没来得及 refresh,因为对 searcher 不可见。如果不做refresh,那后面的更新还是在老版本上做的,前面的更新就会丢失掉。

  另外一个问题,为啥 ElasticSearch5.x 之前的版本没有这个性能问题呢?

  看了 ElasticSearch2.4 的 GET 方法源码,其没有采用 refresh 的方式来保障数据的实时性,而是通过访问 translog 来达到同样的目的。ElasticSearch5.x 将机制从 translog 改为了 refresh,理由是之前 ElasticSearch 里有很多地方利用 translog 来位数数据的位置,使得很多操作变得很慢,去掉对 translog 的依赖可以全面提高性能。

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

  因此,如果实际应用场景里遇到类似的数据更新问题,只能优化应用数据架构,在应用层面合并相同的 doc id 的数据更新后再写入到 ElasticSearch 索引中;或者 只能使用 ElasticSearch2.x 这样的老版本来间接的解决这类问题。

ElasticStack系列之十四 & ElasticSearch5.x bulk update 中重复 id 性能骤降的更多相关文章

  1. ElasticStack系列之十六 & ElasticSearch5.x index/create 和 update 源码分析

    开篇 在ElasticSearch 系列十四中提到的问题即 ElasticStack系列之十四 & ElasticSearch5.x bulk update 中重复 id 性能骤降,继续这个问 ...

  2. ElasticStack系列之十八 & ElasticSearch5.x XPack 过期新 License 更新

    摘要 当你某一天打开 Kibana 对应的 Monitoring 选项卡的时候,发现提示需要下载新的 license,旧的 license 已经过期了,试用期为30天,如果不是很需要其他的复杂监控.报 ...

  3. Chrome浏览器扩展开发系列之十四

    Chrome浏览器扩展开发系列之十四:本地消息机制Native messaging 时间:2015-10-08 16:17:59      阅读:1361      评论:0      收藏:0    ...

  4. webpack4 系列教程(十四):Clean Plugin and Watch Mode

    作者按:因为教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步<webpack4 系列教程(十四):Clean Plugin and Watch Mode>原文地址.更欢迎 ...

  5. OSGi 系列(十四)之 Event Admin Service

    OSGi 系列(十四)之 Event Admin Service OSGi 的 Event Admin 服务规范提供了开发者基于发布/订阅模型,通过事件机制实现 Bundle 间协作的标准通讯方式. ...

  6. Java 设计模式系列(十四)命令模式(Command)

    Java 设计模式系列(十四)命令模式(Command) 命令模式把一个请求或者操作封装到一个对象中.命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复 ...

  7. Chrome浏览器扩展开发系列之十四:本地消息机制Native messagin

    Chrome浏览器扩展开发系列之十四:本地消息机制Native messaging 2016-11-24 09:36 114人阅读 评论(0) 收藏 举报  分类: PPAPI(27)  通过将浏览器 ...

  8. 《Entity Framework 6 Recipes》中文翻译系列 (20) -----第四章 ASP.NET MVC中使用实体框架之在MVC中构建一个CRUD示例

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第四章  ASP.NET MVC中使用实体框架 ASP.NET是一个免费的Web框架 ...

  9. “全栈2019”Java第二十四章:流程控制语句中决策语句switch下篇

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

随机推荐

  1. Ambiguous mapping. Cannot map 'labelInfoController' method

    使用springboot项目中,启动时出现Ambiguous mapping. Cannot map 'labelInfoController' method , 原因是,@RequestMappin ...

  2. 20162325 金立清 S2 W9 C18

    20162325 2017-2018-2 <程序设计与数据结构>第9周学习总结 教材学习内容概要 堆是一棵完全二叉树,其中每个元素大于等于其所有子结点的值. 向堆中添加一个元素的方法是,首 ...

  3. 冲刺One之站立会议7 /2015-5-20

    2015-5-20 在登陆成功之后要实现的是聊天界面的交互过程,不同的IP进行信息和数据的传递,这方面我们上学期Java实验里面有过相关的内容,我们把它拿过来改了一下格式,试着看能不能成功,目前还没实 ...

  4. requests爬取豆瓣热门电视剧

    # *_*coding:utf-8 *_* import requests payload = {'key1': 'value1', 'key2': 'value2'} headers = {'use ...

  5. find K maximum value from an unsorted array(implement min heap)

    Maintain a min-heap with size = k, to collect the result. //Find K minimum values from an unsorted a ...

  6. centos6.7 安装JDK

      1.卸载JDK 查看系统是否已安装JDK.一般的linux都默认使用了开源的openJDK.显示JDK版本信息,已经安装JDK,否则没有安装.命令行: [root@localhost ~]# ja ...

  7. jdbc -- 001 -- 一般方式创建数据库连接(oracle/mysql)

    连接数据库步骤: 1. 注册驱动(只做一次) 2. 建立连接(Connection) 3. 创建执行SQL的语句(Statement) 4. 执行语句 5. 处理执行结果(ResultSet) 6. ...

  8. cnblog博客CSS定制

    一.页面定制CSS #home { margin: 0 auto; width: 80%;/*原始65*/ min-width: 980px;/*页面顶部的宽度*/ background-color: ...

  9. mysql中LIKE和REGEXP

    mysql中LIKE和REGEXP都可以用来字符匹配 正则表达式REGEXP是为复杂搜索指定模式的强大方式. like用法   LIKE一般与通配符(%)和(_)两个使用 如例 SELECT prod ...

  10. Powerful array CodeForces - 86D(莫队)

    给你n个数,m次询问,Ks为区间内s的数目,求区间[L,R]之间所有Ks*Ks*s的和.1<=n,m<=200000.1<=s<=10^6 #include <iostr ...