我如何吸引Elastic创始人一起对高并发写入进行优化?

导语:在腾讯金融科技数据应用部的全民 BI 项目里,我们每天面对超过 10 亿级的数据写入,提高 ES 写入性能迫在眉睫,在最近的一次优化中,有幸参与到了 Elasticsearch 开源社区中。

背景

为了更便捷地分析数据,腾讯金融科技数据应用部去年推出了全民 BI 的系统。这个系统通过 Elasticsearch 进行基础的统计,超过 10 亿级的数据量需要尽可能快速地导入到 ES 系统中。即使经过多次的参数优化,我们依然需要几个小时才能完成导入,这是系统此前存在的一大瓶颈。

在这样的背景下,我们开始决定进一步深入 ES,寻找优化点。

优化前的准备

我们准备了 1000 万的数据,并在原程序(Spark 程序写入)上进行了几轮单机压测,得到了一些基本的性能数据。

机器配置:CPU 24核,内存 64G
ES 基本配置:
  • 堆内存 31G

  • 其他参数调整包括 lock memory,translog.durability 调整成 async 等(更详细的策略可以参见

文档数:1000 万,字段 400 个(没有 text 字段)
写入耗时:26 分钟
CPU:80%+

寻找理论值

在往下进入深水区之前,我们需要先回顾一下 ES 本身,ES 本身是在 Lucene 基础上设计的分布式搜索系统,在写入方面主要提供了:
  • 事务日志和成组提交的机制提高写入性能并保证可靠性

  • 提供 schema 的字段定义(映射到 Lucene 的字段类型)

要进行优化,首先得验证一个问题:Lucene 的极限速率能到达多少,所以我在我的本机上构建了这样的一个测试。

Macbook Pro 15,6核12线程
数据量 1000 万,每个 document 400 个字段,10 个线程并发(考虑 mac cpu Turbo 4.5G ,服务器 2.4G(24核),所以只采用 10 线程并发)
验证写入耗时 549s(约 10 分钟)。

26 分钟 —> 10 分钟,意味着理论上是可行的。那剩下的就看如何接近这个极限。因为那说明一定是 ES 本身的一些默认特性导致了写入速率无法提升。

下面的介绍忽略了一些相对简单的参数调优,比如关闭 docvalues,这个对于非 text 字段,ES 默认开启,对于不需要 groupby 的场景,是不必要的,这个可以减少不少性能。

经过初步的参数优化写入耗时降低到了 18 分钟,这是后面继续往下优化的基础。

理解 ES 写入的机制

ES 的写入流程(主分片节点)主要有下面的几步
  • 根据文档 ID 获取文档版本信息,判断进行 add 或 update 操作

  • 写 Lucene:这里只写内存,会定期进行成组提交到磁盘生成新分段

  • 写 translog:写入文件

640?wx_fmt=png
▲ translog 作用
除了上面的直接流程,还有三个相关的异步流程
  • 定期进行 flush,对 Lucene 进行 commit

  • 定期对 translog 进行滚动(生成新文件),更新 check point 文件

  • 定期执行 merge 操作,合并 Lucene 分段,这是一个比较消耗资源的操作,但默认情况下都是配置了一个线程。

优化第一步 — 参数调优

写 Lucene 前面已经优化过,那么第一步的文档查找其实是在所有分段中进行查找,因为只提供了一个线程进行 merge,如果 merge 不及时,导致分段过的,必然影响文档版本这一块的耗时。

所以我们观察了写入过程中分段数的变化:
640?wx_fmt=png
▲ 写入过程中分段的变化
观察发现,分段的增长速度比预期的快很多。按照默认配置,index_buffer=10%,堆内存 31G 的情况,按 Lucene 的写分段机制,平均到每个线程,也有 125M,分段产生的速度不应该那么快。而这个问题的根源就是 flush_threshold_size 默认值只有 512M ,这个参数表示在当未提交的 translog 日志达到该阈值的时候进行一次刷盘操作。
640?wx_fmt=png
▲ 小分段的产生
640?wx_fmt=png
▲ 调整后比较缓和的分段增长

测试结果一看:18 分钟!基本没有效果!

理论上可行的方案,为什么却没有效果,带着这个疑问继续潜入深水区。

优化继续 — 线程分析

这时候就需要进行堆栈分析了,多次取样后,发现了下面的一个频繁出现的现象:
640?wx_fmt=png
▲ 被堵塞的线程
发现很多线程都停在了获取锁的等待上,而 writeLock 被 rollGeneration 占用了。

写线程需要获取 readLock

而在高 flush_threshold_size 的配置下,rollGeneration 发生了 300+ 次,每次平均耗时 560ms,浪费了超过 168s,而这个时间里写入线程都只能等待,小分段的优化被这个抵消了。
这里有很多的关联关系,lush 操作和 rollGeneration 操作是互斥的,因为 flush 耗时较长(5~10 秒左右),在默认 flush_threshold_size 配置下,rollGeneration 并没有这么频繁在 100 次左右,提高 flush_threshold 放大了这个问题。

初步优化方案提交

因为我们在写入过程中使用的 translog 持久化策略是 async,所以我很自然地想到了把写日志和刷盘异步化。
640?wx_fmt=png
▲ 初版提交社区的方案

一开始的方案则想引入disruptor,消除写线程之间的竞争问题,后面因为es的第三方组件检查禁止使用sun.misc.Unsafe (disruptor无锁机制基于Unsafe实现)而放弃。基于这个方案,测试结果终于出现了跨越:13分钟。

初版的方案问题比较多,但是它有两个特点:
  • 足够激进:在配置为 async 策略时,将底层都异步化了

  • 凸显了原方案的问题:让大家看到了 translog 写入的影响

Elastic 创始人加入讨论

没想到的是,在社区提交几次优化后,竟然吸引了大佬 Simon Willnauer 的加入。

Simon Willnauer
  • Elastic 公司创始人之一和技术 Leader

  • Lucene Core Commiter and PMC Member

Simon 的加入让我们重新复盘了整个问题。

通过对关键的地方增加统计信息,我最终明确了关键的问题点在于 FileChannel.force 方法,这个操作是最耗时的一步。

sync 操作会调用 FileChannel.force,但没有在 writer 的对象锁范围中,所以影响较小。但是因为 rollGeneration 在 writeLock 中执行,所以阻塞的影响范围就变大了

跟社区讨论后,Simon 最后建议了一个折中的小技巧,就是在关闭原 translog 文件之前(writeLock 之外),先执行一次刷盘操作。
640?wx_fmt=png
▲ 代码修改

这个调整的效果可以让每次 rollGeneration 操作的耗时从平均 570ms 降低到 280ms,在我的基准测试中(配置 flush_threhold_size=30G,该参数仅用于单索引压测设计,不能在生产环境使用),耗时会从 18 分钟下降到 15 分钟。

事实上,这并不是一个非常令人满意的解决方案,这里选择这个方案主要出于两点考虑:
1.未来新的版本将考虑不使用 translog 进行副分片的 recovery,translog 的滚动策略会进行调整(具体方案 elasitc未透露)
2.这个修改非常的风险非常小

提交社区

最后根据讨论的最终结论,我们重新提交了 PR,提交了这个改动,并合并到了主干中。

总结和待续

下面是 ES 写入中的影响关系和调用关系图,从图中可以看到各个因素直接的相互影响。
640?wx_fmt=png
▲ InternalEngine 中的影响关系

最近提交的优化实时上只优化了 rollGeneration,而实际上这里还有一些优化空间 trimUnreferenceReader,这个也在跟社区沟通中,并需要足够的测试数据证明调整的效果,这个调整还在测试中。

而在我们目前实际应用场景中,我们通过调整下面两个参数提高性能:

  • index.translog.flush_threshold_size 默认 512M,可以适当调大,但不能超过 indexBufferSize*1.5 倍/(可能并发写的大索引数量),否则会触发限流,并导致 JVM 内存不释放!

  • index.translog.generation_threshold_size(默认 64M,系统支持,但官方文档没有的参数,超过该阈值会产生新的 translog 文件),要小于 index.translog.flush_threshold_size,否则会影响 flush,进而触发限流机制

参考文档

张超《Elasticsearch源码解析与优化实战》

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/313846.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java线程安全

Java 线程安全 什么是线程安全? 当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。 一:基本概念 共享资源:能够被多个线程同时访问的资源竞态条件:当两个线程竞争统…

P4899 [IOI2018] werewolf 狼人(kruskal 重构树 + 主席树)

P4899 [IOI2018] werewolf 狼人 给定一个有nnn个点mmm条边的无向图,有QQQ个询问 每次输入S,E,L,RS, E, L, RS,E,L,R,表示你在SSS点出发,要到EEE点,且初始时你是人形态,你只能走[L,n][L, n][L,n]的点, 但…

微软+开源,那些亲爱的以及热爱的

微软 Reactor 社区空间开幕式暨 Azure Meetup 社区活动已于9月7日在上海圆满结束!但是…如何构建一个可持续发展的社区未来的路,仍然很长...你应该知道的微软 Reactor微软 Reactor 是微软为构建开发者社区而提供的一个社区空间,以“予力多元…

Java线程问题问答

1、多线程的作用? 发挥多核CPU的优势,提高效率防止阻塞便于建模:将一个任务拆分成多个子任务,分别建立程序模型 2:平时项目中使用锁和synchronized比较多,而很少使用volatile,难道就没有保证可见…

ASP.NET Core 2.2 项目升级至 3.0 备忘录

.NET Core 3.0及ASP.NET Core 3.0 前瞻ASP.NET Core 3.0 迁移避坑指南将 ASP.NET Core 2.2 迁移至 ASP.NET Core 3.0 需要注意的地方记录在这篇随笔中。TargetFramework 改为 netcoreapp3.0 <TargetFramework>netcoreapp3.0</TargetFramework>从 Web 项目&#xff…

Java 死锁

目录&#xff1a; 什么是死锁&#xff1f;死锁是怎么产生的&#xff1f;怎么排查死锁&#xff1f;死锁的预防拓展&#xff1a;Java CPU 100%排查 一 什么是死锁&#xff1f; 注&#xff1a;线程和进程都可能会产生死锁&#xff0c;以下以线程为例 死锁是指两个或两个以上的…

CF196E Opening Portals

CF196E Opening Portals 给定一个有nnn个节点&#xff0c;mmm条边的无向联通图&#xff0c;有kkk个点有portalsportalsportals&#xff0c;当经过了某个点&#xff0c;如果这个点有portalportalportal&#xff0c;它就会永久开启&#xff0c; 对于任意两个开启的portalportal…

.NET Core 微信小程序退款——(统一退款)

点击上方“dotNET名人堂”&#xff0c;选择“设为星标”用学习的姿态&#xff0c;步入工作的状态继上一篇".NET Core 微信小程序支付——&#xff08;统一下单&#xff09;后"&#xff0c;本文将实现统一退款功能&#xff0c;能支付就应该能退款嘛&#xff0c;一般涉…

Java 锁之 CAS

什么是CAS(compare and swap)&#xff1f; CAS&#xff08;Compare & Set&#xff0c;或是 Compare & Swap&#xff09;&#xff0c;即比较并交换&#xff0c;也是实现我们平时所说的自旋锁或乐观锁的核心操作。 它的实现很简单&#xff0c;就是用一个预期的值和内存…

牛客练习赛71 F 红蓝图(kruskal重构树)

红蓝图 给定两个参数x,tx, tx,t&#xff0c;删除边权大于ttt的红边&#xff0c;和边权小于ttt的蓝边&#xff0c;问对于所有的点yyy&#xff0c;既能通过红边走向xxx&#xff0c;又能通过蓝边走向xxx&#xff0c;的点有多少个。 考虑对红边按照边权升序建立一颗kruskalkruska…

.NET 分布式自增Id组件(解决自动分配机器Id、时间回拨问题)

IdHelper是一个.NET&#xff08;支持.NET45或.NET Standard2&#xff09;生成分布式趋势自增Id组件&#xff0c;有两个版本&#xff1a;原始版为基于雪花Id&#xff08;不了解请自行百度&#xff09;方案&#xff0c;需要手动管理设置WorkerId&#xff1b;完美版在原始版的基础…

悲观锁与乐观锁

悲观锁 总是假设最坏的情况&#xff0c;每次取数据的时候都认为别人会来修改&#xff0c;所以每次取数据的时候都会上锁。其它线程想要取这份数据就必须拿到相应的锁&#xff08;共享资源每次只供一个线程使用&#xff0c;其它线程阻塞&#xff0c;用完之后转让给其他线程&…

1190 最小公倍数之和 V2

1190 最小公倍数之和 V2 ∑iablcm(i,b)∑iabibgcd⁡(i,b)b∑d∣b∑i⌈ad⌉bdi[gcd(i,bd)1]b∑d∣b∑k∣bdμ(k)k∑i⌈⌈ad⌉k⌉abkib∑T∣n∑i⌈aT⌉bTi∑k∣Tμ(k)kb∑T∣n(bT⌈aT⌉)(bT−⌈aT⌉1)2∑k∣Tμ(k)k设f(n)∑d∣nμ(d)d,f(1)1,f(p)1−p,f(pk)1−p,且为积性函数\sum…

有关 VS Code 的五大谣言,背后的真相到底是如何的?

2015 年 4 月 29 日&#xff0c;在微软 Build 2015 大会上&#xff0c;微软发布了 Visual Studio Code 第一个预览版本。随着 Visual Studio Code 有了越来越多的使用者&#xff0c;随之而来的各类谣言也层出不穷。让我们就来看看有哪些与 VS Code 相关的谣言&#xff0c;背后的…

Mysql数据库锁机制

一&#xff1a;概念介绍 MySQL数据库锁管理机制&#xff1a; SQL层实现的锁机制    Meta-data元数据锁&#xff1a;在table cache缓存里实现的&#xff0c;为DDL&#xff08;Data Definition Language&#xff09;提供隔离操作。一种特别的meta-data元数据类型&#xff0c;…

HDU 6340 Problem I. Delightful Formulas(伯努利数 + 积性函数反演)

Problem I. Delightful Formulas 大概就是照着题解抄了一遍吧&#xff0c;这道题太神仙了…… aiik,si∑j1iajcalc∑i1nsi[gcd⁡(i,n)1]∑d∣nμ(d)∑i1ndsida_i i ^ k, s_i \sum_{j 1} ^{i} a_j\\ calc\ \sum_{i 1} ^{n} s_i[\gcd(i, n) 1]\\ \sum_{d \mid n} \mu(d) \s…

干货|亲测有效的N倍学习效果笔记法

这里是Z哥的个人公众号每周五11&#xff1a;45 按时送达当然了&#xff0c;也会时不时加个餐&#xff5e;我的第「108」篇原创敬上大家好&#xff0c;我是Z哥。先祝大家中秋快乐。我猜你现在心情不错&#xff0c;毕竟小长假的第一天才开始&#xff0c;后面还有60个小时的假期&a…

Java偏向锁、轻量级锁、重量级锁

先Mark&#xff0c;后补充 参照&#xff1a; https://www.infoq.cn/article/java-se-16-synchronized http://www.cnblogs.com/paddix/p/5405678.html http://www.cnblogs.com/lzh-blogs/p/7477157.html

.NET Core 3.0 可卸载程序集原理简析

文章转载授权级别&#xff1a;A 预计阅读时间&#xff1a;8分钟 损失发量&#xff1a;不好统计因为最近在群里被问到如何理解 .NET Core 3.0 可卸载程序集&#xff0c;所以就写了这篇简单的分析。因为时间实在很少&#xff0c;这篇文章只简单的罗列了相关的代码&…

P4331 [BalticOI 2004]Sequence 数字序列(左偏树)

P4331 [BalticOI 2004]Sequence 数字序列 给定一个序列整数a1,a2,a3,…,an−1,ana_1, a_2, a_3, \dots, a_{n - 1}, a_na1​,a2​,a3​,…,an−1​,an​&#xff0c;要找一个整数序列bbb&#xff0c;满足b1<b2<b3<⋯<bn−1<bnb_1 < b_2 < b_3< \dots&…