7

杨亚洲-专注mongodb及高性能中间件的个人空间

 3 years ago
source link: https://my.oschina.net/u/4087916/blog/5122200
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

      某百亿级mongodb业务只保存近期7天的数据,由于数据量大、流量高,数据过期删除点比较集中,同时不能错峰方式解决问题,因此如何利用最小物理成本来满足业务需求就成为了本集群性能优化的难点。

      通过几轮和业务配合调优,包括存储引擎调优、数据删除方式调优、业务错峰读写等,最终完美解决了业务痛点,达到ms级业务读写访问。

关于作者

       前滴滴出行专家工程师,现任OPPO文档数据库mongodb负责人,负责数万亿级数据量文档数据库mongodb内核研发、性能优化及运维工作,一直专注于分布式缓存、高性能服务端、数据库、中间件等相关研发。后续持续分享《MongoDB内核源码设计、性能优化、最佳运维实践》,Github账号地址:https://github.com/y123456yz

序言

       本文是oschina专栏《mongodb 源码实现、调优、最佳实践系列》的第24篇文章,其他文章可以参考如下链接:

Qcon-万亿级数据库 MongoDB 集群性能数十倍提升及机房多活容灾实践

Qcon 现代数据架构 -《万亿级数据库 MongoDB 集群性能数十倍提升优化实践》核心 17 问详细解答

百万级高并发 mongodb 集群性能数十倍提升优化实践 (上篇)

百万级高并发 mongodb 集群性能数十倍提升优化实践 (下篇)

Mongodb特定场景性能数十倍提升优化实践(记一次mongodb核心集群雪崩故障)

常用高并发网络线程模型设计及mongodb线程模型优化实践

为何要对开源mongodb数据库内核做二次开发

盘点 2020 | 我要为分布式数据库 mongodb 在国内影响力提升及推广做点事

 百万级代码量 mongodb 内核源码阅读经验分享

话题讨论 | mongodb 拥有十大核心优势,为何国内知名度远不如 mysql 高?

Mongodb 网络模块源码实现及性能极致设计体验

网络传输层模块实现二

网络传输层模块实现三

网络传输层模块实现四

command 命令处理模块源码实现一

command 命令处理模块源码实现二

mongodb 详细表级操作及详细时延统计实现原理 (快速定位表级时延抖动)

[图、文、码配合分析]-Mongodb write 写 (增、删、改) 模块设计与实现

Mongodb集群搭建一篇就够了-复制集模式、分片模式、带认证、不带认证等(带详细步骤说明)

300条数据变更引发的血案-记某十亿级核心mongodb集群部分请求不可用故障踩坑记

记十亿级Es数据迁移mongodb成本节省及性能优化实践

千亿级数据迁移mongodb成本节省及性能优化实践

记某千亿级IOT业务迁移mongodb成本节省及性能优化实践(附性能对比质疑解答)

记某百亿级mongodb集群数据过期性能优化实践

  1. 业务背景

      线上某业务数据量约100亿,白天为写流量高峰期,峰值写约14W/s,如下图所示:

up-48ca2f9183c7daa6b3ed7002e8681d29a88.png

up-ca8c2832954de0d1e76009561cd49b3f6ec.png

      业务每天白天生产数据,同时凌晨批量拉取过去几天数据做大数据分析,整个集群只保存最近七天数据。单条数据约800字节,如下所示:

1.  {  
2.        "_id" : ObjectId("608592008bd3dad61675b491"),  
3.        "deviceSn" : "xxxxxxxxxxxxx",  
4.        "itemType" : 0,  
5.        "module" : "xxxxxx",  
6.        "userId" : "xxxxx",  
7.        "callTimes" : NumberLong(2),  
8.        "capacityAdd" : NumberLong(0),  
9.        "capacityDelete" : ”xxxxxxxx”,  
10.        "capacityDownload" :”xxxxxxxxxxxx”,  
11.        "capacityModify" : ”xxxxxxxxxxxx”,  
12.        "createTime" : NumberLong("1619366400003"),  
13.        "expireAt" : ISODate("2021-05-02T22:53:45.497Z"),  
14.        "numAdd" : NumberLong(2),  
15.        "numDelete" : NumberLong(345),  
16.        "numDownload" : NumberLong(43),  
17.        "numModify" : NumberLong(3),  
18.        "osVersion" : "xxxx",  
19.        "reversedUserId" : "xxxxx",  
20.        "updateTime" : NumberLong("1619366402106")  
21.} 
  1. mongodb资源评估及部署架构

      通过和业务对接梳理,该集群规模及业务需求总结如下:

  • 数据量百亿级
  • 单条数据800字节,100亿条预计7.5T数据
  • 所有数据只保留七天

2.1 mongodb资源评估

      分片数及存储节点套餐规格选定评估过程如下:

  • 内存评估

      我司都是容器化部署,以以网经验来看,mongodb对内存消耗不高,历史百亿级以上mongodb集群单个容器最大内存基本上都是64Gb,因此内存规格确定为64G。

  1. 分片评估

      业务流量峰值10W/s多,预计需要3个分片支持读写。

  • 磁盘评估

    100亿数据7.5T,由于mongodb默认有高压缩,预计真实磁盘占用2.5~3T左右。三个分片,一个分片刚好1T。

  • CPU规格评估

      由于容器调度套餐化限制,因此CPU只能限定为16CPU(实际上用不了这么多CPU)。

  • mongos代理及config server规格评估

       此外,由于分片集群还有mongos代理和config server复制集,因此还需要评估mongos代理和config server节点规格。由于config server只主要存储路由相关元数据,因此对磁盘、CUP、MEM消耗都很低;mongos代理只做路由转发只消耗CPU,因此对内存和磁盘消耗都不高。最终,为了最大化节省成本,我们决定让一个代理和一个config server复用同一个容器,容器规格如下:

       8CPU/8G内存/50G磁盘,一个代理和一个config server节点复用同一个容器。

分片及存储节点规格总结:4分片/16CPU、64G内存、1T磁盘。

mongos及config server规格总结:8CPU/8G内存/50G磁盘

up-f6f98ac8c8fb0a64e3ae7f3e92c67d021ab.png

3.2 集群部署架构

      该业务数据不是很重要,为了节省成本,因此我们采用2+1模式部署,也就是:2mongod+1arbiter模式,同城机房部署,部署架构图如下图所示:

up-35b76bd0e0606726ef7e2ae062ee909eb1d.png

       考虑到数据重要性不高,通过2mongod+1arbiter模式即可满足用户要求,同时可以最大化节省成本。

4. 性能优化过程

       该集群优化过程按照如下两个步骤优化:业务使用集群前的性能优化、业务使用过程中的性能优化。

       业务提前建好查询对应的最优索引,同时创建过期索引:

db.xxx.createIndex( { "createTime": 1 }, { expireAfterSeconds: 604800} )

4.1 业务使用集群前的性能优化

       和业务沟通确定,业务每条数据都携带有一个设备标识userId,同时业务查询更新等都是根据userId维度查询该设备下面的单条或者一批数据,因此片建选择userId。

  • 分片方式

       为了充分散列数据到3个分片,因此选择hash分片方式,这样数据可以最大化散列,同时可以满足同一个userId数据落到同一个分片,保证查询效率。

  • 预分片

       mongodb如果分片片建为hashed分片,则可以提前做预分片,这样就可以保证数据写进来的时候比较均衡的写入多个分片。预分片的好处可以规避非预分片情况下的chunk迁移问题,最大化提升写入性能。

sh.shardCollection("xxx_xx.xxx", {userId:"hashed"}, false, { numInitialChunks: 8192} )

  • 就近读

       客户端增加secondaryPreferred配置,优先读从节点。

  • 禁用enableMajorityReadConcern

       禁用该功能后ReadConcern majority将会报错,ReadConcern majority功能注意是避免脏读,和业务沟通业务没该需求,因此可以直接关闭。

mongodb默认使能了enableMajorityReadConcern,该功能开启对性能有一定影响,参考:

MongoDB readConcern 原理解析

OPPO百万级高并发MongoDB集群性能数十倍提升优化实践

  1. 存储引擎cacheSize规格选择

       单个容器规格:16CPU、64G内存、7T磁盘,考虑到全量迁移过程中对内存压力,内存碎片等压力会比较大,为了避免OOM,设置cacheSize=42G。

5.2 业务使用过程中遇到的问题及性能优化

5.2.1 第一轮优化:存储引擎优化

       业务高峰期主要是数据写入和更新,内存脏数据较多,当脏数据比例达到一定比例后用户读写请求对应线程将会阻塞,用户线程也会去淘汰内存中的脏数据page,最终写性能下降明显。

       wiredtiger存储引擎cache淘汰策略相关的几个配置如下:

up-e0c8d63ac500a066bed9dad285d21b1abee.png

      由于业务全量迁移数据是持续性的大流量写,而不是突发性的大流量写,因此eviction_target、eviction_trigger、eviction_dirty_target、eviction_dirty_trigger几个配置用处不大,这几个参数阀值只是在短时间突发流量情况下调整才有用。

       但是,在持续性长时间大流量写的情况下,我们可以通过提高wiredtiger存储引擎后台线程数来解决脏数据比例过高引起的用户请求阻塞问题,淘汰脏数据的任务最终交由evict模块后台线程来完成。

       全量大流量持续性写存储引擎优化如下:

db.adminCommand( { setParameter : 1, "wiredTigerEngineRuntimeConfig" : "eviction=(threads_min=4, threads_max=20)"})

5.2.2 第一轮优化后存在的问题

       经过存储引擎后台线程数的调优后,数据写入和更新瓶颈解决,写入和更新过程时延都很平滑。但是随着一周后开始数据过期,业务写开始大量抖动,如下所示:

up-fc438153d8c91e69c1910db0ff38661fba7.png

        从上图可以看出平均时延极端情况下甚至达到了几百ms,这啥业务完全接受不了的。通过mongostat监控发现如下现象:

  • 主节点mongostat监控统计

up-55180d8e2cb82547df0f2c26d9f748499f0.png

       从上面的监控可以看出,三个分片的每个主节点只有4000左右的写更新操作(注意:实际上还有4万左右的delete操作,由于主节点过期delete不会统计,因此只能通过从节点查看,详见后面分析,实际单个分片主节点写和删除操作4.4W/s),写流量很低。但是,监控中的脏数据比例持续性的超过20%,超过20%后业务的请求就需要进行脏数据淘汰,最终造成业务请求阻塞抖动。

       通过前面的分析可以看出,业务正常峰值写入3个分片差不多10W/s。一星期后,七天前的数据需要过期,这时候过期删除的ops也需要delete删除10w/S,由于这时候新数据同样按照10w/s写入,因此集群就需要支持20w/s的ops操作才能支持。

       显然,3个分片支持不了持续性的20w/s左右的ops操作,因此如何不扩容情况下支撑业务需求将是一大难点。

  • 为何ttl过期主节点没用delete统计

up-4b82533df7e63cc9c39c41386184a6960a0.png

        上图为mongodb单机模块架构图,主节点默认启用一个TTLMonitor线程,借助查询引起模块实时扫描过期索引,然后把满足条件的数据删除。整个删除过程没有走command命令处理模块,而命令计数操作只会在command模块计数,因此主节点的过期删除不会有delete操作。

命令处理模块处理流程及其计数统计详见:mongodb内核源码模块化设计与实现专栏

      更多mongodb模块化源码设计实现详见:

https://github.com/y123456yz/reading-and-annotate-mongodb-3.6

5.3 第二轮优化(过期删除放凌晨低峰期)-不可行

       从前面的分析可以看出,我们三个分片支撑不了持续性20W/S的更新和删除操作,因此我们考虑业务改造使用方式,把过期删除确定到凌晨低峰期。

       但是业务上线后出现其他的问题,由于业务凌晨会持续性的批量拉取分析过去几天的数据,如果过期和批量读数据叠加到一起,严重影响业务查询效率。最终,该方案不可行,如果不扩容,则需要其他方案。

5.4 第三轮方案优化(天维度建表,过期删表)

       为了不增加成本,同时3个分片又支撑不了20W/s的读写删除等导致,为了尽量通过3个分片不扩容条件下来满足用户需求,因此转变方式,通过删表的方式来避免过期,具体实现如下:

  • 业务改造代码,以天为单位建表,每天的数据存到对应的表中。
  • 建表后业务启用预分片功能,确保数据均衡分布到三个分片。
  • 业务保存8天数据,第九天凌晨删除第一天的表

    该方案业务时延统计对应收益如下:

up-fd29fc126c6e695b0a595194f91a95fafdd.png

up-53e141caac25eeec94e01d77c4bab4ddbb2.png

       如上图所示,通过过期方式的优化,最终问题彻底解决,并且读写时延控制在0.5ms-2ms。

6 优化总结

     通过前面的一系列优化,最终没有扩容,并且解决了业务过期和大量数据写入更新引起的时延抖动问题,总体收益如下:

  • 优化前时延:经常几百ms抖动
  • 优化后时延:0.5-2ms

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK