2012年10月29日 星期一

MongoDB Concurrency

MongoDB 的 concurrency 的資料,感覺好像比較難查....

Global Write Lock / Yield Lock (Locking-With-Yield)

首先是關於在 MongoDB v2.0 以前(含 2.0)的版本,MongoDB 具有兩個特性:global write lock 和 yielding lock。
詳情可以參考看看 [2-4],以下說明大略是出自 [2] 的說法~。

一般認為資料庫有 global write lock 應該是一件很糟糕的事情
因為那表示當程序要對資料庫寫入時,即使寫入的東西完全沒有衝突也必須排隊進行而無法同時進行。
不過在 MongoDB,這個狀況稍微有點不同~以下節錄 [2] 說明 global write lock 的內容:

MongoDB uses memory mapped files to access it's DB files. So a considerable chunk of your data resides in the RAM and hence results in fast access - fast read all the time and very fast write without journaling and pretty fast write with journaling. This means that for several regular operations MongoDB will not hit the disk before sending the response at all - including write operations. So the global lock that is applied exists only for the duration of time needed to update the record in the RAM. This is orders of magnitude faster than writing to the disk. So the DB is locked for a very tiny amount of time. This global lock is after all not as bad as it sounds at first.

大意是說,MongoDB 基本上是會把資料快取在記憶體上,在記憶體裡面去處理資料
因此當要寫入時,MongoDB 是在記憶體裡面去對資料做更新,效率自然非常地高(畢竟在記憶體內做 I/O 的速度遠高於在磁碟上做 I/O)。
在這種狀況下,就算是寫入時有 global write lock,影響也就沒那麼大了~因為 RAM I/O 的時間非常地短。

不過在資料庫比較大的時候,表示資料庫不可能把所有資料都快取在記憶體上,這時就會出現需要 yielding lock 的狀況了。
根據 [4] 的「a single global reader-writer lock 如何影响并行」小節,節錄原文如下:

It’s important to understand how concurrency works in MongoDB.

As of MongoDB v2.0, the locking strategy is rather coarse; a single global reader-writer lock reigns over the entire mongod instance. What this means is that at any moment in time, the database permits either one writer or multiple readers (but not both). This sounds a lot worse than it is in practice because there exist quite a few concurrency optimizations around this lock.

One is that the database keeps an internal map of which document are in RAM. For requests to read or write documents not residing in RAM, the database yields to other operations until the document can be paged into memory.

A second optimization is the yielding of write locks. The issue is that if any one write takes a long time to complete, all other read and write operations will be blocked for the duration of the original write. All inserts, updates, and removes take a write lock. Inserts rarely take a long time to complete. But updates that affect, say, an entire collection, as well as deletes that affect a lot of documents, can run long. The current solution to this is to allow these long-running ops to yield periodically for other readers and writers. When an operation yields, it pauses itself, releases its lock, and resumes later. But when updating and removing documents, this yielding behavior can be amixed blessing. It’s easy to imagine situations where you’d want all documents updated or removed before any other operation takes place.

For these cases, you can use a special option called $atomic to keep the operation from yielding. You simply add the $atomic operator to the query selector like so:

db.reviews.remove({user_id: ObjectId(’4c4b1476238d3b4dd5000001′),{$atomic: true}})

從上述的原文,我對它的理解是說~MongoDB 在遇到資料不在記憶體時(即發生 page fault),會先將自己這個程序暫停
把優先權讓給別的程序去進行資料庫的操作,然後自己暫停下來等待要操作的資料被讀取到記憶體內。
要操作的資料進入記憶體以後,就如同上面的狀況,會使用 global write lock 去做資料寫入。
不過上面節錄的原文也有提到,yielding lock 雖然有助於提昇 concurrency 的效率,但可能導致不一致的問題
比如說我們常會希望指定的某些文件會在別的操作執行前,先被修改或者刪除。
這時的解法就是要加上 atomic 關鍵字,MongoDB 即不會對這個操作使用 yielding lock 去改變操作真正被執行的時間。

在 MongoDB v2.0 以上版本加入了 Yield Lock 以後,對寫入效能的影響可以參考 [3] 的效能測試
測試方法是對 v1.8 和 v2.0 的 MongoDB 都插入 10,000 個資料,測驗 non-faulting 的效能。
測試結果如下圖:


Non-faulting Write,轉錄自 [3]

可以看出在沒有發生 page fault 的狀況時,MongoDB v1.8 和 v2.0 效能上差不多。
而測試 page fault 的狀況時,[3] 插入了 150,000,000 個資料,測試結果如下圖:


Faulting Write,轉錄自 [3]

這裡可以明顯看出,MongoDB v2.0 的效率下降速度較 v1.8 來得慢很多~
足以表示在 v2.0 中使用了 Yield Lock 所帶來的好處。

Removing Global Write Lock

根據 [5] 所說,在 MongoDB v2.2 開始,把原本的 Global Write Lock 給移除掉了!
[5] 提到了在 v2.2 的重要的改變有以下兩點:
  • Elimination of the global reader/writer lock – database level locks as the first step.
  • PageFaultException architecture – yield lock on page fault.
第一點即是移除了 Global Write Lock 並且實現了 Database Level Lock~
意義應該是當程序使用了多個 MongoDB 資料庫時,寫入的 Lock 可以鎖住目前要寫入的資料庫,而不是對整個程序的寫入行為上鎖。

[5] 使用了跟 [3] 相同的測試方法,但額外加入了 MongoDB v2.1 的實驗(但不包含 non-faulting 的實驗)。
測試結果如下圖:


Faulting Write,轉錄自 [5]

圖中可以看出,在 MongoDB v2.1 中同時寫入和讀取時,即使寫入的量增加,讀取的速度仍然幾乎沒有被影響。
至於原因可以看到 [5] 的第二個實驗圖表,[5] 呼叫了 mongostat 來取得目前 MongoDB 的狀態,藉以得知目前有多少個 Global Lock
結果如下圖:


Percentage of Global Lock,轉錄自 [5]

圖中即證實了~MongoDB v2.0 在寫入的量增加時,要求的 Global Lock 的百分比較少
而 MongoDB v2.1 則是完全沒有 Global Lock。

參考資料:
1、SQL vs NoSQL:數據庫並發寫入性能比拼
2、MongoDB concurrency - Global write lock and yielding locks
3、MongoDB's Write Lock
4、MongoDB.in.Action notes
5、Goodbye global lock – MongoDB 2.0 vs 2.2

沒有留言: