ElasticStack系列之十四 & ElasticSearch5.x bulk update 中重复 id 性能骤降
目前在绝对多数公司在使用 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 性能骤降的更多相关文章
- ElasticStack系列之十六 & ElasticSearch5.x index/create 和 update 源码分析
开篇 在ElasticSearch 系列十四中提到的问题即 ElasticStack系列之十四 & ElasticSearch5.x bulk update 中重复 id 性能骤降,继续这个问 ...
- ElasticStack系列之十八 & ElasticSearch5.x XPack 过期新 License 更新
摘要 当你某一天打开 Kibana 对应的 Monitoring 选项卡的时候,发现提示需要下载新的 license,旧的 license 已经过期了,试用期为30天,如果不是很需要其他的复杂监控.报 ...
- Chrome浏览器扩展开发系列之十四
Chrome浏览器扩展开发系列之十四:本地消息机制Native messaging 时间:2015-10-08 16:17:59 阅读:1361 评论:0 收藏:0 ...
- webpack4 系列教程(十四):Clean Plugin and Watch Mode
作者按:因为教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步<webpack4 系列教程(十四):Clean Plugin and Watch Mode>原文地址.更欢迎 ...
- OSGi 系列(十四)之 Event Admin Service
OSGi 系列(十四)之 Event Admin Service OSGi 的 Event Admin 服务规范提供了开发者基于发布/订阅模型,通过事件机制实现 Bundle 间协作的标准通讯方式. ...
- Java 设计模式系列(十四)命令模式(Command)
Java 设计模式系列(十四)命令模式(Command) 命令模式把一个请求或者操作封装到一个对象中.命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复 ...
- Chrome浏览器扩展开发系列之十四:本地消息机制Native messagin
Chrome浏览器扩展开发系列之十四:本地消息机制Native messaging 2016-11-24 09:36 114人阅读 评论(0) 收藏 举报 分类: PPAPI(27) 通过将浏览器 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (20) -----第四章 ASP.NET MVC中使用实体框架之在MVC中构建一个CRUD示例
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第四章 ASP.NET MVC中使用实体框架 ASP.NET是一个免费的Web框架 ...
- “全栈2019”Java第二十四章:流程控制语句中决策语句switch下篇
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
随机推荐
- Beta版本互评
基于NABCD评论作品,及改进建议 经过alpha发布之后,迫不及待的使用了psp daily这款软件,使用非常方便,基本的功能都可以实现,经过beta周之后,我对这款产品非常期待,希望能给我更友好的 ...
- Java将json字符串转成map
Map<String, Object> map = (Map<String, Object>) JSONUtils.parse(result)
- 第三次作业— C++计算器项目的初始部分
作业题目: C++计算器项目的初始部分 仓库 代码: Scan.h #ifndef SCAN_H #define SCAN_H #include<string> #include<i ...
- HDU 4539 郑厂长系列故事——排兵布阵 状压dp
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4539 郑厂长系列故事--排兵布阵 Time Limit: 10000/5000 MS (Java/O ...
- boolean类型的按位或||和|的区别
boolean类型既可以使用&&和||做逻辑运算,也可以使用&和|做逻辑运算,但前者是经过优化的(执行短路运算),后者未优化. 以下代码验证: 逻辑或|| public cla ...
- Java网络编程二:Socket详解
Socket又称套接字,是连接运行在网络上两个程序间的双向通讯的端点. 一.使用Socket进行网络通信的过程 服务端:服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户端的连接 ...
- 简易版本vue的实现和注解
本文参考的是前辈的简易版本Vue实现:http://www.cnblogs.com/canfoo/p/6891868.html,感谢.前辈GitHub地址:https://github.com/can ...
- sql update set from 的用法 (转)
关键字: update set from 下面是这样一个例子: 两个表a.b,想使b中的memo字段值等于a表中对应id的name值 表a:id, name 1 ...
- 热部署在Eclipse和IDE里面的使用
热部署在Eclipse和IDE里面的使用 简介:讲解热部署的好处及使用注意事项,在eclipse里面默认开启,在IDE里面默认关闭 .增加依赖 <dependency> <group ...
- javascript定时保存表单数据的代码
(忘记是不是两家邮箱都有这个功能). 那这个功能是怎么做的呢? 定时,我们知道怎么弄,但保存呢?也许我们会通过隐藏域等手段来存放数据.但是,这个却有个缺点:那就是刷新页面后,数据将会丢失. 而此时,就 ...