乐趣区

关于数据库:滴滴ElasticSearch千万级TPS写入性能翻倍技术剖析



桔妹导读:滴滴 ElasticSearch 平台承接了公司外部所有应用 ElasticSearch 的业务,包含外围搜寻、RDS 从库、日志检索、平安数据分析、指标数据分析等等。平台规模达到了 3000+ 节点,5PB 的数据存储,超过万亿条数据。平台写入的峰值写入 TPS 达到了 2000w/s,每天近 10 亿次检索查问。为了承接这么大的体量和丰盛的应用场景,滴滴 ElasticSearch 须要解决稳定性、易用性、性能、老本等诸多问题。咱们在 4 年多的工夫里,做了大量优化,积攒了十分丰盛的教训。通过建设滴滴搜寻平台,打造滴滴 ES 引擎,全方位晋升用户应用 ElasticSearch 体验。这次给大家分享的是滴滴在写入性能优化的实际,优化后,咱们将 ES 索引的写入性能翻倍,联合数据冷热拆散场景,反对大规格存储的物理机,给公司每年节俭千万左右的服务器老本。

1. 背景

前段时间,为了升高用户应用 ElasticSearch 的存储老本,咱们做了数据的冷热拆散。为了放弃集群磁盘利用率不变,咱们缩小了热节点数量。ElasticSearch 集群开始呈现写入瓶颈,节点产生大量的写入 rejected,大量从 kafka 同步的数据呈现写入提早。咱们深入分析写入瓶颈,找到了突破点,最终将 Elasticsearch 的写入性能晋升一倍以上,解决了 ElasticSearch 瓶颈导致的写入提早。这篇文章介绍了咱们是如何发现写入瓶颈,并对瓶颈进行深入分析,最终进行了创新性优化,极大的晋升了写入性能。

2. 写入瓶颈剖析

2.1 发现瓶颈

咱们去剖析这些提早问题的时候,发现了一些不太好解释的景象。之前做性能测试时,ES 节点 cpu 利用率能超过 80%,而生产环境提早索引所在的节点 cpu 资源只应用了不到 50%,集群均匀 cpu 利用率不到 40%,这时候 IO 和网络带宽也没有压力。通过晋升写入资源,写入速度根本没减少。于是咱们开始一探到底,咱们选取了一个索引进行验证,该索引应用 10 个 ES 节点。从下图看到,写入速度不到 20w/s,10 个 ES 节点的 cpu,峰值在 40-50% 之间。

为了确认客户端资源是足够的,在客户端不做任何调整的状况下,将索引从 10 个节点,扩容到 16 个节点,从下图看到,写入速度来到了 30w/ s 左右。

这证实了瓶颈出在服务端,ES 节点扩容后,性能晋升,阐明 10 个节点写入曾经达到瓶颈。然而上图能够看到,CPU 最多只到了 50%,而且此时 IO 也没达到瓶颈。

2.2 ES 写入模型阐明

这里要先对 ES 写入模型进行阐明,上面剖析起因会跟写入模型无关。

客户端个别是筹备好一批数据写入 ES,这样能极大缩小写入申请的网络交互,应用的是 ES 的 BULK 接口,申请名为 BulkRequest。这样一批数据写入 ES 的 ClientNode。ClientNode 对这一批数据按数据中的 routing 值进行散发,组装成一批 BulkShardRequest 申请,发送给每个 shard 所在的 DataNode。发送 BulkShardRequest 申请是异步的,然而 BulkRequest 申请须要期待全副 BulkShardRequest 响应后,再返回客户端。

2.3 寻找起因

咱们在 ES ClientNode 上有记录 BulkRequest 写入 slowlog。

  • items是一个 BulkRequest 的发送申请数
  • totalMills 是 BulkRequest 申请的耗时
  • max记录的是耗时最长的 BulkShardRequest 申请
  • avg记录的是所有 BulkShardRequest 申请的均匀耗时。

我这里截取了局部示例。

[xxx][INFO][o.e.m.r.RequestTracker] [log6-clientnode-sf-5aaae-10] bulkDetail||requestId=null||size=10486923||items=7014||totalMills=2206||max=2203||avg=37
[xxx][INFO][o.e.m.r.RequestTracker] [log6-clientnode-sf-5aaae-10] bulkDetail||requestId=null||size=210506||items=137||totalMills=2655||max=2655||avg=218

从示例中能够看到,2 条记录的 avg 相比 max 都小了很多。一个 BulkRequest 申请的耗时,取决于最初一个 BulkShardRequest 申请的返回。这就很容易联想到分布式系统的长尾效应。

接下来再看一个景象,咱们剖析了某个节点的 write 线程的状态,发现节点有时候 write 线程全是 runnable 状态,有时候又有大量在 waiting。此时写入是有瓶颈的,runnable 状态能够了解,但却经常出现 waiting 状态。所以这也能印证了 CPU 利用率不高。同时也论证长尾效应的存在,因为长尾节点忙碌,ClientNode 在期待忙碌节点返回 BulkShardRequest 申请,其余节点可能呈现绝对闲暇的状态。上面是一个节点 2 个时刻的线程状态:

时刻一:

时刻二:

2.4 瓶颈剖析

谷歌大神 Jeffrey Dean《The Tail At Scale》介绍了长尾效应,以及导致长尾效应的起因。总结下来,就是失常申请都很快,然而偶然单次申请会特地慢。这样在分布式操作时会导致长尾效应。咱们从 ES 原理和实现中剖析,造成 ES 单次申请特地慢的起因。发现了上面几个因素会造成长尾问题:

2.4.1 lucene refresh

咱们关上 lucene 引擎外部的一些日志,能够看到:

write 线程是用来解决 BulkShardRequest 申请的,然而从截图的日志能够看到,write 线程也会会进行 refresh 操作。这外面的实现比较复杂,简略说,就是 ES 定期会将写入 buffer 的数据 refresh 成 segment,ES 为了避免 refresh 不过去,会在 BulkShardRequest 申请的时候,判断以后 shard 是否有正在 refresh 的工作,有的话,就会帮忙一起摊派 refresh 压力,这个是在 write 线程中进行的。这样的问题就是造成单次 BulkShardRequest 申请写入很慢。还导致长时间占用了 write 线程。在 write queue 的起因会具体介绍这种危害。

2.4.2 translog ReadWriteLock

ES 的 translog 相似 LSM-Tree 的 WAL log。ES 实时写入的数据都在 lucene 内存 buffer 中,所以须要依赖写入 translog 保证数据的可靠性。ES translog 具体实现中,在写 translog 的时候会上 ReadLock。在 translog 过期、翻滚的时候会上 WriteLock。这会呈现,在 WriteLock 期间,实时写入会期待 ReadLock,造成了 BulkShardRequest 申请写入变慢。咱们配置的 tranlog 写入模式是 async,失常开销是十分小的,然而从图中能够看到,写 translog 偶然可能超过 100ms。

2.4.3 write queue

ES DataNode 的写入是用规范的线程池模型是,提供一批 active 线程,咱们个别配置为跟 cpu 个数雷同。而后会有一个 write queue,咱们配置为 1000。DataNode 接管 BulkShardRequest 申请,先将申请放入 write queue,而后 active 线程有空隙的,就会从 queue 中获取 BulkShardRequest 申请。这种模型下,当写入 active 线程忙碌的时候,queue 中会沉积大量的申请。这些申请在期待执行,而从 ClientNode 角度看,就是 BulkShardRequest 申请的耗时变长了。上面日志记录了 action 的 slowlog,其中 waitTime 就是申请期待执行的工夫,能够看到等待时间超过了 200ms。

[xxx][INFO][o.e.m.r.RequestTracker] [log6-datanode-sf-4f136-100] actionStats||action=indices:data/write/bulk[s][p]||requestId=546174589||taskId=6798617657||waitTime=231||totalTime=538

[xxx][INFO][o.e.m.r.RequestTracker] [log6-datanode-sf-4f136-100] actionStats||action=indices:data/write/bulk[s][p]||requestId=546174667||taskId=6949350415||waitTime=231||totalTime=548

###2.4.4 JVM GC

ES 失常一次写入申请根本在亚毫秒级别,然而 jvm 的 gc 可能在几十到上百毫秒,这也减少了 BulkShardRequest 申请的耗时。这些减轻长尾景象的 case,会导致一个状况就是,有的节点很忙碌,发往这个节点的申请都 delay 了,而其余节点却闲暇下来,这样整体 cpu 就无奈充分利用起来。

2.5 论证论断

长尾问题次要来自于 BulkRequest 的一批申请会扩散写入多个 shard,其中有的 shard 的申请会因为上述的一些起因导致响应变慢,造成了长尾。如果每次 BulkRequest 只写入一个 shard,那么就不存在写入期待的状况,这个 shard 返回后,ClientNode 就能将后果返回给客户端,那么就不存在长尾问题了。

咱们做了一个验证,批改客户端 SDK,在每批 BulkRequest 写入的时候,都传入雷同的 routing 值,而后写入雷同的索引,这样就保障了 BulkRequest 的一批数据,都写入一个 shard 中。

优化后,第一个安稳曲线是,每个 bulkRequest 为 10M 的状况,写入速度在 56w/ s 左右。之后将 bulkRequest 改为 1M(10M 差不多有 4000 条记录,之前写 150 个 shard,所以 bulkSize 比拟大)后,性能还有进一步晋升,达到了 65w/s。

从验证后果能够看到,每个 bulkRequest 只写一个 shard 的话,性能有很大的晋升,同时 cpu 也能充分利用起来,这合乎之前单节点压测的 cpu 利用率预期。

3. 性能优化

从下面的写入瓶颈剖析,咱们发现了 ES 无奈将资源用满的起因来自于分布式的长尾问题。于是咱们着重思考如何打消分布式的长尾问题。而后也在探寻其余的优化点。整体性能优化,咱们分成了三个方向:

  • 横向优化,优化写入模型,打消分布式长尾效应。
  • 纵向优化,晋升单节点写入能力。
  • 利用优化,探索业务节俭资源的可能。

这次的性能优化,咱们在这三个方向上都获得了一些冲破。

3.1 优化写入模型

写入模型的优化思路是将一个 BulkRequest 申请,转发到尽量少的 shard,甚至只转发到一个 shard,来缩小甚至打消分布式长尾效应。咱们实现的写入模型优化,最终能做到一个 BulkRequest 申请只转发到一个 shard,这样就打消了分布式长尾效应。

写入模型的优化分成两个场景。一个是数据不带 routing 的场景,这种场景用户不依赖数据分布,比拟容易优化的,能够做到只转发到一个 shard。另一个是数据带了 routing 的场景,用户对数据分布有依赖,针对这种场景,咱们也实现了一种优化计划。

3.1.1 不带 routing 场景

因为用户对 routing 散布没有依赖,ClientNode 在解决 BulkRequest 申请中,给 BulkRequest 的一批申请带上了雷同的随机 routing 值,而咱们生成环境的场景中,一批数据是写入一个索引中,所以这一批数据就会写入一个物理 shard 中。

3.1.2 带 routing 场景

上面着重介绍下咱们在带 routing 场景下的实现计划。这个计划,咱们须要在 ES Server 层和 ES SDK 都进行优化,而后将两者综合应用,来达到一个 BulkRequest 上的一批数据写入一个物理 shard 的成果。优化思路 ES SDK 做一次数据散发,在 ES Server 层做一次随机写入来让一批数据写入同一个 shard。

先介绍下 Server 层引入的概念,咱们在 ES shard 之上,引入了逻辑 shard 的概念,命名为number_of_routing_size。ES 索引的实在 shard 咱们称之为物理 shard,命名是number_of_shards

物理 shard 必须是逻辑 shard 的整数倍,这样一个逻辑 shard 能够映射到多个物理 shard。一组逻辑 shard,咱们命名为 slot,slot 总数为number_of_shards / number_of_routing_size

数据在写入 ClientNode 的时候,ClientNode 会给 BulkRequest 的一批申请生成一个雷同的随机值,目标是为了让写入的一批数据,都能写入雷同的 slot 中。数据流转如图所示:

最终计算一条数据所在 shard 的公式如下:

slot = hash(random(value)) % (number_of_shards/number_of_routing_size)
shard_num = hash(_routing) % number_of_routing_size + number_of_routing_size * slot

而后咱们在 ES SDK 层进一步优化,在 BulkProcessor 写入的时候减少逻辑 shard 参数,在 add 数据的时候,能够按逻辑 shard 进行 hash,生成多个 BulkRequest。这样发送到 Server 的一个 BulkRequest 申请,只有一个逻辑 shard 的数据。最终,写入模型变为如下图所示:

通过 SDK 和 Server 的两层作用,一个 BulkRequest 中的一批申请,写入了雷同的物理 shard。

这个计划对写入是十分敌对的,然而对查问会有些影响。因为 routing 值是对应的是逻辑 shard,一个逻辑 shard 要对应多个物理 shard,所以用户带 routing 的查问时,会去一个逻辑 shard 对应的多个物理 shard 中查问。

咱们针对优化的是日志写入的场景,日志写入场景的特色是写多读少,而且读写比例差异很大,所以在理论生产环境中,查问的影响不是很大。

3.2 单节点写入能力晋升

单节点写入性能晋升次要有以下优化:

backport 社区优化,包含上面 2 方面:

  • merge 社区 flush 优化个性:[#27000] Don’t refresh on _flush _force_merge and _upgrade
  • merge 社区 translog 优化个性,包含上面 2 个:
  • [#45765] Do sync before closeIntoReader when rolling generation to improve index performance
  • [#47790] sync before trimUnreferencedReaders to improve index preformance

这些个性咱们在生产环境验证下来,性能大略能够带来 18% 的性能晋升。

咱们还做了 2 个可选性能优化点:

  • 优化 translog,反对动静开启索引不写 translog,不写 translog 的话,咱们能够不再触发 translog 的锁问题,也能够缓解了 IO 压力。然而这可能带来数据失落,所以目前咱们做成动静开关,能够在须要追数据的时候长期开启。后续咱们也在思考跟 flink 团队联合,通过 flink checkpoint 保证数据可靠性,就能够不依赖写入 translog。从生产环境咱们验证的状况看,在写入压力较大的索引上开启不写 translog,能有 10-30% 不等的性能晋升。
  • 优化 lucene 写入流程,反对在索引上配置在 write 线程不同步 flush segment,解决后面提到长尾起因中的 lucene refresh 问题。在生产环境上,咱们验证下来,能有 7 -10% 左右的性能晋升。

3.2.1 业务优化

在本次进行写入性能优化探索过程中,咱们还和业务一起发现了一个优化点,业务的日志数据中存在 2 个很大的冗余字段(args、response),这两个字段在日志原文中存在,还另外用了 2 个字段存储,这两个字段并没有加索引,日志数据写入 ES 时能够不从日志中解析出这 2 个字段,在查问的时候间接从日志原文中解析进去。

不荡涤大的冗余字段,咱们验证下来,能有 20% 左右的性能晋升,该优化同时还带来了 10% 左右存储空间节约。

4. 生产环境性能晋升后果

4.1 写入模型优化

咱们重点看下写入模型优化的成果,上面的优化,都是在客户端、服务端资源没做任何调整的状况下的生产数据。

下图所示索引开启写入模型优化后,写入 tps 间接从 50w/s,晋升到 120w/s。

生产环境索引写入性能的晋升比例跟索引混部状况、索引所在资源大小(长尾问题影响水平)等因素影响。从理论优化成果看,很多索引都能将写入速度翻倍,如下图所示:

4.2 写入回绝量 (write rejected) 降落

而后再来看一个要害指标,写入回绝量(write rejected)。ES datanode queue 满了之后就会呈现 rejected。

rejected 异样带来个危害,一个是个别节点呈现 rejected,阐明写入队列满了,大量申请在队列中期待,而 region 内的其余节点却可能很闲暇,这就造成了 cpu 整体利用率上不去。

rejected 异样另一个危害是造成失败重试,这减轻了写入累赘,减少了写入提早的可能。

优化后,因为一个 bulk 申请不再分到每个 shard 上,而是写入一个 shard。一来缩小了写入申请,二来不再须要期待全副 shard 返回。

4.3 提早状况缓解

最初再来看下写入提早问题。通过优化后,写入能力失去大幅晋升后,极大的缓解了以后的提早状况。上面截取了集群优化前后的提早状况比照。

5. 总结

这次写入性能优化,滴滴 ES 团队获得了突破性停顿。写入性能晋升后,咱们用更少的 SSD 机器撑持了数据写入,撑持了数据冷热拆散和大规格存储物理机的落地,在这过程中,咱们下线了超过 400 台物理机,节俭了每年千万左右的服务器老本。在整个优化过程中,咱们深入分析 ES 写入各个环节的耗时状况,去探寻每个耗时环节的优化点,对 ES 写入细节有了更加粗浅的意识。咱们还在继续探寻更多的优化形式。而且咱们的优化不仅在写入性能上。在查问的性能和稳定性,集群的元数据变更性能等等方面也都在一直摸索。咱们也在继续探索如何给用户提交高牢靠、高性能、低成本、更易用的 ES,将来会有更多干货分享给大家。

团队介绍

滴滴云平台事业群滴滴搜寻平台在开源 Elasticsearch 根底上提供企业级的海量数据的 binlog 数仓,数据分析、日志搜寻,全文检索等场景的服务。通过多年的技术积淀,基于滴滴深度定制的 Elasticsearch 内核,打造了稳固易用,低成本、高性能的搜寻服务。滴滴搜寻平台除了服务滴滴外部应用 Elasticsearch 的全副业务,还在进行商业化输入,已和多家公司开展商业单干。目前团队外部有三位 Elasticsearch Contributor。

作者简介

滴滴 Elasticsearch 引擎负责人,负责率领引擎团队深刻 Elasticsearch 内核,解决在海量规模下 Elasticsearch 遇到的稳定性、性能、老本方面的问题。曾在隆重、网易工作,有丰盛的引擎建设教训。

延长浏览

内容编辑 | Charlotte
分割咱们 | DiDiTech@didiglobal.com

滴滴技术 出品

退出移动版