- 求職 : Linux運(yùn)維
- 論壇徽章:
- 203
|
MongoDB 3.2.9 版本在 wiredtiger 上做了很多改進(jìn),但不幸的時(shí),這個(gè)版本引入了一個(gè)新的 bug,持續(xù)大量 insert/update 場(chǎng)景,有一定的可能導(dǎo)致 wiredtiger 進(jìn)入 deadlock,MongoDB 官方迅速的在3.2.10里修復(fù)了該問(wèn)題,該版本在 wiredtiger 內(nèi)存使用上也做了控制,盡量避免了因?yàn)閮?nèi)存碎片導(dǎo)致 wiredtiger 內(nèi)存使用遠(yuǎn)超出 cacheSizeGB 配置的問(wèn)題,目前 MongoDB 3.2.10+ 的版本已經(jīng)非常穩(wěn)定。
MongoDB 目前有4個(gè)可配置的參數(shù)來(lái)支持 wiredtiger 存儲(chǔ)引擎的 eviction 策略調(diào)優(yōu),其含義是:
參數(shù) 默認(rèn)值 含義
eviction_target 80 當(dāng) cache used 超過(guò) eviction_target,后臺(tái)evict線程開始淘汰 CLEAN PAGE
eviction_trigger 95 當(dāng) cache used 超過(guò) eviction_trigger,用戶線程也開始淘汰 CLEAN PAGE
eviction_dirty_target 5 當(dāng) cache dirty 超過(guò) eviction_dirty_target,后臺(tái)evict線程開始淘汰 DIRTY PAGE
eviction_dirty_trigger 20 當(dāng) cache dirty 超過(guò) eviction_dirty_trigger, 用戶線程也開始淘汰 DIRTY PAGE
上述默認(rèn)值是在3.2.10版本里調(diào)整的,如果你正在使用 MongoDB 3.0/3.2,遇到了 wiredtiger 相關(guān)問(wèn)題(絕大部分場(chǎng)景遇不到),可以先升級(jí)到3.2的最新版本。
在此基礎(chǔ)上(使用3.2.10+),如果通過(guò) mongostat 發(fā)現(xiàn) used、dirty 持續(xù)超出eviction_trigger、eviction_dirty_trigger,這時(shí)用戶的請(qǐng)求線程也會(huì)去干 evict的事情(開銷大),會(huì)導(dǎo)致請(qǐng)求延時(shí)上升,這時(shí)基本可以判定,mongodb 已經(jīng)存在資源不足的問(wèn)題,即用戶讀寫『從磁盤上讀取的數(shù)據(jù)的速率』 遠(yuǎn)遠(yuǎn) 超出了 『mongodb 將數(shù)據(jù)從內(nèi)存淘汰出去速率』,可以做的優(yōu)化包括:
增強(qiáng) IO 能力
SATA 盤升級(jí)到 SSD
將 wiredtiger 的數(shù)據(jù)和 journal 分到不同的盤上
擴(kuò)充機(jī)器內(nèi)存,加大 wiredtiger cache
cache 越大,越能平衡上述2個(gè)速率的差距
eviction 參數(shù)調(diào)優(yōu)
降低eviction_target 或 eviction_dirty_target,讓evict 盡早將數(shù)據(jù)從 wiredtiger 的 cache 刷到操作系統(tǒng)的 page cache,以便提早刷盤。
db.runCommand({setParameter: 1, wiredTigerEngineRuntimeConfig: “eviction_dirty_target=5,eviction_target=80″})
–
接下來(lái)分析一下 MongoDB 3.2.9 bug 產(chǎn)生的原因,想了解源碼的往下看
當(dāng)用戶請(qǐng)求打開wiredtiger cursor 的時(shí)候,會(huì)檢查是否需要 進(jìn)行 cache 淘汰,當(dāng) 『cache 使用百分比超出 eviction_trigger』 或者 『cache 臟頁(yè)百分比超過(guò) eviction_dirty_triger』,用戶請(qǐng)求線程就會(huì)進(jìn)入到 cache 淘汰邏輯,執(zhí)行__wt_cache_eviction_worker
static inline bool
__wt_eviction_needed(WT_SESSION_IMPL *session) {
bytes_inuse = __wt_cache_bytes_inuse(cache);
bytes_max = conn->cache_size + 1; // Avoid division by zero
pct_full = (u_int)((100 * bytes_inuse) / bytes_max);
if (pct_full > cache->eviction_trigger)
return true;
if (__wt_cache_dirty_inuse(cache) >
(cache->eviction_dirty_trigger * bytes_max) / 100)
return (true);
return false;
}
用戶線程執(zhí)行 __wt_cache_eviction_worker 會(huì)持續(xù)的檢查 __wt_eviction_needed 條件是否滿足,不需要 evict 時(shí),用戶線程就會(huì)繼續(xù)響應(yīng)請(qǐng)求;如果需要evict,就會(huì)從 evict queue 里取 page 進(jìn)行淘汰,當(dāng) evict queue 為空時(shí),用戶線程 wait 一段時(shí)間繼續(xù)重復(fù)上述邏輯。
int
__wt_cache_eviction_worker(WT_SESSION_IMPL *session, bool busy, u_int pct_full) {
for (; {
/* See if eviction is still needed. */
if (!__wt_eviction_needed(session, busy, &pct_full) ||
(pct_full pages_evict > init_evict_count + max_pages_evicted))
return (0);
/* Evict a page. */
switch (ret = __evict_page(session, false)) {
case 0:
if (busy)
return (0);
/* FALLTHROUGH */
case EBUSY:
break;
case WT_NOTFOUND:
/* Allow the queue to re-populate before retrying. */
__wt_cond_wait(
session, conn->evict_threads.wait_cond, 10000);
cache->app_waits++;
break;
}
}
后臺(tái)的 evict server 線程會(huì)遍歷 wiredtiger 的 btree 頁(yè),將滿足條件的的 page 加入到 evict queue 并進(jìn)行淘汰,每一輪都會(huì)通過(guò) __evict_update_work 更新當(dāng)前的工作狀態(tài)信息,并告知調(diào)用者是否還需要繼續(xù)執(zhí)行 evict。
// 這個(gè)就是執(zhí)行上述表格中描述的邏輯
// WT_CACHE_EVICT_CLEAN 標(biāo)記代表后臺(tái)線程需要淘汰 CLEAN PAGE
// WT_CACHE_EVICT_CLEAN_HARD 代表用戶線程也需要去淘汰 CLEAN PAGE
// DIRTY* 參數(shù)類似
static bool
__evict_update_work(WT_SESSION_IMPL *session)
{
bytes_max = conn->cache_size + 1;
bytes_inuse = __wt_cache_bytes_inuse(cache);
if (bytes_inuse > (cache->eviction_target * bytes_max) / 100)
F_SET(cache, WT_CACHE_EVICT_CLEAN);
if (__wt_eviction_clean_needed(session, NULL))
F_SET(cache, WT_CACHE_EVICT_CLEAN_HARD);
dirty_inuse = __wt_cache_dirty_leaf_inuse(cache);
if (dirty_inuse > (cache->eviction_dirty_target * bytes_max) / 100)
F_SET(cache, WT_CACHE_EVICT_DIRTY);
if (__wt_eviction_dirty_needed(session, NULL))
F_SET(cache, WT_CACHE_EVICT_DIRTY_HARD);
return (F_ISSET(cache, WT_CACHE_EVICT_ALL | WT_CACHE_EVICT_URGENT));
}
__evict_update_work 最后通過(guò) F_ISSET(cache, WT_CACHE_EVICT) 來(lái)判斷是否要繼續(xù) evict
#define WT_CACHE_EVICT_ALL (WT_CACHE_EVICT_CLEAN | WT_CACHE_EVICT_DIRTY)
這樣可能會(huì)出現(xiàn)一種情況,eviction_trigger 或 eviction_dirty_trigger 觸發(fā)了,這時(shí)后臺(tái)線程是需要繼續(xù)進(jìn)行 evict 的,但eviction_target、eviction_ditry_target都不滿足,導(dǎo)致上述判斷條件返回 false,后臺(tái)線程不繼續(xù)干活,這樣就不會(huì)有新的 page 加入到 evict queue,而上述用戶線程還在繼續(xù)等待 evict,一直不會(huì)返回,這樣就會(huì)導(dǎo)致請(qǐng)求 hang 。
修復(fù)上述問(wèn)題的主要代碼如下:github commit
主要修改邏輯是,當(dāng) used 超過(guò)eviction_trigger時(shí),同時(shí)也設(shè)置WT_CACHE_EVICT_CLEAN標(biāo)記(DIRTY 類似),這樣確保有用戶線程在等時(shí),evict 一定會(huì)進(jìn)行。
static bool
__evict_update_work(WT_SESSION_IMPL *session)
{
bytes_max = conn->cache_size + 1;
bytes_inuse = __wt_cache_bytes_inuse(cache);
if (bytes_inuse > (cache->eviction_target * bytes_max) / 100)
F_SET(cache, WT_CACHE_EVICT_CLEAN);
if (__wt_eviction_clean_needed(session, NULL))
- F_SET(cache, WT_CACHE_EVICT_CLEAN_HARD);
+ F_SET(cache, WT_CACHE_EVICT_CLEAN | WT_CACHE_EVICT_CLEAN_HARD);
dirty_inuse = __wt_cache_dirty_leaf_inuse(cache);
if (dirty_inuse > (cache->eviction_dirty_target * bytes_max) / 100)
F_SET(cache, WT_CACHE_EVICT_DIRTY);
if (__wt_eviction_dirty_needed(session, NULL))
- F_SET(cache, WT_CACHE_EVICT_DIRTY_HARD);
+ F_SET(cache, WT_CACHE_EVICT_DIRTY | WT_CACHE_EVICT_DIRTY_HARD);
return (F_ISSET(cache, WT_CACHE_EVICT_ALL | WT_CACHE_EVICT_URGENT));
}
|
|