2021 iThome 鐵人賽 - DAY20 MongoDB Oplog 玩壞它

2021 iThome 鐵人賽 MongoDB披荊斬棘之路


DAY20 MongoDB Oplog 玩壞它

把手弄髒,親眼於本機見證節點同步跟不上

本篇的目的就是要在本機端大量寫入資量,讓次節點的更新開始跟不上主節點,藉由這樣教你如何觀察這個現象以及處裡。

step1 建立 replica set

請直接參考這篇之前文章,在 local 啟動一組 MongoDB replica set,並直接連上 primary。

step1 寫入大量資料

準備了這個 script,大量寫入資料進資料庫中。
mongo --host localhost --port 27668 < test-data-script.js

  • 27668 是你的 primary node port
  • test-data-script.js 是腳本檔案名稱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
use ith2021;

var TestDataCreator = {
template: function () {
return {
"FieldA" : NumberDecimal("10"),
"FieldB" : NumberDecimal("10"),
"FieldC" : NumberDecimal("10"),
"FieldD" : NumberDecimal("10")
};
},

insertTestData: function () {
let that = this;
let batchSize = 10000;
let totalBatch = 100000;

for (let otrItr = 0 ; otrItr < totalBatch ; otrItr++) {

let models = [];
for (let itr = 0 ; itr < batchSize ; itr++) {
let model = {};
model.insertOne = {};
model.insertOne.document = that.template();
models.push(model);
}

db.getCollection('test-data').bulkWrite(models, {ordered: false});
print(new Date() + " inserted batch ***");
}
print(new Date() + "insert " + totalBatch + " batches all done ***");

},

start: function () {
this.insertTestData();
}
};

TestDataCreator.start();

step2 發動毀滅性語法

  • 先準備好幾個 terminal 視窗,分別連上各個節點。

隨著資料量的增長,可以看到 oplog 偶有點小小延誤

1
2
3
4
5
6
7
8
9
10
11
12
ith2021-rs [direct: primary] ith2021> rs.printSecondaryReplicationInfo()
source: mongo_node1:27666
{
syncedTo: 'Sun Sep 12 2021 22:07:46 GMT+0800 (台北標準時間)',
replLag: '-2 secs (0 hrs) behind the primary '
}
---
source: mongo_node2:27667
{
syncedTo: 'Sun Sep 12 2021 22:07:46 GMT+0800 (台北標準時間)',
replLag: '-2 secs (0 hrs) behind the primary '
}

好像很可以,資料落差還在 5 秒內,這時只要去更改大量資料,就會產生大量差異。`

db.getCollection('test-data-2021-06-28').updateMany({"FieldA":10}, {$set:{"FieldA":"10-u"}})

step3 資料同步開始脫勾了

陸續下了幾個指令查看次節點資訊,我們看看發生什麼事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
test-RS:PRIMARY> db.printSlaveReplicationInfo()
source: test-rs-mongo2:27018
syncedTo: Mon Jun 28 2021 00:51:18 GMT+0800 (CST)
13 secs (0 hrs) behind the primary
source: test-rs-mongo3:27019
syncedTo: Mon Jun 28 2021 00:51:17 GMT+0800 (CST)
14 secs (0 hrs) behind the primary

test-RS:PRIMARY> db.printSlaveReplicationInfo()
source: test-rs-mongo2:27018
syncedTo: Mon Jun 28 2021 00:51:46 GMT+0800 (CST)
27 secs (0.01 hrs) behind the primary
source: test-rs-mongo3:27019
syncedTo: Mon Jun 28 2021 00:51:41 GMT+0800 (CST)
32 secs (0.01 hrs) behind the primary

test-RS:PRIMARY> db.printSlaveReplicationInfo()
source: test-rs-mongo2:27018
syncedTo: Mon Jun 28 2021 00:56:11 GMT+0800 (CST)
167 secs (0.05 hrs) behind the primary
source: test-rs-mongo3:27019
syncedTo: Mon Jun 28 2021 00:56:20 GMT+0800 (CST)
158 secs (0.04 hrs) behind the primary

這時我們看看主節點的資訊

1
2
3
4
5
6
test-RS:PRIMARY> db.printReplicationInfo()
configured oplog size: 990MB
log length start to end: 305secs (0.08hrs)
oplog first event time: Mon Jun 28 2021 00:54:03 GMT+0800 (CST)
oplog last event time: Mon Jun 28 2021 00:59:08 GMT+0800 (CST)
now: Mon Jun 28 2021 00:59:15 GMT+0800 (CST)

可以看到主節點還是不斷得在寫入新的資料,但是次節點的同步已經來到落後一百多秒了,再下去就會讓 oplog 滿出來,接著觸發 full resync,讓次節點失效。

其實就是透過這樣的方式實驗,在大流量時很容易就把同步機制弄到掛掉。


這邊要特別解釋一下關於 step3,我無意欺騙大家,step3 的內容是我在幾個月前在本機電腦跑出來的結果。

而幾個月後的現在,我改用 5版的 MongoDB 卻跑不出來了,用的步驟和手法是一模一樣的,暫時認為 MongoDB 在 Oplog 機制改善了不少,所以整體來說應該是件好事啦XD。

在測試過程中,資料量上到一千五百萬左右,還是有遇到某個節點直接崩潰的情形。


解決方案

其實說穿了目前的快速解決方案就是兩個,長期下來我還是認為治標不治本。MongoDB 資料同步這一塊在網路上很多人都會遇到,應該說只要把產品上線,資料量大一點就馬上會體會到,最常聽到的莫過於兩個人同時使用系統,但是查詢結果不同。

一般來說使用 replica set,主要目的也是讀寫分離,讓收資料端能夠發揮最大效益,而查詢面,只要不是最即時的需求,都會放到次節點去讀取。

oplog 剩餘的時間,代表的是 觸發全同步的剩餘時間,意思是你的次節點會停止服務,接著進行同步,直到全部與主節點同步為止。但這件事背後的隱憂是,就是因為資料來不及同步,才會觸發,那現在次節點無法服務,又同時在進行 full resync,會跟得上嗎?這有待商榷,但是可以確定的是主節點壓力又更重了,這可能會導致雪崩式的中斷服務,因此這問題一定都需要即時監控的。

目前來看,如果不想要自己寫服務接收指令回傳的資訊,大概都是要使用雲端收費服務才能有圖表監控功能了。

根除問題

  1. 以商業邏輯角度,確認是否需要對 DB 進行這麼大量的操作
  2. 這些操作是否可以分散到離峰時間進行
  3. replace 是否可以改用 update 特定欄位即可(replace對主節點更新速度最快,比 update 快到約 30%,所以請衡量使用情境)
  4. TTL很好用,但它也是對 DB 進行 Delete,大資料量同時觸發 TTL,也是一個負擔。
  5. 花更多錢,將高頻、併發的需求放在第一組 replica set,而查詢或統計類型的資料放在第二組 replica set,以更緩慢的方是進行寫入/同步,以減輕其壓力。

特效藥,花錢

網路上看到的解決方法多半都只有這兩個:

  1. 加大 oplog size,這個做法就是用空間換時間,爭取延長尖峰期觸發 full resync 的時間。
  2. 加大 replWriterThreadCount,提高次節點存取 thread 數。預設值是 16。這樣做代價就是記憶體使用率也會拉高。(這個項目在 MongoDB Atlas 似乎沒有辦法設定,但是5版的速度提升後,可能也不用改了)
  • 作者: MingYi Chou
  • 版權聲明: 轉載不用問,但請註明出處!本網誌均採用 BY-NC-SA 許可協議。