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,會跟得上嗎?這有待商榷,但是可以確定的是主節點壓力又更重了,這可能會導致雪崩式的中斷服務,因此這問題一定都需要即時監控的。
目前來看,如果不想要自己寫服務接收指令回傳的資訊,大概都是要使用雲端收費服務才能有圖表監控功能了。
根除問題
以商業邏輯角度,確認是否需要對 DB 進行這麼大量的操作
這些操作是否可以分散到離峰時間進行
replace
是否可以改用 update
特定欄位即可(replace對主節點更新速度最快,比 update 快到約 30%,所以請衡量使用情境)
TTL
很好用,但它也是對 DB 進行 Delete
,大資料量同時觸發 TTL,也是一個負擔。
花更多錢,將高頻、併發的需求放在第一組 replica set,而查詢或統計類型的資料放在第二組 replica set,以更緩慢的方是進行寫入/同步,以減輕其壓力。
特效藥,花錢 網路上看到的解決方法多半都只有這兩個:
加大 oplog size
,這個做法就是用空間換時間,爭取延長尖峰期觸發 full resync 的時間。
加大 replWriterThreadCount
,提高次節點存取 thread 數。預設值是 16
。這樣做代價就是記憶體使用率也會拉高。(這個項目在 MongoDB Atlas 似乎沒有辦法設定,但是5版的速度提升後,可能也不用改了)