某百亿级mongodb业务只保存近期7天的数据,因为数据量大、流量高,数据过时删除点比较集中,同时不能错峰方式解决问题,所以如何利用最小物理成原本知足业务需求就成为了本集群性能优化的难点。mysql
经过几轮和业务配合调优,包括存储引擎调优、数据删除方式调优、业务错峰读写等,最终完美解决了业务痛点,达到ms级业务读写访问。git
关于做者
前滴滴出行专家工程师,现任OPPO文档数据库mongodb负责人,负责数万亿级数据量文档数据库mongodb内核研发、性能优化及运维工做,一直专一于分布式缓存、高性能服务端、数据库、中间件等相关研发。后续持续分享《MongoDB内核源码设计、性能优化、最佳运维实践》,Github帐号地址:https://github.com/y123456yzgithub
序言
本文是oschina专栏《mongodb 源码实现、调优、最佳实践系列》的第24篇文章,其余文章能够参考以下连接:sql
Qcon-万亿级数据库 MongoDB 集群性能数十倍提高及机房多活容灾实践mongodb
Qcon 现代数据架构 -《万亿级数据库 MongoDB 集群性能数十倍提高优化实践》核心 17 问详细解答数据库
百万级高并发 mongodb 集群性能数十倍提高优化实践 (上篇)缓存
百万级高并发 mongodb 集群性能数十倍提高优化实践 (下篇)性能优化
Mongodb特定场景性能数十倍提高优化实践(记一次mongodb核心集群雪崩故障)网络
经常使用高并发网络线程模型设计及mongodb线程模型优化实践架构
盘点 2020 | 我要为分布式数据库 mongodb 在国内影响力提高及推广作点事
话题讨论 | mongodb 拥有十大核心优点,为什么国内知名度远不如 mysql 高?
mongodb 详细表级操做及详细时延统计实现原理 (快速定位表级时延抖动)
[图、文、码配合分析]-Mongodb write 写 (增、删、改) 模块设计与实现
Mongodb集群搭建一篇就够了-复制集模式、分片模式、带认证、不带认证等(带详细步骤说明)
300条数据变动引起的血案-记某十亿级核心mongodb集群部分请求不可用故障踩坑记
记某千亿级IOT业务迁移mongodb成本节省及性能优化实践(附性能对比质疑解答)
- 业务背景
线上某业务数据量约100亿,白天为写流量高峰期,峰值写约14W/s,以下图所示:
业务天天白天生产数据,同时凌晨批量拉取过去几天数据作大数据分析,整个集群只保存最近七天数据。单条数据约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.}
- mongodb资源评估及部署架构
经过和业务对接梳理,该集群规模及业务需求总结以下:
- 数据量百亿级
- 单条数据800字节,100亿条预计7.5T数据
- 读写分离
- 全部数据只保留七天
2.1 mongodb资源评估
分片数及存储节点套餐规格选定评估过程以下:
- 内存评估
我司都是容器化部署,以以网经验来看,mongodb对内存消耗不高,历史百亿级以上mongodb集群单个容器最大内存基本上都是64Gb,所以内存规格肯定为64G。
- 分片评估
业务流量峰值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磁盘
3.2 集群部署架构
该业务数据不是很重要,为了节省成本,所以咱们采用2+1模式部署,也就是:2mongod+1arbiter模式,同城机房部署,部署架构图以下图所示:
考虑到数据重要性不高,经过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,该功能开启对性能有必定影响,参考:
OPPO百万级高并发MongoDB集群性能数十倍提高优化实践
- 存储引擎cacheSize规格选择
单个容器规格:16CPU、64G内存、7T磁盘,考虑到全量迁移过程当中对内存压力,内存碎片等压力会比较大,为了不OOM,设置cacheSize=42G。
5.2 业务使用过程当中遇到的问题及性能优化
5.2.1 第一轮优化:存储引擎优化
业务高峰期主要是数据写入和更新,内存脏数据较多,当脏数据比例达到必定比例后用户读写请求对应线程将会阻塞,用户线程也会去淘汰内存中的脏数据page,最终写性能降低明显。
wiredtiger存储引擎cache淘汰策略相关的几个配置以下:
因为业务全量迁移数据是持续性的大流量写,而不是突发性的大流量写,所以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 第一轮优化后存在的问题
通过存储引擎后台线程数的调优后,数据写入和更新瓶颈解决,写入和更新过程时延都很平滑。可是随着一周后开始数据过时,业务写开始大量抖动,以下所示:
从上图能够看出平均时延极端状况下甚至达到了几百ms,这啥业务彻底接受不了的。经过mongostat监控发现以下现象:
- 主节点mongostat监控统计
从上面的监控能够看出,三个分片的每一个主节点只有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统计
上图为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天数据,第九天凌晨删除第一天的表
该方案业务时延统计对应收益以下:
如上图所示,经过过时方式的优化,最终问题完全解决,而且读写时延控制在0.5ms-2ms。
6 优化总结
经过前面的一系列优化,最终没有扩容,而且解决了业务过时和大量数据写入更新引发的时延抖动问题,整体收益以下:
- 优化前时延:常常几百ms抖动
- 优化后时延:0.5-2ms