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第 ...
随机推荐
- Ambiguous mapping. Cannot map 'labelInfoController' method
使用springboot项目中,启动时出现Ambiguous mapping. Cannot map 'labelInfoController' method , 原因是,@RequestMappin ...
- 20162325 金立清 S2 W9 C18
20162325 2017-2018-2 <程序设计与数据结构>第9周学习总结 教材学习内容概要 堆是一棵完全二叉树,其中每个元素大于等于其所有子结点的值. 向堆中添加一个元素的方法是,首 ...
- 冲刺One之站立会议7 /2015-5-20
2015-5-20 在登陆成功之后要实现的是聊天界面的交互过程,不同的IP进行信息和数据的传递,这方面我们上学期Java实验里面有过相关的内容,我们把它拿过来改了一下格式,试着看能不能成功,目前还没实 ...
- requests爬取豆瓣热门电视剧
# *_*coding:utf-8 *_* import requests payload = {'key1': 'value1', 'key2': 'value2'} headers = {'use ...
- 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 ...
- centos6.7 安装JDK
1.卸载JDK 查看系统是否已安装JDK.一般的linux都默认使用了开源的openJDK.显示JDK版本信息,已经安装JDK,否则没有安装.命令行: [root@localhost ~]# ja ...
- jdbc -- 001 -- 一般方式创建数据库连接(oracle/mysql)
连接数据库步骤: 1. 注册驱动(只做一次) 2. 建立连接(Connection) 3. 创建执行SQL的语句(Statement) 4. 执行语句 5. 处理执行结果(ResultSet) 6. ...
- cnblog博客CSS定制
一.页面定制CSS #home { margin: 0 auto; width: 80%;/*原始65*/ min-width: 980px;/*页面顶部的宽度*/ background-color: ...
- mysql中LIKE和REGEXP
mysql中LIKE和REGEXP都可以用来字符匹配 正则表达式REGEXP是为复杂搜索指定模式的强大方式. like用法 LIKE一般与通配符(%)和(_)两个使用 如例 SELECT prod ...
- Powerful array CodeForces - 86D(莫队)
给你n个数,m次询问,Ks为区间内s的数目,求区间[L,R]之间所有Ks*Ks*s的和.1<=n,m<=200000.1<=s<=10^6 #include <iostr ...