如何有效地进行 MongoDB 的数据容量规划?
时间:2023-10-18 01:30:01 | 来源:网站运营
时间:2023-10-18 01:30:01 来源:网站运营
如何有效地进行 MongoDB 的数据容量规划?:
二、JSON文档模型设计
2.1、MongoDB文档模型设计的三个误区
- 不需要模型设计
- MongoDB应该用一个超级大文档来组织所有数据
- MongoDB不支持关联或事物
以上这些说法全是错误的。
2.2、关于JOSN文档模型设计
文档模型设计处于是物理模型设计阶段 (PDM)
JSON 文档模型通过内嵌数组或引用字段来表示关系
文档模型设计不遵从第三范式,允许冗余。
严格来说,MongoDB 同样需要概念/逻辑建模
文档模型设计的物理层结构可以和逻辑层类似
2.3、逻辑模型 – JSON 模型
下面是一个JSON模型和逻辑模型的对比, 这里json文档中的联系人,列举出了姓名、性别、创建日期、所属组、和地址;
这样一个物理关系模型中需要多张表,而这里可以直接通过一个json来表述出来。
2.4、文档模式设计原则:性能和易用
在文档模式设计中我们是没有第三范式原则的,这是个好处也是个弊端,很多人不知道该如何设计这个模型。什么样的模型好什么样的模型坏。
我们是有两个关键点,看你这个模型是否性能不错,能够支撑高并发、低延迟的读写。另外一个角度在程序开发过程中是否简易。
这里没有唯一的标准,这里更多的是经验之谈。我们后面给大家些建模的建议。可以尊从文档模式设计三步走:
三、文档模式设计:基础建模
- 根据概念模型或者业务需求推导出逻辑模型 – 找到对象
- 列出实体之间的关系(及基数) - 明确关系
- 套用逻辑设计原则来决定内嵌方式 – 进行建模
- 完成基础模型构建
3.1、1:1关系建立
比如,一个联系人只有一个头像,
- 基本原则:一对一关系以内嵌为主作为子文档形式 或者直接在顶级不涉及到数据冗余;
- 例外情况:如果内嵌后导致文档大小超过16MB
{ "name": "TJ Tang", "company": "TAPDATA", "title": " CTO", "portraits": { "mimetype": "xxx", "data": "xxxx" }}
3.2、一对多关系建立
比如一个联系人可以有多个地址:
- 一对多关系同样以内嵌为主用数组来表示一对多不涉及到数据冗余;
- 例外情况:如果内嵌后导致文档大小超过16MB,数组长度太大(数万或更多),数组长度不确定;
{ "name": "TJ Tang", "company": "TAPDATA", "title": " CTO", "portraits": { "mimetype": "xxx", "data": "xxxx" }, "addresses": [ { "type": "home", .... }, { "type": "work", .... } ]}
3.3、多对多关系建模
比如一个联系人可以有多个地址:
- 一对多关系同样以内嵌为主用数组来表示一对多,通过冗余来实现N-N;
- 例外情况:如果内嵌后导致文档大小超过16MB,数组长度太大(数万或更多),数组长度不确定;
{ "name": "TJ Tang", "company": "TAPDATA", "title": " CTO", "portraits": { "mimetype": "xxx", "data": "xxxx" }, "addresses": [ { "type": "home", .... }, { "type": "work", .... } ], "groups":[ {"name":"FRI"}, {"name":"DF"}, ]}
四、文档模式设计:工况细化
在做工况细化过程中,我们需要根据技术需求对我们模型进行调整,这是个技术导向,我们需要和业务方进行详细的沟通,了解到数据的使用方法,是根据什么来查询的。如:单个查询,报表查询,查询参数是什么、数量有多大、读写比例有多少等等。针对不同需求,我们可以引入引用、关联、或者冗余等手段来解决这些问题。
● 最频繁的数据查询模式
● 最常用的查询参数
● 最频繁的数据写入模式
● 读写操作的比例
● 数据量的大小
比如:联系人管理应用的分组需求
4.1 解决方案:Group 使用单独的集合
类似于关系型设计,使用 id 或者唯一键关联,使用 $lookup 来提供一次查询多表的能力(类似关联)(3.2以上版本支持)。
4.2、联系人的头像: 引用模式
- 头像使用高保真,大小在 5MB-10MB
- 头像一旦上传,一个月不可更换
- 基础信息查询(不含头像)和 头像查询的比例为 9 :1
建议: 使用引用方式,把头像数据放到另外一个集合,可以显著提升 90% 的查询效率。
4.3、什么时候使用引用模式
- 内嵌文档太大,数 MB 或者超过 16MB
- 内嵌文档或数组元素会频繁修改
- 内嵌数组元素会持续增长并且没有封顶
MongoDB 引用设计的限制
- MongoDB 对使用引用的集合之间并无主外键检查
- MongoDB 使用聚合框架的 $lookup 来模仿关联查询
- $lookup 只支持 left outer join
- $lookup 的关联目标(from)不能是分片表
五、文档设计模式:套用设计模式
文档模型:无范式,无思维定式,充分发挥想象力;
设计模式:实战过屡试不爽的设计技巧,快速应用;
举例:一个IoT场景的分桶设计模式,可以帮助把存储空间降低10倍并且查询效率提升数十倍。
问题:物联网场景下的海量数据处理 - 设备监控数据
上报数据格式如下:
{ "_id":"10000000022200000222:CA2091", "icao":"CA2091", "ts":ISODate("2022-04-03T20:21:35.000+0000"), "events":{ "tem":35.3, "humidity":50.3, "lon":38.345, "lat":58.987, "open":"0", "b":"b" "p":[123,245], "s":91 }}
我们假设有10万个设备,每分钟上报一条数据,一年的数据量大约52560*100W = 525亿数据,约10TB;
| 每分钟1条 |
---|
文档条数 | 52.5B | 10W * 365 * 24 * 60 |
索引大小 | 6364GB | 10W * 365 * 24 * 60 * 130 |
_id index | 1468GB | |
{ts:1,icao:1} | 4895GB | |
文档平均大小 | 92Bytes | |
数据大小 | 4503GB | 10W * 365 * 24 * 60 * 92 |
| | |
5.2、解决方案-分桶设计
思路将之前每分钟数据汇总到每小时一条,将每分钟数据存放在events中,变更结构如下:
{ "_id": "10000000022200000222:CA2091", "icao": "CA2091", "ts": ISODate("2022-04-03T20:00:00.000+0000"),//每小时 "events": [//每小时60条数据,集合条数固定 { "tem": 35.3, "humidity": 50.3, "lon": 38.345, "lat": 58.987, "open": "0", "b": "b", "p": [ 123, 245 ], "s": 91, "ts": ISODate("2022-04-03T20:01:00.000+0000")//每分钟 }, { "tem": 35.3, "humidity": 50.3, "lon": 38.345, "lat": 58.987, "open": "0", "b": "b", "p": [ 123, 245 ], "s": 91, "ts": ISODate("2022-04-03T20:02:00.000+0000") } ]}
这里我们一个文档可以存储一个小时的设备数据,而events集合中的数据条数是固定的,每小时60条。
可视化表现 24 小时的设备数据,查询1440 次读操作即可。
指标 | 每分钟1条 | 每小时一个文档 |
---|
文档条数 | 52.5B | 876 M |
索引大小 | 6364GB | 106 GB |
_id index | 1468GB | 24.5 GB |
{ts:1,icao:1} | 4895GB | 81.6 GB |
文档平均大小 | 92Bytes | 758 Bytes |
数据大小 | 4503GB | 618 GB |
分桶方式总结:
场景 | 痛点 | 设计模式方案及优点 |
---|
时序数据 | 数据点采集频繁,数据量太多 | 利用文档内嵌数组,将一个时间段的数据聚合到一个文档里 |
物联网 | | 大量减少文档数量 |
智慧城市 | | 大量减少索引占用空间 |
智慧交通 | | |
六、设计模式集锦
6.1、列转行模式
问题: 大文档,很多字段,很多索引
解决方案:列转行
场景 | 痛点 | 设计模式方案及优点 |
---|
产品属性 ‘color’, ‘size’, ‘dimensions’, …物联网,多语言(多国家)属性 | 文档中有很多类似的字段,会用于组合查询搜索,需要建很多索引 | 转化为数组,一个索引解决所有查询问题 |
6.2、版本字段
问题:模型灵活了,如何管理文档不同版本?
修改前版本:
{ "_id": ObjectId("5de26f197edd62c5d388babb"), "name": "TJ", "company": "Tapdata"}
新增手机号后版本:
{ "_id": ObjectId("5de26f197edd62c5d388babb"), "name": "TJ", "company": "Tapdata", "phone":"182XXXX8888"}
解决方案:增加一个版本字段:
{ "_id": ObjectId("5de26f197edd62c5d388babb"), "name": "TJ", "company": "Tapdata", "phone":"182XXXX8888", "schema_version": 2.0}
场景 | 痛点 | 设计模式方案及优点 |
---|
任何有版本衍变的数据库 | 文档模型格式多,无法知道其合理性,升级时候需要更新太多文档 | 增加一个版本号字段;快速过滤掉不需要升级的文档;升级时候对不同版本的文档做不同的处理 |
6.3、近似计算
问题:统计网页点击流量
每隔10 (X)次写一次,
场景 | 痛点 | 设计模式方案及优点 |
---|
网页计数;各种结果不需要准确的排名; | 写入太频繁,消耗系统资源 | 间隔写入,每隔10次或者100次,大量减少写入需求 |
6.4、使用预聚合字段
问题: 业绩排名,游戏排名,商品统计等精确统计
热销榜:某个商品今天卖了多少,这个星期卖了多少,这个月卖了多少?
电影排行:观影者,场次统计
传统解决方案:通过聚合计算
痛点:消耗资源多,聚合计算时间长
在模型中新增预聚合字段,每次更新数据的时候同步更新统计值。
{ "product": "Bike", "sku": "abc123456", "quantitiy": 20394, "daily_sales": 40, "weekly_sales": 302, "monthly_sales": 1419}db.inventory.update({_id:123},{$inc: { quantity: -1, daily_sales: 1, weekly_sales: 1, monthly_sales: 1, } })
场景 | 痛点 | 设计模式方案及优点 |
---|
准确排名,排行榜 | 统计计算耗时,计算时间长 | 模型中直接增加统计字段;每次更新数据时候同时更新统计值 |
注:以上内容参考《MongoDB 高手课》