《DBA规范利器: MongoDB模式校验》
作者:上海小胖
《MongoDB Validator 是否会因性能影响而成为摆设?》
作者:上海小胖
《DBA规范利器: MongoDB模式校验》
作者:上海小胖
一直以来想做一个关于MongoDB的系列专题,但又没有中心思想,所以一直拖着,但现在想想大可不必。完全可以东打一枪,西放一炮。
所以有了今天的第一篇文章。
今天就来说说runCommand的collMod命令
首先我们先来盘算一下,MongoDB创建集合的方式有哪几种:
- 对希望创建的collection插入数据 db..insert({: })
- 通过对collection创建索引 db..createIndex({:1}
- 调用官方API接口显式创建 db.createCollection(
从MongoDB 3.2开始,官方提供了相应接口对Schema作 强一致性 的策略:
当数据进行Insert或者Update的时候,通过创建集合时的validator option的参数控制,按照自定义的validation rules 或者 expressions,达到数据强一致性检查。
db.createCollection( "contacts",
{ validator: { $or:
[
{ phone: { $type: "string" } },
{ email: { $regex: /@mongodb.com$/ } },
{ status: { $in: [ "Unknown", "Incomplete" ] } }
]
}
} )
这里提供了3种验证方式,满足一种即可。若是$and,则需要同时满足。
若不满足,MongoDB将会返回121错误,提示Document failed validation,插入0条。
该特性对MongoDB的Schemaless以及对DBA维护MongoDB表结构、规范等提供了相当大的帮助。
下面图1既是引用官方文档关于validation level的定义:
VALIDATIONLEVEL | DESCRIPTION |
---|---|
“off” | 关闭数据校验。 |
“strict” | 默认值。对所有的update和insert有效。 |
“moderate” | 仅对insert和满足校验规则的document做update有效。对已存在的不符合校验规则的document无效。 |
下面图2既是引用官方文档关于validation action的定义:
VALIDATIONACTION | DESCRIPTION |
---|---|
“error” | 默认值。document必须满足校验规则,才能被写入数据哭。 |
“warn” | document不符合校验规则的,MongoDB允许写入,但会记录一条告警到mongod.log中去。日志内容记录报错信息以及该document的完整记录。 |
db.runCommand( { collMod: "contacts",
validator: { $or:
[
{ phone: { $type: "string" } },
{ email: { $regex: /@mongodb.com$/ } },
{ status: { $in: [ "Unknown", "Incomplete" ] } }
]
},
validationLevel: "moderate",
validationAction: "warn"
} )
目前正准备着手先培训 开发遵守我们的MongoDB 数据规范,之后再开始对历史数据做清理。个人感觉是个大坑。。不过我选择跳。
总 结
collMod的作用是非常明显的,可以帮助DBA去规避之前由于Schemaless而无法约束开发的窘境。可以就collMod来规范MongoDB的开发过程。
敦促我们DBA去有意识、有必要的使用强一致性对相应MongoDB数据作统一规划。
《MongoDB Validator 是否会因性能影响而成为摆设?》
作者:上海小胖
由 来
承接 DBA规范利器: MongoDB模式校验, 又正好在发布后的当天,2017年7月16日,举办了MongoDB中国社区上海站,在会上,TJ总分享了3.6中 Validator 会变得更加的强硬。这个时候就有一个疑惑了,是否如此的强一致性、检查性,会对我们在日常操作中,产生明显的性能影响呢? 由此,引发了我写这篇文章的欲望。
回 顾
先来回顾一下上篇中提到的2个方法,在本篇中我们会用到
创建集合的时候,由DBA来创建,并指定validator 规则,针对日后的统一规范
db.createCollection( "contacts",
{ validator: { $or:
[
{ phone: { $type: "string" } },
{ email: { $regex: /@mongodb.com$/ } },
{ status: { $in: [ "Unknown", "Incomplete" ] } }
]
}
} )
集合创建完毕,需要创建validator,针对历史数据进行清理,需要注意的是,创建之前,需要清理脏数据。(这里的脏数据仅指数据格式不统一的)
db.runCommand( { collMod: "contacts",
validator: { $or:
[
{ phone: { $type: "string" } },
{ email: { $regex: /@mongodb.com$/ } },
{ status: { $in: [ "Unknown", "Incomplete" ] } }
]
},
validationLevel: "moderate",
validationAction: "warn"
} )
准备工作
MongoDB的压测工具,业界良心,YCSB,只可惜YAHOO! 已不复当年之用了。。
https://github.com/brianfrankcooper/YCSB
这是YCSB的地址,我们只需要用到其中的mongodb部分,当然了,整个zip包还是需要一起打包下来的。
简单的使用这里就不赘述了,小胖权当各位大佬都是精通YCSB者了。
硬件配置
服务器
- Product Name: ProLiant DL360 Gen9
- Storage: RAID10, SSD, 1600GB
- CPU*2: Intel(R) Xeon(R) CPU E5-2620 v4 @ 2.10GHz, (8/8 cores; 16 threads)*2
- Memory: 64GB*2
数据库
- 单节点
- wiredTigerCacheSizeGB=4
- 其他全部默认
部分代码解读
众所周知,由于YCSB是通过workload.conf command 的方式进行压测的。因此在第一步load 完数据后,我们是需要通过动态添加validator 的,因此我们需要确定2件事情
- YCSB 的query 条件是什么?(弄清楚了query 条件,我们就可以针对该query 条件添加对应的validator 了)
- YCSB 的insert 条件是什么?
也许会有人问,CRUD 为什么只涉及 select 和 insert 呢?因为update 和 delete ,无非就是select insert, 所以我们只需要搞清楚2个即可。那么上代码。
我们先来看insert, 我将略去不必要的代码
YCSB/mongodb/src/main/java/com/yahoo/ycsb/db/MongoDbClient.java
@Override
public Status insert(String table, String key,
HashMap values) {
try {
MongoCollection collection = database.getCollection(table);
Document toInsert = new Document("_id", key);
for (Map.Entry entry : values.entrySet()) {
toInsert.put(entry.getKey(), entry.getValue().toArray());
}
if (batchSize == 1) {
if (useUpsert) {
……
}
我们来观察一下insert 的代码,可以看到有一条关键代码 Document toInsert = new Document("_id", key); 拿了key 作为参数传给了_id,因此,我们可以断定他的insert 条件就是以_id 为条件。其实也就是修改了_id 为ObjectId。
再来看query
@Override
public Status read(String table, String key, Set fields,
HashMap result) {
try {
MongoCollection collection = database.getCollection(table);
Document query = new Document("_id", key);
FindIterable findIterable = collection.find(query);
……
}
同样的,我们的query 也是通过_id 来获取的。
如上,我们已经非常明确的知道了YCSB 在做CRUD 操作的时候的具体过程了。
接下来就是实cao 环节了。
压 测
代码语言:javascript复制初始化数据./bin/ycsb load mongodb -s -P workloads/mongodb.ycsb -threads 100 -p mongodb.url=mongodb://localhost:27017/ycsb?w=0 > ycsb_validation_load_t100.log
手动添加validator需要注意的是,这里的usertable 是YCSB 自带的collection namedb.runCommand( { collMod: "usertable",
validator: { $or:
[
{ _id: { $type: "string" } }
],
},
validationLevel: "strict",
validationAction: "warn"
} )
./bin/ycsb run mongodb -s -P workloads/mongodb.ycsb -threads 100 -p mongodb.url=mongodb://localhost:27017/ycsb?w=0 > ycsb_validation_run_t100_r95_i5.logrecordcount=10000000
operationcount=10000000
workload=com.yahoo.ycsb.workloads.CoreWorkload
readallfields=true
readproportion=0.95
updateproportion=0
scanproportion=0
insertproportion=0.05
requestdistribution=zipfian
./bin/ycsb run mongodb -s -P workloads/mongodb.ycsb -threads 100 -p mongodb.url=mongodb://localhost:27017/ycsb?w=0 > ycsb_validation_run_t100_r50_u50.logrecordcount=10000000
operationcount=10000000
workload=com.yahoo.ycsb.workloads.CoreWorkload
readallfields=true
readproportion=0.5
updateproportion=0.5
scanproportion=0
insertproportion=0
requestdistribution=zipfian
./bin/ycsb run mongodb -s -P workloads/mongodb.ycsb -threads 100 -p mongodb.url=mongodb://localhost:27017/ycsb?w=0 > ycsb_validation_run_t100_r70_u20_i10.logrecordcount=10000000
operationcount=10000000
workload=com.yahoo.ycsb.workloads.CoreWorkload
readallfields=true
readproportion=0.7
updateproportion=0.2
scanproportion=0
insertproportion=0.1
requestdistribution=zipfian
./bin/ycsb run mongodb -s -P workloads/mongodb.ycsb -threads 100 -p mongodb.url=mongodb://localhost:27017/ycsb?w=0 > ycsb_validation_run_t100_r10_u20_i70.logrecordcount=10000000
operationcount=10000000
workload=com.yahoo.ycsb.workloads.CoreWorkload
readallfields=true
readproportion=0.1
updateproportion=0.2
scanproportion=0
insertproportion=0.7
requestdistribution=zipfian
./bin/ycsb run mongodb -s -P workloads/mongodb.ycsb -threads 100 -p mongodb.url=mongodb://localhost:27017/ycsb?w=0 > ycsb_validation_run_t100_r10_i90.logrecordcount=10000000
operationcount=10000000
workload=com.yahoo.ycsb.workloads.CoreWorkload
readallfields=true
readproportion=0.1
updateproportion=0
scanproportion=0
insertproportion=0.9
- requestdistribution=zipfian
压测报告
压测一共做了2轮,第一轮是没有validator 的,第二轮是有validator的,为了保证数据的无差异性,第一轮压测后,重新load 了数据。
所有的压测数据以及压测过程,都在github 上了
https://github.com/MiracleYoung/mongodb_stressing_validator
r:0.95, i:0.05
CRUD | NORMAL DOC | VALIDATION DOC |
---|---|---|
[INSERT], AverageLatency(us) | 54.32783133598422 | 50.644348350531864 |
[INSERT], 95thPercentileLatency(us) | 52.0 | 50.0 |
[READ], AverageLatency(us) | 24059.437908215754 | 23709.003255993488 |
[READ], 95thPercentileLatency(us) | 75839.0 | 74495.0 |
[UPDATE], AverageLatency(us) | null | null |
[UPDATE], 95thPercentileLatency(us) | null | null |
[OVERALL], RunTime(ms) | 248575.0 | 244206.0 |
[OVERALL], Throughput(ops/sec) | 40229.307050186064 | 40949.034831249024 |
r:0.5, u:0.5
CRUD | NORMAL DOC | VALIDATION DOC |
---|---|---|
[INSERT], AverageLatency(us) | null | null |
[INSERT], 95thPercentileLatency(us) | null | null |
[READ], AverageLatency(us) | 6654.8808580182795 | 6861.01072835122 |
[READ], 95thPercentileLatency(us) | 21999.0 | 21935.0 |
[UPDATE], AverageLatency(us) | 62.51089386161971 | 65.22678019721495 |
[UPDATE], 95thPercentileLatency(us) | 68.0 | 69.0 |
[OVERALL], RunTime(ms) | 337878.0 | 347989.0 |
[OVERALL], Throughput(ops/sec) | 29596.481570270927 | 28736.54052283262 |
r:0.7, u:0.2, i:0.1
CRUD | NORMAL DOC | VALIDATION DOC |
---|---|---|
[INSERT], AverageLatency(us) | 252.44167431584393 | 344.1996127709654 |
[INSERT], 95thPercentileLatency(us) | 108.0 | 110.0 |
[READ], AverageLatency(us) | 2647.108107555894 | 2730.1629217311306 |
[READ], 95thPercentileLatency(us) | 9327.0 | 9223.0 |
[UPDATE], AverageLatency(us) | 248.62104869988795 | 310.29020023763366 |
[UPDATE], 95thPercentileLatency(us) | 69.0 | 70.0 |
[OVERALL], RunTime(ms) | 195966.0 | 205159.0 |
[OVERALL], Throughput(ops/sec) | 51029.260177785945 | 48742.682504788965 |
r:0.1, u:0.2, i:0.7
CRUD | NORMAL DOC | VALIDATION DOC |
---|---|---|
[INSERT], AverageLatency(us) | 82.1098431094292 | 81.66241673973236 |
[INSERT], 95thPercentileLatency(us) | 93.0 | 93.0 |
[READ], AverageLatency(us) | 20279.290684018884 | 20223.295329881992 |
[READ], 95thPercentileLatency(us) | 66943.0 | 67327.0 |
[UPDATE], AverageLatency(us) | 62.3647426526138 | 59.5288535378221 |
[UPDATE], 95thPercentileLatency(us) | 66.0 | 65.0 |
[OVERALL], RunTime(ms) | 212787.0 | 211929.0 |
[OVERALL], Throughput(ops/sec) | 46995.352159671405 | 47185.61404998844 |
r:0.1, i:0.9
CRUD | NORMAL DOC | VALIDATION DOC |
---|---|---|
[INSERT], AverageLatency(us) | 129.41064849336593 | 81.56726077106597 |
[INSERT], 95thPercentileLatency(us) | 98.0 | 83.0 |
[READ], AverageLatency(us) | 911.6383231943234 | 857.076724559656 |
[READ], 95thPercentileLatency(us) | 2103.0 | 2171.0 |
[UPDATE], AverageLatency(us) | 62.3647426526138 | 59.5288535378221 |
[UPDATE], 95thPercentileLatency(us) | 66.0 | 65.0 |
[OVERALL], RunTime(ms) | 88974.0 | 83188.0 |
[OVERALL], Throughput(ops/sec) | 112392.38429204037 | 120209.64562196471 |
结 论
说明:
当我的wiretiger 内存控制在4GB时,我的数据已经超出内存了。
YCSB 的样本数据,默认是1KB 一条doc,1KW * 1KB = 10GB
思考
从压测报告中,可以看出,validator 对于大多数场景下(不排除少部分我并没有测试到的场景,比如range,其实YCSB也有,这里不再赘述)是不影响数据库的使用性能的。warning 意在打出warning 报告,并不会abort,所以也不会对程序有任何的阻塞。
由此,DBAer 可以用validator 大施拳脚,整顿MongoDB Schema Standard!
后 续
洗了个澡,思考了一下,意识到一个问题,会不会因为SSD太快了,数据不够辣鸡导致无差异呢?由此,做了第二次压测,这次只做了一个场景,那就是r:0.05, i:0.95,因为validator的检查一定是在写入数据的时候。
这次的压测数据量是1WW 条,operation 操作数是5KW条。mongodb 内存提升到16G。
结果如下:
r:0.05, i:0.95
CRUD | NORMAL DOC | VALIDATION DOC |
---|---|---|
[INSERT], AverageLatency(us) | 47.76224959524256 | 66.52576949011765 |
[INSERT], MinLatency(us) | 12.0 | 12.0 |
[INSERT], MaxLatency(us) | 375295.0 | 1.6490495E7 |
[INSERT], 95thPercentileLatency(us) | 59.0 | 59.0 |
[INSERT], 99thPercentileLatency(us) | 235.0 | 220.0 |
由以上结论可以得出:
- 从平均值上可以简单得出在大并发、持续性的输入到数据库的时候,是会带来一定的性能消耗。
- 结合最大、最小、95% 和99% 的数据上可以得出,平均值的延迟是由于波谷的一些少数慢插入导致的。
- 综合之前的所有压测报告,我们发现其实validator 对性能的影响真的可以忽略不计啊~~~