天天看點

MongoDB Secondary同步慢問題分析(續)

為了保證幂等性,記錄oplog時,通常需要對寫入的請求做一下轉換,舉個例子,某文檔x字段目前值為100,使用者向primary發送一條<code>{$inc: {x: 1}}</code>,記錄oplog時會轉化為一條<code>{$set: {x: 101}</code>的操作,才能保證幂等性。

簡單元素的操作,<code>$inc</code> 轉化為 <code>$set</code>并沒有什麼影響,執行開銷上也差不多,但當遇到數組元素操作時,情況就不一樣了。

目前文檔内容

在數組尾部push 2個元素,檢視oplog發現$push操作被轉換為了$set操作(設定數組指定位置的元素為某個值)。

<code>$push</code>轉換為<code>帶具體位置的$set</code>開銷上也差不多,但接下來再看看往數組的頭部添加2個元素

可以發現,當向數組的頭部添加元素時,oplog裡的$set操作不再是設定數組某個位置的值(因為基本所有的元素位置都調整了),而是$set數組最終的結果,即<code>整個數組的内容都要寫入oplog</code>。當push操作指定了$slice或者$sort參數時,oplog的記錄方式也是一樣的,會将整個數組的内容作為$set的參數。

$pull, $addtoset等更新操作符也是類似,更新數組後,oplog裡會轉換成<code>$set數組的最終内容</code>,才能保證幂等性。

當數組非常大時,對數組的一個小更新,可能就需要把整個數組的内容記錄到oplog裡,我們遇到一個實際的生産環境案例,使用者的文檔内包含一個很大的數組字段,1000個元素總大小在64kb左右,這個數組裡的元素按時間反序存儲,新插入的元素會放到數組的最前面($position: 0),然後保留數組的前1000個元素($slice: 1000)。

上述場景導緻,primary上的每次往數組裡插入一個新元素(請求大概幾百位元組),oplog裡就要記錄整個數組的内容,secondary同步時會拉取oplog并重放,『primary到secondary同步oplog』的流量是『用戶端到primary網絡流量』的上百倍,導緻主備間網卡流量跑滿,而且由于oplog的量太大,舊的内容很快被删除掉,最終導緻secondary追不上,轉換為recovering狀态。

mongodb對json的操作支援很強大,尤其是對數組的支援,但在文檔裡使用數組時,一定得注意上述問題,避免數組的更新導緻同步開銷被無限放大的問題。使用數組時,盡量注意

數組的元素個數不要太多,總的大小也不要太大

盡量避免對數組進行更新操作

如果一定要更新,盡量隻在尾部插入元素,複雜的邏輯可以考慮在業務層面上來支援

比如上述場景,有如下的改進思路

将數組的内容放到單獨的集合存儲,将數組的操作轉化為對集合的操作(capped collection能很好的支援$slice的功能)

如果一定要用數組,插入數組元素時,直接放到尾部,讓記錄就是按時間戳升序存儲,在使用時反向周遊({$natural: -1})取最新的元素。保持最近1000條的功能,則可在業務邏輯裡實作掉,比如增加背景任務來檢測,當數組元素超過某個門檻值如2000時,就将數組截斷到1000條。

為了盡量避免出現secondary追不上的場景,需要注意以下幾點

保證primary節點有充足的服務能力,如果使用者的請求就能把primary的資源跑得很滿,那麼勢必會影響到主備同步。

合理配置oplog的大小,可以結合寫入的情況,預估下oplog的大小,比如oplog能存儲一天的寫入量,這樣即使備同步慢、故障、或者臨時下線維護等,隻要不超過1天,恢複後還是有希望繼續同步的。

盡量避免複雜的數組更新操作,盡量避免慢更新(比如更新的查詢條件需要周遊整個集合)

<a href="https://yq.aliyun.com/articles/47336?spm=0.0.0.0.xtel7o">mongodb scondary同步慢問題分析</a>

<a href="https://docs.mongodb.com/v3.0/core/replica-set-oplog/">mongodb oplog</a>

<a href="https://docs.mongodb.com/v3.0/reference/operator/update/push/">mongodb $push updator</a>

<a href="https://docs.mongodb.com/v3.0/reference/operator/update/set/">mongodb $set updator</a>