DAY19 MongoDB Oplog 是什麽?迈向高手之路

DAY19 MongoDB Oplog 到底是什麽?

oplog 是什麽?

如果你的 MongoDB 是使用 replication,那你会需要知道什麽是 oplog;如果你的 MongoDB 是只有单一个节点,那就暂时还不需要理解。

oplog 是用来同步主要节点与次节点资料用的。例如我们写入了一笔资料,而这笔资料的异动资讯也会被写入 oplog 中,次节点会主动去主节点的 oplog collection 内执行find, getMore 拿到需要同步的资料,再透过後续抄写机制回次节点,并告诉主节点最後同步时间。

ChainingAllowed

MongoDB 同步机制中,来源不一定是主节点,例如这是我们预期的

  1. 主 -> 次1
  2. 主 -> 次2

但实际上可以是

  1. 主 -> 次1
  2. 次1 -> 次2

这样做的目的是减轻主节点的压力。这个功能叫做 chainingAllowed,在 rs.conf() 可以看到,预设为 true

各个次节点会以 heartbeat 方式确认互相彼此存活,以便进行资料同步。

oplog 怎麽查看

首先你必须於本机建立 replica set,可以参考之前的文章来建立测试环境。

连上任一节点後输入 rs.printReplicationInfo(),预期会取得以下结果:

ith2021-rs:SECONDARY> rs.printReplicationInfo()

configured oplog size:   2300.437744140625MB
log length start to end: 6203secs (1.72hrs)
oplog first event time:  Sun Sep 12 2021 16:08:52 GMT+0800 (CST)
oplog last event time:   Sun Sep 12 2021 17:52:15 GMT+0800 (CST)
now:                     Sun Sep 12 2021 17:52:24 GMT+0800 (CST)
  • oplog size: 可以参阅官网,预设是硬碟的 5%; macOS 则是 192MB。这些都可以再调整。
  • log length start to end: 按照目前增长速度,oplog 甚麽时候会被写满,以上面的例子就是 1.72 hrs 後就会满了。这个值仅供参考,因为写入量随时都在改变,要特别注意尖峰时刻就是了。

查看次节点的同步状况

输入以下指令: db.printSlaveReplicationInfo()

ith2021-rs:SECONDARY> db.printSlaveReplicationInfo()
source: mongo_node1:27666
	syncedTo: Sun Sep 12 2021 17:54:25 GMT+0800 (CST)
	0 secs (0 hrs) behind the primary
source: mongo_node2:27667
	syncedTo: Sun Sep 12 2021 17:54:25 GMT+0800 (CST)
	0 secs (0 hrs) behind the primary

0 secs (0 hrs) behind the primary 这段话的意思是跟主节点资料落差有多少秒,以上面的例子来说就是完全同步的意思。在後面我们会有一些测试方式来看数据的变化。

oplog

查看 oplog 容量

完整资讯是

ith2021-rs [direct: primary] local>  db.getReplicationInfo()
{
  logSizeMB: 130000.0999994278,
  usedMB: 0.1,
  timeDiff: 8474,
  timeDiffHours: 2.35,
  tFirst: 'Sun Sep 12 2021 16:08:52 GMT+0800 (台北标准时间)',
  tLast: 'Sun Sep 12 2021 18:30:06 GMT+0800 (台北标准时间)',
  now: 'Sun Sep 12 2021 18:30:14 GMT+0800 (台北标准时间)'
}
  • logSizeMB 就是该节点目前预先 allocate 的容量
  • usedMB 是目前使用的

*Note: 主节点与次节点预设的 logSizeMB 是不同大小的
*Note2: db.getReplicationInfo(), db.printReplicationInfo() 结果都是一样的,只是显示格式不同

修改 oplog 大小

第一个方法是於启动的时候加上设定值,而单位是 MB,如下:
--oplogSize = 10 这样就是设定为 10MB 大小。

第二个方法是启动後的修改,输入以下指令:
db.adminCommand({replSetResizeOplog:1, size: 123456}) 单位是 MB

  • 范例一:

修改比预设还小的值

ith2021-rs [direct: primary] local> db.getReplicationInfo().logSizeMB
6340
ith2021-rs [direct: primary] local> db.adminCommand({replSetResizeOplog:1, size: 1000.1})
{
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1631443194, i: 1 }),
    signature: {
      hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
      keyId: Long("0")
    }
  },
  operationTime: Timestamp({ t: 1631443194, i: 1 })
}
ith2021-rs [direct: primary] local> db.getReplicationInfo().logSizeMB
6649

可以看到回传虽然 ok 为 1,但并没有任何改变。

  • 范例二:

修改比预设还大的值

ith2021-rs [direct: primary] local> db.adminCommand({replSetResizeOplog:1, size: 7899.1})
{
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1631443324, i: 1 }),
    signature: {
      hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
      keyId: Long("0")
    }
  },
  operationTime: Timestamp({ t: 1631443324, i: 1 })
}
ith2021-rs [direct: primary] local> db.getReplicationInfo().logSizeMB
7899.099999427795

oplog 满了会发生什麽事

首先,oplog 满了,会先把最旧的删除,就像是 FIFO 的概念。
所谓的满,有可能是容量满,或是档案数超过喔!

当 oplog 满了,会触发 full resync,强制让次节点完全同步 oplog 内容後,才会从 recovering 状态变回正常。


oplog 长什麽样子?

这篇文章将实际对 MongoDB 进行一些操作,接着查看 oplog 有何变化。

以下内容的操作步骤:

  1. 使用 ith2021
  2. 直接写入一笔资料进 ironman
  3. 查看 oplog.rs,条件为 {"op": "i"} (细节後谈)
ith2021-rs [direct: primary] local> use ith2021
switched to db ith2021
ith2021-rs [direct: primary] ith2021> db.ironman.insertOne({field:'iThome 2021 Winner'})
{
  acknowledged: true,
  insertedId: ObjectId("613ddc90a3c50f67ffc384cd")
}
ith2021-rs [direct: primary] ith2021> use local
switched to db local
ith2021-rs [direct: primary] local> db.oplog.rs.find({"op":"i"})
[
{
    lsid: {
      id: UUID("84a171ba-6dbe-47b0-8187-6775dacd1281"),
      uid: Binary(Buffer.from("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "hex"), 0)
    },
    txnNumber: Long("2"),
    op: 'i',
    ns: 'ith2021.ironman',
    ui: UUID("c227fbe2-bae6-4ee1-8a7a-8ea32c99edd3"),
    o: {
      _id: ObjectId("613ddc90a3c50f67ffc384cd"),
      field: 'iThome 2021 Winner'
    },
    ts: Timestamp({ t: 1631444112, i: 1 }),
    t: Long("1"),
    v: Long("2"),
    wall: ISODate("2021-09-12T10:55:12.886Z"),
    stmtId: 0,
    prevOpTime: { ts: Timestamp({ t: 0, i: 0 }), t: Long("-1") }
  }
]

从上面的操作步骤可以看到,我们写入一笔资料後,oplog 也会有有一笔相同内容资讯的纪录,这个就是让次节点拿去同步的。上面的查询条件 op 是代表 operation 的意思,MongoDb很常使用,应该是不陌生;i 则代表 insert,还有以下操作:

  • "i":insert
  • "u":update
  • "d":delete
  • "c":资料库相关指令
  • "n":no op,从 msg 可以得知是定期执行的指令,确保其运作。

使用 Update 更新

这边我们将第一个栏位更新(update)其内容,再查看 oplog:

ith2021-rs [direct: primary] ith2021> db.ironman.updateOne({_id:ObjectId("613ddc90a3c50f67ffc384cd")}, {$set:{"field":"iThome 2021 Winner - eplis"}})

ith2021-rs [direct: primary] local> db.oplog.rs.find({"op":"u"})
[
  {
    lsid: {
      id: UUID("129cd660-1da1-4a2a-94ae-569d0b406ec3"),
      uid: Binary(Buffer.from("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "hex"), 0)
    },
    txnNumber: Long("2"),
    op: 'u',
    ns: 'ith2021.ironman',
    ui: UUID("c227fbe2-bae6-4ee1-8a7a-8ea32c99edd3"),
    o: { '$v': 2, diff: { u: { field: 'iThome 2021 Winner - eplis' } } },
    o2: { _id: ObjectId("613ddc90a3c50f67ffc384cd") },
    ts: Timestamp({ t: 1631444513, i: 1 }),
    t: Long("1"),
    v: Long("2"),
    wall: ISODate("2021-09-12T11:01:53.945Z"),
    stmtId: 0,
    prevOpTime: { ts: Timestamp({ t: 0, i: 0 }), t: Long("-1") }
  }
]

使用 Replace 更新

再使用 Replace 语法去做更新,查看 oplog..

ith2021-rs [direct: primary] ith2021> db.ironman.replaceOne({_id: ObjectId("613ddc90a3c50f67ffc384cd")}, {field: 'iThome Winner', year: 2021, winner: 'eplis'})

ith2021-rs [direct: primary] local> db.oplog.rs.find({"op":"u"})
[
  {
    lsid: {
      id: UUID("129cd660-1da1-4a2a-94ae-569d0b406ec3"),
      uid: Binary(Buffer.from("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "hex"), 0)
    },
    txnNumber: Long("3"),
    op: 'u',
    ns: 'ith2021.ironman',
    ui: UUID("c227fbe2-bae6-4ee1-8a7a-8ea32c99edd3"),
    o: {
      _id: ObjectId("613ddc90a3c50f67ffc384cd"),
      field: 'iThome Winner',
      year: 2021,
      winner: 'eplis'
    },
    o2: { _id: ObjectId("613ddc90a3c50f67ffc384cd") },
    ts: Timestamp({ t: 1631444709, i: 1 }),
    t: Long("1"),
    v: Long("2"),
    wall: ISODate("2021-09-12T11:05:09.177Z"),
    stmtId: 0,
    prevOpTime: { ts: Timestamp({ t: 0, i: 0 }), t: Long("-1") }
  }
]

可以看到同样属於 update 操作,但是魔鬼藏在细节里!!

  • 如果使用 update 只会存修改的栏位
  • 如果使用 replace 整份文件都会写入 oplog

确实很合理,但是如果是大量使用 replace 或是一个文件大小很大的话,会造成 oplog 大量被占用记忆体,进而导致缩短可用时间,再继续下去可能就会触发次节点 full resync。所以使用上一定要特别斟酌是否需要 replace。


本系列文章会同步发表於我个人的部落格 Pie Note


<<:  [Day04 - UI/UX] 确认使用的 UI framework

>>:  Re: 新手让网页 act 起来: Day04 - JSX

DVWA练习-Cross Site Request Forgery

由於浏览器请求会自动包含与站点关联的任何凭据 多数站点继承了受害者的身份和特权,如Session c...

【Day 12】C 语言的 if 条件判断(上)

写程序的时候,我们常常需要「判断」某些条件,当条件成立、条件为"真"的时候,执行某一段程序码,而条件...

[iT铁人赛Day20]JAVA学习心得

做完了这几天的JAVA分享。。。我说是分享啦,因为我没有厉害到可以教别人 恩,所以做完分享之後,我也...

D-19. Git中的tag 、Git flow && Array Partition I

如果单纯从学习Ruby再学习运用Rails开发网页专案,那可能还要再认识一些技能,对开发上能更有帮助...

[鼠年全马] W37 - Vue出一个旅馆预约平台(11)

这周接续上周进度 把真实资料喂进 SuccessCard.vue 罗~ #左侧预约功能(接续) 真实...