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

本文原载于Elastic中文社区: https://elasticsearch.cn/publish/article/273
本文是针对社区问题question#2352的分析和总结

现在很多公司(包括我们自己)将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<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参数决定了是否以实时的方式获取数据。 如果设置为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这样的老版本了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 很少写技术博客,一是觉得自己技术有限,怕误人子弟;二是文笔较差,比较排斥写作之类的工作。话说不锻炼,哪会有进步呢,...
    奔跑的番茄酱阅读 42,275评论 7 132
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,424评论 25 707
  • INDEX API 示例: 说明:1.索引文档使用PUT方法,需要指定index(test)、type(user)...
    imsilence阅读 3,046评论 0 0
  • 定义 ThreadThread 是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异...
    learning_lx阅读 238评论 0 1