某智能产品业务数据之前存储在 Elasticsearch (Es) 中,磁盘占用约 30T( 按照单副本计算 ) ,总数据量 25 亿,按照不同业务分类分别存在于不同表中。迁移前,业务存在较严重的性能及成本问题,当前业务已经迁移部分数据到 mongodb 中,迁移后效果明显,成本实现十倍级节省,业务抖动问题也得以解决。
当前我司已有数百亿Es 数据迁移 mongodb ,同时也有数百亿 mongodb 迁移 Es ,根本原因业务就是选型错误引起。
本文以该场景业务迁移作为案例,主要分享以下方面的内容:
l mongodb 适用场景及不适用场景
l mongodb 和 Es 各自优势
l mongodb 和 Es 同样数据,真实磁盘消耗对比
l 对 《从MongoDB 迁移到 ES 后,我们减少了 80% 的服务器》 一文的一些不同看法
没有万能的数据库,本文最后会总结mongodb 和 Es 各自的适用场景,以客观立场分析评价 mongodb 和 Es ,拒绝“捧一个,踩一个”。
Es 绝对是一款优秀的搜索引擎,在模糊匹配、全文搜索、复杂检索等方面相比 mongodb 拥有更大的优势。
本文对应业务场景查询比较简单,查询更新等条件都是固定字段,不涉及复杂检索,所以在此场景mongodb 更具优势。另外, mongodb 当前默认的 wiredtiger 存储引擎,在高压缩、高性能、锁粒度等方面进一步提升了 mongodb 在该场景下的优势。
我司已有多个业务数百亿级数据从Es 迁移 mongodb ,由于其他业务迁移 mongodb 的时间较早,之前没有详细记录迁移前后的 ES 和 mongodb 详细资源对比,只有大概资源消耗比值。为了尽量客观评价迁移前后的数据对比,因此选择近期正在从 Es 迁移 mongodb 的一个集群,同时记录源集群和目的集群的详细资源占用情况,这样的对比结果会更加客观真实。
1. 业务背景
该业务存储智能产品相关数据,总数据量20 多亿,单个集群 ES 磁盘消耗约 30T 。业务迁移背景如下 ( 以下为业务开发同事整理 ) :
① Es 集群不太稳定,造成秒级耗时,对我们业务影响挺大,感知非常明显。
② 具体我们业务需求,主要是根据用户id 来进行精确查询,没有复杂全文检索、模糊查询等需求,所以其实用不到 Es 的优点。
③ Es 成本太高。
2. 源Elasticsearch集群资源及部署情况
源ES 集群业务在两个机房各申请了一个集群,由业务自己通过双写的方式来保障数据一致性,当一个集群异常业务自己切流量到另外一个集群。
源ES 集群部署架构及资源规格如下:
2.1 源 Elasticsearch 集群部署架构

如上图所示,由于为了实现两机房双活容灾及单集群抖动引起的业务故障,在A 机房和 B 机房各搭建了一个 ES 集群,业务通过双写来自己维护两个集群的数据一致性。
当A 机房集群 1 异常,或者 A 机房掉电,则业务切流量到 B 机房备集群 2 ,对应架构图如下:

2.2 集群资源规格
A 机房集群 1 和 B 机房集群 2 内部部署架构完全已有,单个集群总共有 26 个节点,每个节点都部署在容器中,单个容器规格资源如下所示:
l CPU : 32
l 内存:64G
l 磁盘:2T
l 磁盘类型:SSD
单个集群总资源消耗如下:
l CPU : 32*26=832
l 内存:64G*32=2048G
l 磁盘:2T*32=64T
两个集群总资源消耗如下:
l CPU : 32*26*2=1664
l 内存:64G*32*2=4096G
l 磁盘:2T*32*2=128T
2.3 源集群架构业务痛点
从上面的分析可以看出,为了保障业务多活和集群高可用,业务通过双写实现,异常后业务自己判断切换,这增加了业务痛点。该架构主要痛点如下:
① 成本高,本身每个集群都是多副本,第2 个备集群进一步增加了成本
② 增加了业务开发难度,业务需要双写逻辑
③ 数据一致性无法得到保障,例如集群1 异常或者故障,业务读写切到集群 2 ,当集群 1 恢复正常,异常这断时间内,集群 2 的数据会比集群 1 多。
④ 不利于业务快速迭代开发
⑤ 业务只是按照固定字段做查询,查询条件单一,这种场景选择mongodb 本身性能会更好。
3. 目的mongodb集群架构
业务开始迁移mongodb 的时候,通过和业务对接梳理,该集群规模及业务需求总结如下:
① 总数据量20 多亿
② Es 数据单集群磁盘消耗总和 30.5T 左右
③ 读写峰值流量流量很小,几百上千
④ 同城两机房多活容灾
3.1 mongodb 资源评估
分片数及存储节点套餐规格选定评估过程如下:
l 内存评估
我司都是容器化部署,以以网经验来看,mongodb 对内存消耗不高,历史百亿级以上 mongodb 集群单个容器最大内存基本上都是 64Gb ,因此内存规格确定为 64G 。
l 分片评估
业 务读写流量很低,但是数据量较大,因此分片数确定为2 个分片。
l 磁盘评估
按照以往测试验证及线上真实数据迁移对比,同样的数据存入mongodb 和 Es 中真实磁盘消耗占比如下:
mongodb:Es ≈ 1 : 6
25 亿 Es 真实磁盘消耗 30.5T ,预计 mongodb 磁盘消耗 5T 左右,考虑到未来数据增长,我们按照 50 亿数据计算,预计需要 10T 空间。 2 个分片,因此每个分片 5T 数据,最终确定单个 mongod 实例容器磁盘规格 5T 。
l CPU 规格评估
由于容器调度套餐化限制,因此CPU 只能限定为 16CPU( 实际上用不了这么多 CPU) 。
l mongos 代理及 config server 规格评估
此外,由于分片集群还有mongos 代理和 config server 复制集,因此还需要评估 mongos 代理和 config server 节点规格。由于 config server 只主要存储路由相关元数据,因此对磁盘、 CUP 、 MEM 消耗都很低; mongos 代理只做路由转发只消耗 CPU ,因此对内存和磁盘消耗都不高。最终,为了最大化节省成本,我们决定让一个代理和一个 config server 复用同一个容器,容器规格如下:
8CPU/8G 内存 /50G 磁盘,一个代理和一个 config server 节点复用同一个容器。
分片及存储节点规格总结: 2 分片 /16CPU 、 64G 内存、 5T 磁盘。
mongos 及 config server 规格总结: 8CPU/8G 内存 /50G 磁盘

3.2 集群部署架构
由于该业务所在城市只有两个机房,因此我们采用2+2+1(2mongod+2mongod+1arbiter 模式 ) ,在 A 机房部署 2 个 mongod 节点, B 机房部署 2 个 mongod 节点, C 机房部署一个最低规格的选举节点,如下图所示:

说明:
① 每个机房代理部署2 个 mongos 代理,保证业务访问代理高可用,任一代理挂掉,对应机房业务不受影响。
② 如果机房A 挂掉,则机房 B 和机房 C 剩余 2mongod+1arbiter ,则会在 B 机房 mongod 中从新选举一个主节点。 arbiter 选举节点不消耗资源
③ 客户端配置nearest ,实现就近读,确保请求通过代理转发的时候,转发到最近网络时延节点,也就是同机房对应存储节点读取数据。
弊端:如果是异地机房,B 机房和 C 机房写存在跨机房写场景。如果 A B C 为同城机房,则没用该弊端,同城机房时延可以忽略。
4. 性能优化过程
该集群优化过程按照如下两个步骤优化:数据迁移开始前的提前预优化、迁移过程中瓶颈分析及优化、迁移完成后性能优化。
4.1 数据迁移开始前的提前预操作
和业务沟通确定,业务每条数据都携带有唯一_id (用户生成的,不是 mongodb 内部生成),同时业务查询更新等都是根据 _id 维度查询该设备下面的单条或者一批数据,因此片建选择 _id 。
l 分片方式
为了充分散列数据到2 个分片,因此选择 hash 分片方式,这样数据可以最大化散列,同时可以满足同一个 _id 数据落到同一个分片,保证查询效率。
l 预分片
mongodb 如果分片片建为 hashed 分片,则可以提前做预分片,这样就可以保证数据写进来的时候比较均衡的写入多个分片。预分片的好处可以规避非预分片情况下的 chunk 迁移问题,最大化提升写入性能。
sh.shardCollection("user_xxx.user_xxx", {_id:"hashed"}, false, { numInitialChunks: 8192} )
注意事项: 切记提前对ssoid 创建 hashed 索引,否则对后续分片扩容有影响。
l 就近读
客户端增加nearest 配置,从离自己最近的节点读,保证了读的性能。
l mongos代理配置
A机房业务只配置A机房的代理,B机房业务只配置B机房代理,同时带上nearest配置,最大化的实现本机房就近读,同时避免客户端跨机房访问代理。
l 禁用enableMajorityReadConcern
禁用该功能后ReadConcern majority将会报错,ReadConcern majority功能注意是避免脏读,和业务沟通业务没该需求,因此可以直接关闭。
mongodb默认使能了enableMajorityReadConcern,该功能开启对性能有一定影响,参考:
OPPO百万级高并发MongoDB集群性能数十倍提升优化实践
l 存储引擎cacheSize规格选择
单个容器规格:16CPU、64G内存、7T磁盘,考虑到全量迁移过程中对内存压力,内存碎片等压力会比较大,为了避免OOM,设置cacheSize=42G。
4.2 数据迁移过程中优化过程
全量数据迁移过程中,迁移速度较块,内存涨数据较多,当脏数据比例达到一定比例后用户读写请求对应线程将会阻塞,用户线程也会去淘汰内存中的脏数据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)"})
更多存储引擎及mongodb 内核涉及实现参考: mongodb 源码分析、更多实践案例细节
4.3 业务流量读写优化
前面章节我们提到,在容器资源评估的时候,我们最终确定选择单个容器套餐规格为如下:
16CPU 、 64G 内存、 5T 磁盘。
全量迁移过程中为了避免OOM ,预留了约 1/3 内存给 mongodb server 层、操作系统开销等,当数据迁移完后,业务写流量相比全量迁移过程小了很多。
也就是说,前量迁移完成后,cache 中涨数据比例几乎很少,基本上不会达到 20% 阀值,业务读流量相比之前多了很多 ( 数据迁移过程中读流量走原 Es 集群 ) 。为了提升读性能,因此做了如下性能调整 ( 提前建好索引 ) :
l 节点cacheSize 从之前的 42G 调整到 55G ,尽量多的缓存热点数据到内存,供业务读,最大化提升读性能。
l 每天凌晨低峰期做一次cache 内存加速释放,避免 OOM 。
5. 迁移mongodb后性能对比
当前已有2 个表从 Es 迁移到该 mongodb 集群,同时该业务新增了 15 亿其他业务数据到该集群,当前目的 mongodb 集群已有近 20 亿数据。
5.1 Es 时延情况
由于该集群ES 没有历史时延统计曲线统计,因此 ES 的时延统计只有以下现象 ( 来自业务方反馈 ) :
查询秒级耗时,对我们业务影响挺大,感知非常明显。
5.2 mongodb 集群时延曲线

从上面的监控可以看出,由于除了迁移有源Es 的数据,另外还有该业务的其他业务数据流量流向该集群,因此 mongodb 集群流量相比 Es 会更高, mongodb 整体时延约 1.5ms 左右,远远好于之前 Es 的有时秒级时延抖动。
6. 迁移成本收益对比
6.1 ElasticSearch 集群规格
原Es 单个集群一共 26 个节点,每个节点副本容器规格: 32CPU 、 64Gmem 、 2T 磁盘,磁盘类型 SSD ,单个集群规格总结如下:
① 单集群节点总数:26
② 每个节点规格:32CPU 、 64Gmem 、 2T 磁盘
③ 总数据量:25 亿
④ 为了实现机房多活容灾和业务高可用,实际部署了两个Es 集群,实际规格成本还的在上面的基础上增加一倍。
6.2 mongodb 集群规格

当前该mongodb 集群已有约 16 亿数据 ( 其中部分为 Es 集群以外数据,该集群除了存储部分 Es 迁移过来的数据,还存储该业务线其他业务数据 ) ,该 mongodb 集群规格如下:
① 分片数:2
② 单分片副本数:4
③ 每个节点规格:16CPU 、 64G mem 、 5T 磁盘
④ 两个分片预计存储最大数据量:预计存储Es 集群中总数据量的两倍。
6.3 成本对比计算过程
l CPU 、 MEM 内存成本对比计算过程
源Es 两个集群和目的 mongodb 集群资源对比如下:

说明: 由于集群部署方式可能有很多冗余,上面的CPU 和内存成本比对比实际上不客观,可能 Es 部署时候规格设置浪费。当然, mongodb 实际上 CPU 资源也非常空闲,所以 CPU 和内存指标对比无太大参考作用。
l 磁盘成本对比计算过程( 成本比约 6 : 1)
由于目的mongodb 集群中当前除了有源 ES 迁移过来的部分表外,还有该业务的其他数据,为了保障磁盘对比的客观性,磁盘对比选材过程如下 ( 说明:源 Es 有两个集群,这里只计算单个集群单分数据的磁盘消耗,如果按照两个集群计算,磁盘成本比为 12:1 ,这种对比方法不客观 ) :
① 只对比从源Es 中迁移到 mongodb 中的表
② 只对比源Es 集群和目的 mongodb 集群表中数据量完全一样的表
③ 排除源ES 迁移到 mongodb 但是当前还没有迁移完的表
④ 源Es 集群只计算一个集群单副本的磁盘消耗
通过上面的选择算法,基本上做到了同样数据在Es 和 mongodb 中的对比,磁盘真实消耗对比结果如下 ( 说明:以下数据的 Es 磁盘占用为单个 Es 集群单副本方式计算结果,由我司 Es 运维开发人员提供; mongodb 磁盘占用计算方法为 2 个分片主节点真实磁盘占用之和,包括数据磁盘消耗 + 索引磁盘消耗 ) :

从上面的数据对比可以看出,同样的数据Es 磁盘占用约为 mongodb 磁盘占用的 6 倍,和之前其他 Es 迁移 mongodb 过程的数据占用比值类似。
表1 文档内容如下:
1. {
2. "_id" : null,
3. "channel" : null,
4. "content" : "04A193398BE7xxx7E080E2C3CC7B3sxxxxxxxxxC99F9520B8CD0842638DB0F550E125xxxxxxxxxxxxxDB5D3F320642A42CECD3EB5C27714524D0C1BF2A0C6B607D0DFDB669D6633A0E48C65B2623EA15E6DBB0FBF643150E18DD3D0575BDE448C03735A8841E312F8AF0D2BF67D1D357D1AB6249BF3FA4E014C5Axxxxxxxxxxxxxx30C10487667",
5. "create_time" : ISODate("2021-02-16T09:56:03Z"),
6. "duid" : "6F9E856EDDBB5xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxF41B0676FB2DE2171C18450E683DE1E9523B518F266856E01B0D6855E29911E5D10F0FA7E4A8EE5816333D89296E7554F05A58",
7. "update_time" : ISODate("2021-02-16T09:56:03Z")
8. }
表2 文档内容如下:
1. {
2. "_id" : "F9654874C1A37F74DA5E862C408EDC45",
3. "channel" : "10001",
4. "content" : "BA7110279AF5XXXXXXXXX6D7ACB8D0F9XXXXXXXXXXXXXX5CDB4693BE949F70E78A20E",
5. "create_time" : ISODate("2021-01-21T11:49:46Z"),
6. "imei" : "F96548XXXXXXXXXXXXXXXXX2C408EDC45",
7. "update_time" : ISODate("2021-01-21T11:49:46Z")
8. }
7. Elasticsearch 和mongodb各自适用场景总结
由于业务开发对业务场景评估不到位,当前我司线上mongodb 和 Es 适用过程存在如下现象:
1) 数百亿Es 数据迁移 mongodb
2) 也有数百亿mongodb 数据迁移到 Es
从线上真实业务使用情况为例,以下场景不适合mongodb ,实际上也不适合 mysql 等数据库:
l 8 字段以上的随机组合查询
有些业务场景,查询条件是由用户触发,查询条件不固定,可能存在多个字段的随机组合查询。mongodb 和 mysql 等数据库,都需要手动创建索引,由于 8 字段以上的随机组合查询情况种类太多,因此很难手动建索引覆盖所有场景,所以选择 Es 更优,只是成本会更高。
l 全文检索
虽然mongodb 也支持全文检索, mongodb-4.2 以下版本全文检索能力性能和 ES 没法比,建议全文检索适用 Es 。
mongodb-4.2 全文搜索已经开始支持 Lucene 引擎,可能性能会有很大提升,暂时没做研究,也没做性能对比,后续有空在研究。
l 其他复杂检索,例如非前缀模糊匹配查询
例如查询db.member.find({"name":{ $regex:/XXX/ }}) ,查询 name 字段包含 XXX 的查询,这类查询 Es 更优,因为 mongodb 、 mysql 等底层都是 KV 存储,查找 KEY 的时候都是从左到右比较 key 字符串,如果是非前缀匹配模糊查询,就需要全表扫描。
查询以某字段为开头的文档,db.member.find({"name":{$regex:/^XXX/}}) 这类就比较适合用 mongodb 查询,前缀匹配。
mongodb 和 Es 不同场景性能对比 ( 以下为真实线上数据对比 ) :

最后,脱离业务场景评估一个数据库优劣很不合适,主流数据库都优其存在的意义,不能因为数据库在某种场景下不合适而全盘否定该数据库。
8. 对 《从MongoDB迁移到ES后,我们减少了80%的服务器》 一文的不同看法
《从MongoDB 迁移到 ES 后,我们减少了 80% 的服务器》 一文中以下观点个人认不太赞同,主要如下:
8.1 文章内容的不同看法
mongodb 近几年持续排名全球前五,市值近两年已翻数倍,当前市值近 200 亿左右。 DB-Engines Ranking 排名得分持续提升,说明本身有自己得市场和应用场景,而不是浪得虚名,没 《从MongoDB 迁移到 ES 后,我们减少了 80% 的服务器》 一文中说的不堪一击。
l 服务器80% 节省? mongodb 部署架构严重资源浪费
从该文章可以看出,单个文档200 多字节, mongodb 存储引擎 wiredtiger 默认高压缩、高性能、细粒度锁。单个复制集即可存储数十亿数据,你们用了十多个容器。
同样的数据,默认mongodb 磁盘占用是 Es 的六分之一,加上这是日志集群, mongodb 可以采用 1mongod+1mongod+1arbiter 部署,规格 8c/32gb/100gb 2 个容器就可以满足要求。这样的部署才是合理的,成本会比同样数据 Es 减少数倍。如果用 mongodb ,本身 2 个 8c/32gb/100gb+1 个低规格选举节点容器即可搞定的事,你用了 15 台。
l 任意组合的,现有MongoDB 是不支持的?
任意组合的查询不是mongodb 不支持,是建索引麻烦,包括 mysql 、 tidb 等数据库都是需要手动一个一个建索引。
l 性能提高十倍?一个走索引一个不走索引,这种数据对比不客观
这本身就是个数据库选型问题,随机组合条件太多,索引不好建,你应该把索引查询条件对应索引建好后对比。不过多字段的随机组合查询,确实不适合用mongodb ,建索引麻烦。
建议把mongodb 对应查询索引建好,重新测试下查询性能数据。
l MongoDB 单集合数据量超过10 亿条,此情况下即使简单条件查询性能也不理想 ?
我司最大的mongodb 集群单表几千亿数据,查询 2ms 以内。
10 亿规模对我们线上业务就是毛毛雨,我们把 mongodb 集群归类为如下几档:
① 小规模集群:数据量<100 亿
② 中型集群:数据量100 亿到 1000 亿之间
③ 大型集群:数据量大于1000 亿
当前我司15%-20% 左右集群是百亿级以上集群,已成功实现单个集群 万亿级离线数据 读写存储,当前正在挑战单个 万亿级实时在线数据 高并发在线读写。
万亿级离线数据读写优化案例详见Qcon 、 dbaplus 、 mongodb 社区等分享: 用最少人力玩转万亿级数据,我用的就是MongoDB !
后续将分享《单集群万亿级在线数据高并发读写优化实践》
l 没有人敢在核心项目中使用 MongoDB ?
我司25%-35% 以上数据存储文件、图片等元数据,甚至包括少量交易集群,非常核心,当前我司规模早已超过万亿级。
8.2 文章以下回复的不同看法
l mongodb 有的, Es 都有?
没有万能的数据库,正如上文所述,mongodb 在高并发写、固定字段索引查询方面的性能表现。此外 mongodb 存储引擎高压缩高性能,在成本上面体现很明显。
业务场景很重要,不能“捧一个,踩一个”,主流数据库都有其存在的理由,排名第五绝不是浪得虚名。
l 只有用事务的才是核心数据?
不太赞成这样定义核心数据,用事务的不一定是核心数据,不用事务的也未必不是核心数据。例如我司存储的几千亿文件、图片等元数据,没有用到事务,但是是非常核心的数据,丢失会造成严重后果。
8.3 业界对数据库几个错误认识 ( 以 OPPO 接入业务过程真实案例分享 )
结合公司内部使用mongodb 为例,有时候出现以下情况:一些不适合 mongodb 的业务场景,例如全文检索、 8 字段以上的随机组合查询、非前缀匹配模糊查询,这些场景本身不适合选用 mongodb ,但是业务选型错误,造成使用过程中的瓶颈。
甚至有相关研发人员因为选型错误在各种群里面说:“远离 mongo ,珍爱生命” ( 该业务有全文检索需求 ) ;此外还存在业务数组索引使用不当引起集群抖动 ( 该业务数组用法没建索引引起 ) ,认为 mongodb 设计有问题,这些都是极不负责的行为。
通过这些真实业务接触过程中的案例,总结如下几点:
① 没有万能的数据库,切记结合自身业务场景,选择最优数据库;
② 主流数据库都有其存在的理由,不要因为在某些场景不适合而全盘否定该数据库其他层面的优势。例如不能因为mongodb 在全文检索等复杂检索上面的弱势而全面否定 mongodb ,也不能因为 Es 在磁盘成本、高并发读写等方面的劣势而否定 Es 在复杂检索上面的优势。
③ 切记以偏概全,捧一个踩一个,这都是及其不客观的行为。