MongoDB oplog 分析
MongoDB数据复制的基础是oplog,复制集的任何数据变更都会在primary节点local.oplog.rs集合下记录oplog,secondary节点从primary持续拉取oplog并在本地回放,实现主从节点的数据实时同步。
oplog包含下列键:
-
ts
时间戳,由unix时间戳和自增计数构成,自增计数表示该操作为当前秒内的操作顺序
-
h
唯一标识
-
v
-
op
操作类型,其值是下列之一
- i - insert
- u - update
- d - remove
- c - command
- n - no-op
-
ns
namespace,格式是database.collection
-
o
具体操作内容,对于i,表示要写入的文档;对于u,表示要执行的变更;对于d,表示要删除的文档,即_id;对于c,表示要执行的命令 udpate操作如果只更新部分field,o键的内容是*{ $set: { … } };如果是replace整个文档,o键的内容是{ _id: …, field0: … filed1: … }*
-
o2
仅出现于update操作,用于指定要操作的目标文档,即_id
举个栗子,依次执行下列操作:
|
|
然后观察对应的oplog,
|
|
可以看到,command的oplog类型不一定是c,比如建立索引,实际上是在目标数据库的system.indexes集合写入一条文档,类型是i。
有一点需要注意,对于类型是c的oplog,o键的值是有序的,比如
|
|
回放过程相当于执行db.runCommand({ "deleteIndexes" : "coll", "index" : "b_1" })
,第一个键deleteIndexes表示操作类型是删除索引,第二个键index指定目标索引,二者要保证顺序。
在写同步工具时碰到过,pymongo将读取的文档存到一个dict,对于Python来说,dict是键序无关的,但对于MongoDB,键序不对将导致command执行失败,解决方法可参考 https://dzone.com/articles/pymongo-and-key-order
后记@20171229
如果oplog用于MongoDB间的数据同步,直接回放就行;如果MongoDB数据同步到其他数据库,update需要关注细节,特别是内嵌文档操作,字段名采用".“点分隔的扁平化表示。
以文档{ info: { a: 1, b: 2 } }
为例,insert操作的olog的o字段即原始文档;update操作的oplog相对复杂,看下面几个情景:
-
修改a字段 =>
{ info: { a: 100, b: 2 } }
oplog为
{ op: 'u', o2: {_id: 'xxxx'}, o: { $set: { 'info.a': 100 } } }
注意
{ op: 'u', o2: {_id: 'xxxx'}, o: { $set: { 'info': { 'a': 100 } } } }
执行结果是{ info: { a: 100 } }
-
修改a&b字段 =>
{ info: { a: 100, b: 200 } }
oplog为
{ op: 'u', o2: {_id: 'xxxx'}, o: { $set: { 'info.a': 100, 'info.b': 200 } } }
-
增加c字段 =>
{ info: { a: 1, b: 2, c:3 } }
oplog为
{ op: 'u', o2: {_id: 'xxxx'}, o: { $set: { 'info.c': 3 } } }
注意
{ op: 'u', o2: {_id: 'xxxx'}, o: { $set: { 'info': { 'c': 3 } } } }
执行结果是{ info: { c: 3 } }
-
删除a字段 =>
{ info: { b: 2 } }
oplog为
{ op: 'u', o2: {_id: 'xxxx'}, o: { $unset: { 'info.a': true } } }
info.a后面可以是任意值,不影响字段删除
注意字段名扁平化,不要使用嵌套,下面的语义是删除info字段,*{ ‘a’: true }*相当于info的值
{ op: 'u', o2: {_id: 'xxxx'}, o: { $unset: { 'info': { 'a': true } } } }
等同于
{ op: 'u', o2: {_id: 'xxxx'}, o: { $unset: { 'info': true } }
-
删除a&b字段 =>
{ info: { } }
oplog为
{ op: 'u', o2: {_id: 'xxxx'}, o: { $unset: { 'info.a': true, 'info.b': true } } }
-
删除info字段 =>
{ }
oplog为
{ op: 'u', o2: {_id: 'xxxx'}, o: { $unset: { 'info': true } }
简单总结,对于update来说,$set和$unset对象的key(s)是目标操作字段。