MongoDB建立大量集合測試問題
問題背景
對使用 wiredtiger 引擎的 mongod 進行如下測試,不斷的『建立集合、建立索引,插入一條記錄』,然後統計這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); // 建立集合
db[collName].createIndex({name: 1}); // 建立索引
db[collName].insert(doc); // 插入一條記錄
var end = (new Date()).getTime(); // 統計耗時
print("cost: " + (end - start));
}
隨著集合數越來越多,測試過程中發現2個問題
- 偶爾會出現耗時很長的請求(1s、2s、3s..不斷上升),統計了下頻率,大約1分鐘左右出現一次。
- 平均耗時不斷增加,從最開始平均10ms 不到,一直到20ms、30ms、40ms…
測試問題1
因為耗時很長的請求頻率大概1分鐘一次,跟 wiredtiger 預設的60scheckpoint 很接近,懷疑問題跟 checkpoint 有關,從執行慢日誌看,耗時長是因為 createIndex 的原因。
通過當時的 pstack 發現,建立索引的執行緒正在等鎖,只有 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::openBulkCursor(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<mongo::IndexAccessMethod::BulkBuilder> >, bool, bool, std::set<mongo::RecordId, std::less<mongo::RecordId>, std::allocator<mongo::RecordId> >*) ()
#11 0x0000000000b07410 in mongo::MultiIndexBlock::doneInserting(std::set<mongo::RecordId, std::less<mongo::RecordId>, std::allocator<mongo::RecordId> >*) ()
#12 0x0000000000b0797d in mongo::MultiIndexBlock::insertAllDocumentsInCollection(std::set<mongo::RecordId, std::less<mongo::RecordId>, std::allocator<mongo::RecordId> >*) ()
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 有衝突?分析索引程式碼發現,前臺建索引時,mongod 會使用 wiredtiger 的 bulk cursor,而openBulkCursor是要競爭 checkpoint 鎖的(個人理解是避免在 bulk insert 過程中出現 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));
另外從目前的實現看,後臺建索引時並不是 bulk cursor,而是使用普通的 cursor 逐條插入,故不會去競爭 checkpoint 的鎖,上述測試程式碼在createIndex 時加上{background: true}
選項時問題解決。
建議使用者在建立索引時,儘量選擇後臺建索引的方式,可能效能上不如前臺方式,但後臺建索引對業務的影響是最小的(前臺建索引還會獲取 db 的寫鎖,導致 db 上的讀寫都被阻塞),最好的方式是 DDL 和 DML 分離,在業務程式碼中不要出現建索引、建集合的邏輯,預先建立好,業務只做CRUD 操作。
測試問題2
這個問題主要跟檔案系統機制相關,testdb 下建立了數萬個集合,對應到 wiredtiger 的實現,會出現一個目錄下數萬個檔案的情況(集合的每個索引也要對應一個檔案),而從ext4檔案系統層面上,在目錄裡建立檔案,先要遍歷整個目錄下所有的檔案項,檔案越多效率越低。
上述問題通常的解決方法是『將扁平化的目錄層次化』,對應到 mongodb,就是將數萬個集合分散到多個 DB 裡,具體方法如下。
- 配置 storage.directoryPerDB 選項為 true
- 業務上將集合分散到多個 DB 裡(如100個,平均每個目錄下就只有幾百個檔案)
總結
MongoDB 使用 wiredtiger 引擎時,大量集合的場景(通常業務設計上是有問題的),可能會遇到很多未知的問題,畢竟這不屬於常見的應用場景,官方在這方面的測試支援也會相對弱些,比如上述提到的2個問題,還有之前分享的一個集合太多無法同步的問題,建議大家使用 MongoDB 時,合理設計資料模型,避免踩不必要的坑。
相關文章
- 【Mongodb】 對 shard 進行大量資料拆分測試MongoDB
- mongodb 分片叢集建立分片集合MongoDB
- 測試CMS同步問題測試CMS同步問題
- 測試工具集合
- 測試面試問題(二)面試
- iOS測試奇葩問題iOS
- 測試跨域問題跨域
- mongodb replica sets 測試MongoDB
- 軟體測試面試問題_介面測試(二)面試
- Hadoop測試常見問題和測試方法Hadoop
- 測試MySQL鎖的問題MySql
- TPA測試專案管理系統-測試問題管理專案管理
- 迅速插入大量測試資料的方法
- 測試團隊效率問題思考
- 軟體測試面試問題(一)面試
- 滲透測試 網站安全測試行業問題分析網站行業
- MongoDB之固定集合MongoDB
- tempdb大量閂鎖等待問題分析
- 軟體測試面試常見問題面試
- app 測試環境切換問題APP
- 使用UDL測試SQLServer連線問題SQLServer
- Web ADI測試遇到的問題Web
- QTP測試AJAX時的等待問題QT
- 軟體測試中存在的問題
- 迴歸測試遇到的問題求助
- MongoDB 資料庫建立刪除、表(集合)建立刪除、資料增刪改查MongoDB資料庫
- myBatis 基礎測試 表關聯關係配置 集合 測試MyBatis
- PHP相關問題集合PHP
- Myexclipse建立Junit測試
- 初級軟體測試必問面試題面試題
- 效能測試工具的 Coordinated Omission 問題
- jboss3.2.1 mysql4.0.17 測試問題S3MySql
- 軟體測試員的思考問題方式
- 相同 App 同步迭代中的測試問題APP
- selenium自動化測試面試集合面試
- 建立測試佇列,傳送接受測試訊息佇列
- 建立測試用例以及測試結果儲存
- Unixbench的使用(綜合效能測試、2D測試)和問題解決