- 求職 : Linux運維
- 論壇徽章:
- 203
|
問題背景
對使用 wiredtiger 引擎的 mongod 進行如下測試,不斷的『創(chuàng)建集合、創(chuàng)建索引,插入一條記錄』,然后統(tǒng)計這3個動作的耗時。
var db = db.getSiblingDB("testdb" ;
for (var i = 0; i < 100000; i++) {
var start = (new Date()).getTime();
var collName = "test" + i;
var doc = {name: "name" +i, seq: i};
db.createCollection(collName); // 創(chuàng)建集合
db[collName].createIndex({name: 1}); // 創(chuàng)建索引
db[collName].insert(doc); // 插入一條記錄
var end = (new Date()).getTime(); // 統(tǒng)計耗時
print("cost: " + (end - start));
}
隨著集合數(shù)越來越多,測試過程中發(fā)現(xiàn)2個問題
偶爾會出現(xiàn)耗時很長的請求(1s、2s、3s..不斷上升),統(tǒng)計了下頻率,大約1分鐘左右出現(xiàn)一次。
平均耗時不斷增加,從最開始平均10ms 不到,一直到20ms、30ms、40ms…
測試問題1
因為耗時很長的請求頻率大概1分鐘一次,跟 wiredtiger 默認(rèn)的60scheckpoint 很接近,懷疑問題跟 checkpoint 有關(guān),從運行慢日志看,耗時長是因為 createIndex 的原因。
通過當(dāng)時的 pstack 發(fā)現(xiàn),創(chuàng)建索引的線程正在等鎖,只有 checkpoint 線程在干活
Thread 4 (Thread 0x7f80c3c72700 (LWP 70891)):
#0 0x00007f80c2ddc054 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x00007f80c2dd7388 in _L_lock_854 () from /lib64/libpthread.so.0
#2 0x00007f80c2dd7257 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3 0x00000000019f3f95 in __wt_curfile_open ()
#4 0x0000000001a580a5 in __session_open_cursor_int ()
#5 0x0000000001a09e13 in __wt_curtable_open ()
#6 0x0000000001a57f29 in __session_open_cursor_int ()
#7 0x0000000001a584b9 in __session_open_cursor ()
#8 0x000000000108cfe9 in mongo::WiredTigerIndex::BulkBuilder: penBulkCursor(mongo::WiredTigerIndex*) ()
#9 0x000000000108841e in mongo::WiredTigerIndexStandard::getBulkBuilder(mongo::OperationContext*, bool) ()
#10 0x0000000000cb09e9 in mongo::IndexAccessMethod::commitBulk(mongo::OperationContext*, std::unique_ptr<mongo::IndexAccessMethod::BulkBuilder, std::default_delete >, bool, bool, std::set<mongo::RecordId, std::less, std::allocator >*) ()
#11 0x0000000000b07410 in mongo::MultiIndexBlock::doneInserting(std::set<mongo::RecordId, std::less, std::allocator >*) ()
#12 0x0000000000b0797d in mongo::MultiIndexBlock::insertAllDocumentsInCollection(std::set<mongo::RecordId, std::less, std::allocator >*) ()
Thread 68 (Thread 0x7f80b9336700 (LWP 37085)):
#0 0x00000000019db9e0 in __config_next ()
#1 0x00000000019dc106 in __config_getraw.isra.0 ()
#2 0x00000000019dc5a6 in __wt_config_getones ()
#3 0x0000000001a2437d in __wt_meta_ckptlist_get ()
#4 0x0000000001a65218 in __checkpoint_worker.isra.10 ()
#5 0x0000000001a64888 in __checkpoint_apply ()
#6 0x0000000001a6657a in __txn_checkpoint ()
#7 0x0000000001a66e17 in __wt_txn_checkpoint ()
#8 0x0000000001a57854 in __session_checkpoint ()
#9 0x00000000019e4f8f in __ckpt_server ()
#10 0x00007f80c2dd5851 in start_thread () from /lib64/libpthread.so.0
#11 0x0000003403ee767d in clone () from /lib64/libc.so.6
為什么建索引會跟 checkpoint 有沖突?分析索引代碼發(fā)現(xiàn),前臺建索引時,mongod 會使用 wiredtiger 的 bulk cursor,而openBulkCursor是要競爭 checkpoint 鎖的(個人理解是避免在 bulk insert 過程中出現(xiàn) checkpoint),所以 createIndex 會阻塞等待 checkpoint 完成。
// src/cursor/cur_file.c:__wt_curfile_open
/* Bulk handles require exclusive access. */
if (bulk)
LF_SET(WT_BTREE_BULK | WT_DHANDLE_EXCLUSIVE);
/* Get the handle and lock it while the cursor is using it. */
if (WT_PREFIX_MATCH(uri, "file:" ) {
/*
* If we are opening exclusive, get the handle while holding
* the checkpoint lock. This prevents a bulk cursor open
* failing with EBUSY due to a database-wide checkpoint.
*/
if (LF_ISSET(WT_DHANDLE_EXCLUSIVE))
WT_WITH_CHECKPOINT_LOCK(session, ret,
ret = __wt_session_get_btree_ckpt(
session, uri, cfg, flags));
另外從目前的實現(xiàn)看,后臺建索引時并不是 bulk cursor,而是使用普通的 cursor 逐條插入,故不會去競爭 checkpoint 的鎖,上述測試代碼在createIndex 時加上{background: true}選項時問題解決。
建議用戶在建立索引時,盡量選擇后臺建索引的方式,可能性能上不如前臺方式,但后臺建索引對業(yè)務(wù)的影響是最小的(前臺建索引還會獲取 db 的寫鎖,導(dǎo)致 db 上的讀寫都被阻塞),最好的方式是 DDL 和 DML 分離,在業(yè)務(wù)代碼中不要出現(xiàn)建索引、建集合的邏輯,預(yù)先創(chuàng)建好,業(yè)務(wù)只做CRUD 操作。
測試問題2
這個問題主要跟文件系統(tǒng)機制相關(guān),testdb 下創(chuàng)建了數(shù)萬個集合,對應(yīng)到 wiredtiger 的實現(xiàn),會出現(xiàn)一個目錄下數(shù)萬個文件的情況(集合的每個索引也要對應(yīng)一個文件),而從ext4文件系統(tǒng)層面上,在目錄里創(chuàng)建文件,先要遍歷整個目錄下所有的文件項,文件越多效率越低。
上述問題通常的解決方法是『將扁平化的目錄層次化』,對應(yīng)到 mongodb,就是將數(shù)萬個集合分散到多個 DB 里,具體方法如下。
配置 storage.directoryPerDB 選項為 true
業(yè)務(wù)上將集合分散到多個 DB 里(如100個,平均每個目錄下就只有幾百個文件)
總結(jié)
MongoDB 使用 wiredtiger 引擎時,大量集合的場景(通常業(yè)務(wù)設(shè)計上是有問題的),可能會遇到很多未知的問題,畢竟這不屬于常見的應(yīng)用場景,官方在這方面的測試支持也會相對弱些,比如上述提到的2個問題,還有之前分享的一個集合太多無法同步的問題,建議大家使用 MongoDB 時,合理設(shè)計數(shù)據(jù)模型,避免踩不必要的坑。 |
|