diff options
author | Michael Cahill <michael.cahill@mongodb.com> | 2016-08-26 12:50:11 +1000 |
---|---|---|
committer | Alex Gorrod <alexander.gorrod@mongodb.com> | 2016-08-26 12:50:11 +1000 |
commit | a48b73bdd3da302262582a04830e981ca60ad1cb (patch) | |
tree | 09035bcdfaf9edeba769ffe6b4fc9270daa2bbca | |
parent | 0cdefa89f437bab5aaf554a2fa211028b674bbd0 (diff) | |
download | mongo-a48b73bdd3da302262582a04830e981ca60ad1cb.tar.gz |
WT-2816 General improvements to WiredTiger eviction performance (#2949)
A set of changes to the eviction algorithm including:
* Fix a bug in how many items can be added to the urgent queue
* Have the eviction server sleep less so it recovers from disruptions faster.
* Only have application threads evict dirty pages if they are blocked on the dirty trigger.
* Swap eviction queues when one becomes empty.
* Have the eviction server populate the "other" queue whenever it notices that it isn't full.
-rw-r--r-- | bench/wtperf/wtperf.c | 3 | ||||
-rw-r--r-- | dist/stat_data.py | 6 | ||||
-rw-r--r-- | src/conn/conn_cache.c | 8 | ||||
-rw-r--r-- | src/conn/conn_dhandle.c | 4 | ||||
-rw-r--r-- | src/evict/evict_lru.c | 334 | ||||
-rw-r--r-- | src/evict/evict_page.c | 6 | ||||
-rw-r--r-- | src/include/btree.i | 2 | ||||
-rw-r--r-- | src/include/cache.h | 29 | ||||
-rw-r--r-- | src/include/cache.i | 38 | ||||
-rw-r--r-- | src/include/extern.h | 2 | ||||
-rw-r--r-- | src/include/stat.h | 2 | ||||
-rw-r--r-- | src/include/wiredtiger.in | 10 | ||||
-rw-r--r-- | src/support/stat.c | 11 | ||||
-rw-r--r-- | src/txn/txn_ckpt.c | 2 | ||||
-rw-r--r-- | test/csuite/wt2834_join_bloom_fix/main.c | 7 | ||||
-rw-r--r-- | tools/wtstats/stat_data.py | 6 |
16 files changed, 290 insertions, 180 deletions
diff --git a/bench/wtperf/wtperf.c b/bench/wtperf/wtperf.c index d969d625b76..d3d54fff6e3 100644 --- a/bench/wtperf/wtperf.c +++ b/bench/wtperf/wtperf.c @@ -1540,8 +1540,7 @@ execute_populate(CONFIG *cfg) print_ops_sec = 0; } else { print_secs = (double)msecs / (double)MSEC_PER_SEC; - print_ops_sec = - (uint64_t)((cfg->icount / msecs) / MSEC_PER_SEC); + print_ops_sec = (uint64_t)(cfg->icount / print_secs); } lprintf(cfg, 0, 1, "Load time: %.2f\n" "load ops/sec: %" PRIu64, diff --git a/dist/stat_data.py b/dist/stat_data.py index 51cc487f04c..af410a341bd 100644 --- a/dist/stat_data.py +++ b/dist/stat_data.py @@ -201,12 +201,12 @@ connection_stats = [ CacheStat('cache_eviction_queue_empty', 'eviction server candidate queue empty when topping up'), CacheStat('cache_eviction_queue_not_empty', 'eviction server candidate queue not empty when topping up'), CacheStat('cache_eviction_server_evicting', 'eviction server evicting pages'), - CacheStat('cache_eviction_server_not_evicting', 'eviction server populating queue, but not evicting pages'), CacheStat('cache_eviction_server_slept', 'eviction server slept, because we did not make progress with eviction'), CacheStat('cache_eviction_server_toobig', 'eviction server skipped very large page'), CacheStat('cache_eviction_slow', 'eviction server unable to reach eviction goal'), CacheStat('cache_eviction_split_internal', 'internal pages split during eviction'), CacheStat('cache_eviction_split_leaf', 'leaf pages split during eviction'), + CacheStat('cache_eviction_state', 'eviction state', 'no_clear,no_scale'), CacheStat('cache_eviction_walk', 'pages walked for eviction'), CacheStat('cache_eviction_walks_active', 'files with active eviction walks', 'no_clear,no_scale,size'), CacheStat('cache_eviction_walks_started', 'files with new eviction walks started'), @@ -276,8 +276,8 @@ connection_stats = [ LogStat('log_slot_unbuffered', 'consolidated slot unbuffered writes'), LogStat('log_sync', 'log sync operations'), LogStat('log_sync_dir', 'log sync_dir operations'), - LogStat('log_sync_dir_duration', 'log sync_dir time duration (usecs)'), - LogStat('log_sync_duration', 'log sync time duration (usecs)'), + LogStat('log_sync_dir_duration', 'log sync_dir time duration (usecs)', 'no_clear,no_scale'), + LogStat('log_sync_duration', 'log sync time duration (usecs)', 'no_clear,no_scale'), LogStat('log_write_lsn', 'log server thread advances write LSN'), LogStat('log_write_lsn_skip', 'log server thread write LSN walk skipped'), LogStat('log_writes', 'log write operations'), diff --git a/src/conn/conn_cache.c b/src/conn/conn_cache.c index e5de28887de..ba1e0210334 100644 --- a/src/conn/conn_cache.c +++ b/src/conn/conn_cache.c @@ -179,9 +179,11 @@ __wt_cache_create(WT_SESSION_IMPL *session, const char *cfg[]) &cache->evict_queues[i].evict_lock, "cache eviction")); } - /* Ensure there is always a non-NULL current queue. */ - cache->evict_current_queue = - &cache->evict_queues[WT_EVICT_URGENT_QUEUE + 1]; + /* Ensure there are always non-NULL queues. */ + cache->evict_current_queue = cache->evict_fill_queue = + &cache->evict_queues[0]; + cache->evict_other_queue = &cache->evict_queues[1]; + cache->evict_urgent_queue = &cache->evict_queues[WT_EVICT_URGENT_QUEUE]; /* * We get/set some values in the cache statistics (rather than have diff --git a/src/conn/conn_dhandle.c b/src/conn/conn_dhandle.c index 3f242b9df7e..9eb4d4a7746 100644 --- a/src/conn/conn_dhandle.c +++ b/src/conn/conn_dhandle.c @@ -572,14 +572,14 @@ __wt_conn_dhandle_discard_single( set_pass_intr = false; if (!F_ISSET(session, WT_SESSION_LOCKED_HANDLE_LIST)) { set_pass_intr = true; - (void)__wt_atomic_add32(&S2C(session)->cache->pass_intr, 1); + (void)__wt_atomic_addv32(&S2C(session)->cache->pass_intr, 1); } /* Try to remove the handle, protected by the data handle lock. */ WT_WITH_HANDLE_LIST_LOCK(session, tret = __conn_dhandle_remove(session, final)); if (set_pass_intr) - (void)__wt_atomic_sub32(&S2C(session)->cache->pass_intr, 1); + (void)__wt_atomic_subv32(&S2C(session)->cache->pass_intr, 1); WT_TRET(tret); /* diff --git a/src/evict/evict_lru.c b/src/evict/evict_lru.c index 8700f06b4a2..56fba412c56 100644 --- a/src/evict/evict_lru.c +++ b/src/evict/evict_lru.c @@ -9,15 +9,15 @@ #include "wt_internal.h" static int __evict_clear_all_walks(WT_SESSION_IMPL *); -static int __evict_helper(WT_SESSION_IMPL *); static int WT_CDECL __evict_lru_cmp(const void *, const void *); static int __evict_lru_pages(WT_SESSION_IMPL *, bool); static int __evict_lru_walk(WT_SESSION_IMPL *); static int __evict_page(WT_SESSION_IMPL *, bool); static int __evict_pass(WT_SESSION_IMPL *); static int __evict_server(WT_SESSION_IMPL *, bool *); -static int __evict_walk(WT_SESSION_IMPL *, uint32_t); -static int __evict_walk_file(WT_SESSION_IMPL *, uint32_t, u_int, u_int *); +static int __evict_walk(WT_SESSION_IMPL *, WT_EVICT_QUEUE *); +static int __evict_walk_file( + WT_SESSION_IMPL *, WT_EVICT_QUEUE *, u_int, u_int *, bool *); #define WT_EVICT_HAS_WORKERS(s) \ (S2C(s)->evict_threads.current_threads > 1) @@ -52,6 +52,10 @@ __evict_read_gen(const WT_EVICT_ENTRY *entry) if (__wt_page_is_empty(page)) return (WT_READGEN_OLDEST); + /* Any large page in memory is likewise a good choice. */ + if (page->memory_footprint > btree->splitmempage) + return (WT_READGEN_OLDEST); + /* * The base read-generation is skewed by the eviction priority. * Internal pages are also adjusted, we prefer to evict leaf pages. @@ -139,6 +143,29 @@ __wt_evict_list_clear_page(WT_SESSION_IMPL *session, WT_REF *ref) } /* + * __evict_queue_empty -- + * Is the queue empty? + */ +static inline bool +__evict_queue_empty(WT_EVICT_QUEUE *queue) +{ + return (queue->evict_current == NULL || + queue->evict_candidates == 0); +} + +/* + * __evict_queue_full -- + * Is the queue full (i.e., it has been populated with candidates and none + * of them have been evicted yet)? + */ +static inline bool +__evict_queue_full(WT_EVICT_QUEUE *queue) +{ + return (queue->evict_current == queue->evict_queue && + queue->evict_candidates != 0); +} + +/* * __wt_evict_server_wake -- * Wake the eviction server thread. */ @@ -216,7 +243,7 @@ __wt_evict_thread_run(WT_SESSION_IMPL *session, WT_THREAD *thread) session, cache->evict_cond, did_work); __wt_verbose(session, WT_VERB_EVICTSERVER, "waking"); } else - WT_ERR(__evict_helper(session)); + WT_ERR(__evict_lru_pages(session, false)); } /* @@ -390,25 +417,6 @@ __wt_evict_destroy(WT_SESSION_IMPL *session) } /* - * __evict_helper -- - * Thread to help evict pages from the cache. - */ -static int -__evict_helper(WT_SESSION_IMPL *session) -{ - WT_CONNECTION_IMPL *conn; - WT_DECL_RET; - - conn = S2C(session); - - if ((ret = __evict_lru_pages(session, false)) == WT_NOTFOUND) - __wt_cond_wait(session, conn->evict_threads.wait_cond, 10000); - else - WT_RET(ret); - return (0); -} - -/* * __evict_update_work -- * Configure eviction work state. */ @@ -429,16 +437,7 @@ __evict_update_work(WT_SESSION_IMPL *session) if (!F_ISSET(conn, WT_CONN_EVICTION_RUN)) return (false); - /* - * Setup the number of refs to consider in each handle, depending - * on how many handles are open. We want to consider less candidates - * from each file as more files are open. Handle the case where there - * are no files open by adding 1. - */ - cache->evict_max_refs_per_file = - WT_MAX(100, WT_MILLION / (conn->open_file_count + 1)); - - if (cache->evict_queues[WT_EVICT_URGENT_QUEUE].evict_current != NULL) + if (!__evict_queue_empty(cache->evict_urgent_queue)) FLD_SET(cache->state, WT_EVICT_STATE_URGENT); /* @@ -451,19 +450,25 @@ __evict_update_work(WT_SESSION_IMPL *session) bytes_inuse = __wt_cache_bytes_inuse(cache); if (bytes_inuse > (cache->eviction_target * bytes_max) / 100) FLD_SET(cache->state, WT_EVICT_STATE_CLEAN); + if (bytes_inuse > (cache->eviction_trigger * bytes_max) / 100) + FLD_SET(cache->state, WT_EVICT_STATE_CLEAN_HARD); + + dirty_inuse = __wt_cache_dirty_leaf_inuse(cache); + if (dirty_inuse > (cache->eviction_dirty_target * bytes_max) / 100) + FLD_SET(cache->state, WT_EVICT_STATE_DIRTY); + if (dirty_inuse > (cache->eviction_dirty_trigger * bytes_max) / 100) + FLD_SET(cache->state, WT_EVICT_STATE_DIRTY_HARD); /* * Scrub dirty pages and keep them in cache if we are less than half - * way between the cache target and trigger. + * way to the clean or dirty trigger. */ if (bytes_inuse < ((cache->eviction_target + cache->eviction_trigger) * + bytes_max) / 200 && dirty_inuse < + ((cache->eviction_dirty_target + cache->eviction_dirty_trigger) * bytes_max) / 200) FLD_SET(cache->state, WT_EVICT_STATE_SCRUB); - dirty_inuse = __wt_cache_dirty_leaf_inuse(cache); - if (dirty_inuse > (cache->eviction_dirty_target * bytes_max) / 100) - FLD_SET(cache->state, WT_EVICT_STATE_DIRTY); - /* * If the cache has been stuck and is now under control, clear the * stuck flag. @@ -479,6 +484,8 @@ __evict_update_work(WT_SESSION_IMPL *session) FLD_SET(cache->state, WT_EVICT_STATE_AGGRESSIVE); } + WT_STAT_FAST_CONN_SET(session, cache_eviction_state, cache->state); + return (FLD_ISSET(cache->state, WT_EVICT_STATE_ALL | WT_EVICT_STATE_URGENT)); } @@ -555,7 +562,17 @@ __evict_pass(WT_SESSION_IMPL *session) cache->bytes_dirty_intl + cache->bytes_dirty_leaf); WT_RET(__evict_lru_walk(session)); - WT_RET_NOTFOUND_OK(__evict_lru_pages(session, true)); + + /* + * If the queue has been empty recently, keep queuing more + * pages to evict. If the rate of queuing pages is high + * enough, this score will go to zero, in which case the + * eviction server might as well help out with eviction. + */ + if (cache->evict_empty_score < WT_EVICT_EMPTY_SCORE_CUTOFF || + (!WT_EVICT_HAS_WORKERS(session) && + !__evict_queue_empty(cache->evict_urgent_queue))) + WT_RET_NOTFOUND_OK(__evict_lru_pages(session, true)); /* * If we're making progress, keep going; if we're not making @@ -575,8 +592,7 @@ __evict_pass(WT_SESSION_IMPL *session) */ WT_STAT_FAST_CONN_INCR(session, cache_eviction_server_slept); - __wt_cond_wait(session, - cache->evict_cond, WT_THOUSAND * WT_MAX(loop, 1)); + __wt_cond_wait(session, cache->evict_cond, WT_THOUSAND); if (loop == 100) { /* @@ -688,13 +704,13 @@ __wt_evict_file_exclusive_on(WT_SESSION_IMPL *session) * this point. */ F_SET(btree, WT_BTREE_NO_EVICTION); - (void)__wt_atomic_add32(&cache->pass_intr, 1); + (void)__wt_atomic_addv32(&cache->pass_intr, 1); WT_FULL_BARRIER(); /* Clear any existing LRU eviction walk for the file. */ WT_WITH_PASS_LOCK(session, ret, ret = __evict_clear_walk(session)); - (void)__wt_atomic_sub32(&cache->pass_intr, 1); + (void)__wt_atomic_subv32(&cache->pass_intr, 1); WT_ERR(ret); /* @@ -762,7 +778,6 @@ __wt_evict_file_exclusive_off(WT_SESSION_IMPL *session) __wt_spin_unlock(session, &cache->evict_walk_lock); } -#define APP_EVICT_THRESHOLD 3 /* Threshold to help evict */ /* * __evict_lru_pages -- * Get pages from the LRU queue to evict. @@ -770,34 +785,26 @@ __wt_evict_file_exclusive_off(WT_SESSION_IMPL *session) static int __evict_lru_pages(WT_SESSION_IMPL *session, bool is_server) { - WT_CACHE *cache; + WT_CONNECTION_IMPL *conn; WT_DECL_RET; - uint64_t app_evict_percent, total_evict; - /* - * The server will not help evict if the threads are coping with - * eviction workload, that is, if fewer than the threshold of the - * pages are evicted by application threads. - */ - if (is_server && WT_EVICT_HAS_WORKERS(session)) { - cache = S2C(session)->cache; - total_evict = cache->app_evicts + - cache->server_evicts + cache->worker_evicts; - app_evict_percent = (100 * cache->app_evicts) / - (total_evict + 1); - if (app_evict_percent < APP_EVICT_THRESHOLD) { - WT_STAT_FAST_CONN_INCR(session, - cache_eviction_server_not_evicting); - return (0); - } - } + conn = S2C(session); /* * Reconcile and discard some pages: EBUSY is returned if a page fails * eviction because it's unavailable, continue in that case. */ - while ((ret = __evict_page(session, is_server)) == 0 || ret == EBUSY) - ; + while (F_ISSET(S2C(session), WT_CONN_EVICTION_RUN) && ret == 0) + if ((ret = __evict_page(session, is_server)) == EBUSY) + ret = 0; + + /* If a worker thread found the queue empty, pause. */ + if (ret == WT_NOTFOUND && !is_server && + F_ISSET(S2C(session), WT_CONN_EVICTION_RUN)) { + ret = 0; + __wt_cond_wait(session, conn->evict_threads.wait_cond, 10000); + } + return (ret); } @@ -810,21 +817,45 @@ __evict_lru_walk(WT_SESSION_IMPL *session) { WT_CACHE *cache; WT_DECL_RET; - WT_EVICT_QUEUE *queue; + WT_EVICT_QUEUE *queue, *other_queue; uint64_t read_gen_oldest; - uint32_t candidates, entries, queue_index; + uint32_t candidates, entries; cache = S2C(session)->cache; + /* Age out the score of how much the queue has been empty recently. */ + cache->evict_empty_score = (99 * cache->evict_empty_score) / 100; + /* Fill the next queue (that isn't the urgent queue). */ - queue_index = - 1 + (cache->evict_queue_fill++ % (WT_EVICT_QUEUE_MAX - 1)); - queue = &cache->evict_queues[queue_index]; + queue = cache->evict_fill_queue; + other_queue = cache->evict_queues + (1 - (queue - cache->evict_queues)); + + /* If this queue is full, try the other one. */ + if (__evict_queue_full(queue) && !__evict_queue_full(other_queue)) + queue = other_queue; + other_queue = cache->evict_fill_queue = + &cache->evict_queues[1 - (queue - cache->evict_queues)]; + + /* + * If both queues are full and haven't been empty on recent refills, + * we're done. + */ + if (__evict_queue_full(queue) && + cache->evict_empty_score < WT_EVICT_EMPTY_SCORE_CUTOFF) + return (0); /* Get some more pages to consider for eviction. */ - if ((ret = __evict_walk(cache->walk_session, queue_index)) != 0) + if ((ret = __evict_walk(cache->walk_session, queue)) != 0) return (ret == EBUSY ? 0 : ret); + /* Make sure the other queue is current before locking. */ + if (cache->evict_current_queue != other_queue) { + __wt_spin_lock(session, &cache->evict_queue_lock); + cache->evict_other_queue = queue; + cache->evict_current_queue = other_queue; + __wt_spin_unlock(session, &cache->evict_queue_lock); + } + /* Sort the list into LRU order and restart. */ __wt_spin_lock(session, &queue->evict_lock); @@ -909,20 +940,20 @@ __evict_lru_walk(WT_SESSION_IMPL *session) } } - queue->evict_current = queue->evict_queue; - __wt_spin_unlock(session, &queue->evict_lock); - - /* - * Now we can set the next queue. - */ - __wt_spin_lock(session, &cache->evict_queue_lock); - if (cache->evict_current_queue->evict_current == NULL) + if (__evict_queue_empty(queue)) { + /* + * This score varies between 0 (if the queue hasn't been empty + * for a long time) and 100 (if the queue has been empty the + * last 10 times we filled up. + */ + cache->evict_empty_score = WT_MIN(100, + cache->evict_empty_score + WT_EVICT_EMPTY_SCORE_BUMP); WT_STAT_FAST_CONN_INCR(session, cache_eviction_queue_empty); - else + } else WT_STAT_FAST_CONN_INCR(session, cache_eviction_queue_not_empty); - cache->evict_current_queue = queue; - __wt_spin_unlock(session, &cache->evict_queue_lock); + queue->evict_current = queue->evict_queue; + __wt_spin_unlock(session, &queue->evict_lock); /* * Signal any application or helper threads that may be waiting @@ -938,16 +969,15 @@ __evict_lru_walk(WT_SESSION_IMPL *session) * Fill in the array by walking the next set of pages. */ static int -__evict_walk(WT_SESSION_IMPL *session, uint32_t queue_index) +__evict_walk(WT_SESSION_IMPL *session, WT_EVICT_QUEUE *queue) { WT_BTREE *btree; WT_CACHE *cache; WT_CONNECTION_IMPL *conn; WT_DATA_HANDLE *dhandle; WT_DECL_RET; - WT_EVICT_QUEUE *queue; u_int max_entries, prev_slot, retries, slot, start_slot, spins; - bool dhandle_locked, incr; + bool dhandle_locked, incr, progress; conn = S2C(session); cache = S2C(session)->cache; @@ -960,7 +990,6 @@ __evict_walk(WT_SESSION_IMPL *session, uint32_t queue_index) * Set the starting slot in the queue and the maximum pages added * per walk. */ - queue = &cache->evict_queues[queue_index]; start_slot = slot = queue->evict_entries; max_entries = WT_MIN(slot + WT_EVICT_WALK_INCR, cache->evict_slots); @@ -1027,11 +1056,18 @@ retry: while (slot < max_entries && ret == 0) { continue; /* - * Also skip files that are checkpointing or configured to - * stick in cache until we get aggressive. + * Skip files that are checkpointing if we are only looking for + * dirty pages. */ - if ((btree->checkpointing != WT_CKPT_OFF || - btree->evict_priority != 0) && + if (btree->checkpointing != WT_CKPT_OFF && + !FLD_ISSET(cache->state, WT_EVICT_STATE_CLEAN)) + continue; + + /* + * Skip files that are configured to stick in cache until we + * become aggressive. + */ + if (btree->evict_priority != 0 && !FLD_ISSET(cache->state, WT_EVICT_STATE_AGGRESSIVE)) continue; @@ -1049,6 +1085,7 @@ retry: while (slot < max_entries && ret == 0) { continue; btree->evict_walk_skips = 0; prev_slot = slot; + progress = false; (void)__wt_atomic_addi32(&dhandle->session_inuse, 1); incr = true; @@ -1069,9 +1106,9 @@ retry: while (slot < max_entries && ret == 0) { !__wt_spin_trylock(session, &cache->evict_walk_lock)) { if (!F_ISSET(btree, WT_BTREE_NO_EVICTION)) { cache->evict_file_next = dhandle; - WT_WITH_DHANDLE(session, dhandle, - ret = __evict_walk_file(session, - queue_index, max_entries, &slot)); + WT_WITH_DHANDLE(session, dhandle, ret = + __evict_walk_file(session, queue, + max_entries, &slot, &progress)); WT_ASSERT(session, session->split_gen == 0); } __wt_spin_unlock(session, &cache->evict_walk_lock); @@ -1081,7 +1118,7 @@ retry: while (slot < max_entries && ret == 0) { * If we didn't find any candidates in the file, skip it next * time. */ - if (slot == prev_slot) + if (slot == prev_slot && !progress) btree->evict_walk_period = WT_MIN( WT_MAX(1, 2 * btree->evict_walk_period), 100); else @@ -1149,6 +1186,14 @@ __evict_push_candidate(WT_SESSION_IMPL *session, evict->btree = S2BT(session); evict->ref = ref; evict->score = __evict_read_gen(evict); + + /* Adjust for size when doing dirty eviction. */ + if (FLD_ISSET(S2C(session)->cache->state, WT_EVICT_STATE_DIRTY) && + evict->score != WT_READGEN_OLDEST && evict->score != UINT64_MAX && + !__wt_page_is_modified(ref->page)) + evict->score += WT_MEGABYTE - + WT_MIN(WT_MEGABYTE, ref->page->memory_footprint); + return (true); } @@ -1158,14 +1203,13 @@ __evict_push_candidate(WT_SESSION_IMPL *session, */ static int __evict_walk_file(WT_SESSION_IMPL *session, - uint32_t queue_index, u_int max_entries, u_int *slotp) + WT_EVICT_QUEUE *queue, u_int max_entries, u_int *slotp, bool *progressp) { WT_BTREE *btree; WT_CACHE *cache; WT_CONNECTION_IMPL *conn; WT_DECL_RET; WT_EVICT_ENTRY *end, *evict, *start; - WT_EVICT_QUEUE *queue; WT_PAGE *page; WT_PAGE_MODIFY *mod; WT_REF *ref; @@ -1179,7 +1223,6 @@ __evict_walk_file(WT_SESSION_IMPL *session, conn = S2C(session); btree = S2BT(session); cache = conn->cache; - queue = &cache->evict_queues[queue_index]; internal_pages = restarts = 0; /* @@ -1288,10 +1331,13 @@ __evict_walk_file(WT_SESSION_IMPL *session, __wt_cache_read_gen_new(session, page); /* Pages we no longer need (clean or dirty), are found money. */ - if (page->read_gen == WT_READGEN_OLDEST) { + if (page->read_gen == WT_READGEN_OLDEST || + page->memory_footprint >= btree->splitmempage) { WT_STAT_FAST_CONN_INCR( session, cache_eviction_pages_queued_oldest); - goto fast; + if (__wt_page_evict_urgent(session, ref)) + *progressp = true; + continue; } if (__wt_page_is_empty(page) || @@ -1421,30 +1467,67 @@ __evict_get_ref( WT_SESSION_IMPL *session, bool is_server, WT_BTREE **btreep, WT_REF **refp) { WT_CACHE *cache; + WT_DECL_RET; WT_EVICT_ENTRY *evict; - WT_EVICT_QUEUE *queue, *urgent_queue; + WT_EVICT_QUEUE *other_queue, *queue, *urgent_queue; uint32_t candidates; + bool is_app, urgent_ok; cache = S2C(session)->cache; - urgent_queue = &cache->evict_queues[WT_EVICT_URGENT_QUEUE]; + is_app = !F_ISSET(session, WT_SESSION_INTERNAL); + urgent_ok = (!is_app && !is_server) || + !WT_EVICT_HAS_WORKERS(session) || + FLD_ISSET(cache->state, WT_EVICT_STATE_AGGRESSIVE); + urgent_queue = cache->evict_urgent_queue; *btreep = NULL; *refp = NULL; - /* Avoid the LRU lock if no pages are available. */ WT_STAT_FAST_CONN_INCR(session, cache_eviction_get_ref); - if (cache->evict_current_queue->evict_current == NULL && - urgent_queue->evict_current == NULL) { + + /* Avoid the LRU lock if no pages are available. */ + if (__evict_queue_empty(cache->evict_current_queue) && + __evict_queue_empty(cache->evict_other_queue) && + __evict_queue_empty(urgent_queue)) { WT_STAT_FAST_CONN_INCR(session, cache_eviction_get_ref_empty); return (WT_NOTFOUND); } - __wt_spin_lock(session, &cache->evict_queue_lock); + /* + * The server repopulates whenever the other queue is not full. + * + * Note that there are pathological cases where there are only enough + * eviction candidates in the cache to fill one queue. In that case, + * we will continually evict one page and attempt to refill the queues. + * Such cases are extremely rare in real applications. + */ + if (is_server && + (cache->evict_empty_score > WT_EVICT_EMPTY_SCORE_CUTOFF || + __evict_queue_empty(cache->evict_fill_queue))) { + do { + if ((!urgent_ok || + __evict_queue_empty(urgent_queue)) && + !__evict_queue_full(cache->evict_fill_queue)) + return (WT_NOTFOUND); + } while ((ret = __wt_spin_trylock( + session, &cache->evict_queue_lock)) == EBUSY); + + WT_RET(ret); + } else + __wt_spin_lock(session, &cache->evict_queue_lock); + + /* + * Check if the current queue needs to change. + * The current queue could have changed while we waited for the lock. + */ + queue = cache->evict_current_queue; + other_queue = cache->evict_other_queue; + if (__evict_queue_empty(queue) && !__evict_queue_empty(other_queue)) { + cache->evict_current_queue = other_queue; + cache->evict_other_queue = queue; + } /* Check the urgent queue first. */ - queue = urgent_queue->evict_current != NULL && - (FLD_ISSET(cache->state, WT_EVICT_STATE_AGGRESSIVE) || - (F_ISSET(session, WT_SESSION_INTERNAL) && - (!is_server || !WT_EVICT_HAS_WORKERS(session)))) ? + queue = urgent_ok && !__evict_queue_empty(urgent_queue) ? urgent_queue : cache->evict_current_queue; __wt_spin_unlock(session, &cache->evict_queue_lock); @@ -1464,7 +1547,7 @@ __evict_get_ref( */ for (;;) { /* Verify there are still pages available. */ - if (queue->evict_current == NULL || (uint32_t) + if (__evict_queue_empty(queue) || (uint32_t) (queue->evict_current - queue->evict_queue) >= candidates) { WT_STAT_FAST_CONN_INCR( session, cache_eviction_get_ref_empty2); @@ -1495,14 +1578,24 @@ __evict_get_ref( * However, we can't skip entries in the urgent queue or they * may never be found again. */ - if (is_server && queue != urgent_queue && - WT_EVICT_HAS_WORKERS(session) && + if (is_server && !urgent_ok && !__evict_check_entry_size(session, evict)) { --evict; break; } /* + * Don't force application threads to evict dirty pages if they + * aren't stalled by the amount of dirty data in cache. + */ + if (is_app && !urgent_ok && + !FLD_ISSET(cache->state, WT_EVICT_STATE_DIRTY_HARD) && + __wt_page_is_modified(evict->ref->page)) { + --evict; + break; + } + + /* * Lock the page while holding the eviction mutex to prevent * multiple attempts to evict it. For pages that are already * being evicted, this operation will fail and we will move on. @@ -1539,7 +1632,7 @@ __evict_get_ref( __wt_spin_unlock(session, &queue->evict_lock); - return ((*refp == NULL) ? WT_NOTFOUND : 0); + return (*refp == NULL ? WT_NOTFOUND : 0); } /* @@ -1702,11 +1795,11 @@ __wt_cache_eviction_worker(WT_SESSION_IMPL *session, bool busy, u_int pct_full) } /* - * __wt_page_evict_soon -- + * __wt_page_evict_urgent -- * Set a page to be evicted as soon as possible. */ -void -__wt_page_evict_soon(WT_SESSION_IMPL *session, WT_REF *ref) +bool +__wt_page_evict_urgent(WT_SESSION_IMPL *session, WT_REF *ref) { WT_CACHE *cache; WT_EVICT_ENTRY *evict; @@ -1718,10 +1811,9 @@ __wt_page_evict_soon(WT_SESSION_IMPL *session, WT_REF *ref) WT_ASSERT(session, !__wt_ref_is_root(ref)); page = ref->page; - page->read_gen = WT_READGEN_OLDEST; if (F_ISSET_ATOMIC(page, WT_PAGE_EVICT_LRU) || F_ISSET(S2BT(session), WT_BTREE_NO_EVICTION)) - return; + return (false); /* Append to the urgent queue if we can. */ cache = S2C(session)->cache; @@ -1734,12 +1826,12 @@ __wt_page_evict_soon(WT_SESSION_IMPL *session, WT_REF *ref) goto done; __wt_spin_lock(session, &urgent_queue->evict_lock); - if (urgent_queue->evict_current == NULL) { + if (__evict_queue_empty(urgent_queue)) { urgent_queue->evict_current = urgent_queue->evict_queue; urgent_queue->evict_candidates = 0; } evict = urgent_queue->evict_queue + urgent_queue->evict_candidates; - if (evict < urgent_queue->evict_queue + WT_EVICT_QUEUE_MAX && + if (evict < urgent_queue->evict_queue + cache->evict_slots && __evict_push_candidate(session, urgent_queue, evict, ref)) { ++urgent_queue->evict_candidates; queued = true; @@ -1756,6 +1848,8 @@ done: __wt_spin_unlock(session, &cache->evict_queue_lock); else __wt_evict_server_wake(session); } + + return (queued); } /* diff --git a/src/evict/evict_page.c b/src/evict/evict_page.c index ff967ea0efd..972c72bbfb0 100644 --- a/src/evict/evict_page.c +++ b/src/evict/evict_page.c @@ -74,7 +74,7 @@ __wt_page_release_evict(WT_SESSION_IMPL *session, WT_REF *ref) (void)__wt_atomic_addv32(&btree->evict_busy, 1); - too_big = page->memory_footprint > btree->splitmempage; + too_big = page->memory_footprint >= btree->splitmempage; if ((ret = __wt_evict(session, ref, false)) == 0) { if (too_big) WT_STAT_FAST_CONN_INCR(session, cache_eviction_force); @@ -527,7 +527,9 @@ __evict_review( else if (F_ISSET(cache, WT_CACHE_STUCK)) LF_SET(WT_EVICT_LOOKASIDE); else if (!__wt_txn_visible_all( - session, page->modify->update_txn)) + session, page->modify->update_txn) || + page->read_gen == WT_READGEN_OLDEST || + page->memory_footprint >= S2BT(session)->splitmempage) LF_SET(WT_EVICT_UPDATE_RESTORE); /* diff --git a/src/include/btree.i b/src/include/btree.i index faaddba9dc7..1ca6426eef6 100644 --- a/src/include/btree.i +++ b/src/include/btree.i @@ -1232,7 +1232,7 @@ __wt_page_can_evict( * previous version might be referenced by an internal page already * been written in the checkpoint, leaving the checkpoint inconsistent. */ - if (btree->checkpointing != WT_CKPT_OFF && modified) { + if (modified && btree->checkpointing != WT_CKPT_OFF) { WT_STAT_FAST_CONN_INCR(session, cache_eviction_checkpoint); WT_STAT_FAST_DATA_INCR(session, cache_eviction_checkpoint); return (false); diff --git a/src/include/cache.h b/src/include/cache.h index f00971ff7d8..c7e817fd391 100644 --- a/src/include/cache.h +++ b/src/include/cache.h @@ -26,8 +26,8 @@ struct __wt_evict_entry { uint64_t score; /* Relative eviction priority */ }; -#define WT_EVICT_URGENT_QUEUE 0 /* Urgent queue index */ -#define WT_EVICT_QUEUE_MAX 3 /* Urgent plus two ordinary queues */ +#define WT_EVICT_QUEUE_MAX 3 /* Two ordinary queues plus urgent */ +#define WT_EVICT_URGENT_QUEUE 2 /* Urgent queue index */ /* * WT_EVICT_QUEUE -- @@ -110,14 +110,19 @@ struct __wt_cache { */ WT_SPINLOCK evict_pass_lock; /* Eviction pass lock */ WT_SESSION_IMPL *walk_session; /* Eviction pass session */ + WT_DATA_HANDLE + *evict_file_next; /* LRU next file to search */ WT_SPINLOCK evict_queue_lock; /* Eviction current queue lock */ WT_EVICT_QUEUE evict_queues[WT_EVICT_QUEUE_MAX]; - WT_EVICT_QUEUE *evict_current_queue;/* LRU current queue in use */ - uint32_t evict_queue_fill; /* LRU eviction queue index to fill */ + WT_EVICT_QUEUE *evict_current_queue; /* LRU current queue in use */ + WT_EVICT_QUEUE *evict_fill_queue; /* LRU next queue to fill */ + WT_EVICT_QUEUE *evict_other_queue; /* LRU queue not in use */ + WT_EVICT_QUEUE *evict_urgent_queue; /* LRU urgent queue */ uint32_t evict_slots; /* LRU list eviction slots */ - WT_DATA_HANDLE - *evict_file_next; /* LRU next file to search */ - uint32_t evict_max_refs_per_file;/* LRU pages per file per pass */ +#define WT_EVICT_EMPTY_SCORE_BUMP 10 +#define WT_EVICT_EMPTY_SCORE_CUTOFF 10 + uint32_t evict_empty_score; /* LRU score of how often queues are + empty on refill. */ /* * Cache pool information. @@ -139,15 +144,17 @@ struct __wt_cache { #define WT_EVICT_STATE_AGGRESSIVE 0x01 /* Eviction isn't making progress: try harder */ #define WT_EVICT_STATE_CLEAN 0x02 /* Evict clean pages */ -#define WT_EVICT_STATE_DIRTY 0x04 /* Evict dirty pages */ -#define WT_EVICT_STATE_SCRUB 0x08 /* Scrub dirty pages pages */ -#define WT_EVICT_STATE_URGENT 0x10 /* Pages are in the urgent queue */ +#define WT_EVICT_STATE_CLEAN_HARD 0x04 /* Clean % blocking app threads */ +#define WT_EVICT_STATE_DIRTY 0x08 /* Evict dirty pages */ +#define WT_EVICT_STATE_DIRTY_HARD 0x10 /* Dirty % blocking app threads */ +#define WT_EVICT_STATE_SCRUB 0x20 /* Scrub dirty pages pages */ +#define WT_EVICT_STATE_URGENT 0x40 /* Pages are in the urgent queue */ #define WT_EVICT_STATE_ALL (WT_EVICT_STATE_CLEAN | WT_EVICT_STATE_DIRTY) uint32_t state; /* * Pass interrupt counter. */ - uint32_t pass_intr; /* Interrupt eviction pass. */ + volatile uint32_t pass_intr; /* Interrupt eviction pass. */ /* * Flags. diff --git a/src/include/cache.i b/src/include/cache.i index 1163826422c..155b1a7ab3d 100644 --- a/src/include/cache.i +++ b/src/include/cache.i @@ -68,6 +68,18 @@ __wt_cache_read_gen_new(WT_SESSION_IMPL *session, WT_PAGE *page) } /* + * __wt_page_evict_soon -- + * Set a page to be evicted as soon as possible. + */ +static inline void +__wt_page_evict_soon(WT_SESSION_IMPL *session, WT_REF *ref) +{ + WT_UNUSED(session); + + ref->page->read_gen = WT_READGEN_OLDEST; +} + +/* * __wt_cache_pages_inuse -- * Return the number of pages in use. */ @@ -188,7 +200,7 @@ __wt_eviction_needed(WT_SESSION_IMPL *session, u_int *pct_fullp) WT_CONNECTION_IMPL *conn; WT_CACHE *cache; uint64_t bytes_inuse, bytes_max; - u_int pct_full; + u_int pct_dirty, pct_full; conn = S2C(session); cache = conn->cache; @@ -212,24 +224,16 @@ __wt_eviction_needed(WT_SESSION_IMPL *session, u_int *pct_fullp) * we involve the application thread. */ pct_full = (u_int)((100 * bytes_inuse) / bytes_max); - if (pct_fullp != NULL) - *pct_fullp = pct_full; - - if (pct_full > cache->eviction_trigger) - return (true); + pct_dirty = + (u_int)((100 * __wt_cache_dirty_leaf_inuse(cache)) / bytes_max); - /* - * Check if there are too many dirty bytes in cache. - * - * We try to avoid penalizing read-only operations by only checking the - * dirty limit once a transaction ID has been allocated, or if the last - * transaction did an update. - */ - if (__wt_cache_dirty_leaf_inuse(cache) > - (cache->eviction_dirty_trigger * bytes_max) / 100) - return (true); + if (pct_fullp != NULL) + *pct_fullp = (u_int)WT_MAX(0, 100 - WT_MIN( + (int)cache->eviction_trigger - (int)pct_full, + (int)cache->eviction_dirty_trigger - (int)pct_dirty)); - return (false); + return (pct_full >= cache->eviction_trigger || + pct_dirty >= cache->eviction_dirty_trigger); } /* diff --git a/src/include/extern.h b/src/include/extern.h index 72e7f1aa9ec..2992a97b727 100644 --- a/src/include/extern.h +++ b/src/include/extern.h @@ -345,7 +345,7 @@ extern int __wt_evict_destroy(WT_SESSION_IMPL *session) WT_GCC_FUNC_DECL_ATTRIBU extern int __wt_evict_file_exclusive_on(WT_SESSION_IMPL *session) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result)); extern void __wt_evict_file_exclusive_off(WT_SESSION_IMPL *session); extern int __wt_cache_eviction_worker(WT_SESSION_IMPL *session, bool busy, u_int pct_full) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result)); -extern void __wt_page_evict_soon(WT_SESSION_IMPL *session, WT_REF *ref); +extern bool __wt_page_evict_urgent(WT_SESSION_IMPL *session, WT_REF *ref); extern void __wt_evict_priority_set(WT_SESSION_IMPL *session, uint64_t v); extern void __wt_evict_priority_clear(WT_SESSION_IMPL *session); extern int __wt_cache_dump(WT_SESSION_IMPL *session, const char *ofile) WT_GCC_FUNC_DECL_ATTRIBUTE((warn_unused_result)); diff --git a/src/include/stat.h b/src/include/stat.h index 1df24382236..e0d0a6eb780 100644 --- a/src/include/stat.h +++ b/src/include/stat.h @@ -289,10 +289,10 @@ struct __wt_connection_stats { int64_t cache_eviction_queue_empty; int64_t cache_eviction_queue_not_empty; int64_t cache_eviction_server_evicting; - int64_t cache_eviction_server_not_evicting; int64_t cache_eviction_server_toobig; int64_t cache_eviction_server_slept; int64_t cache_eviction_slow; + int64_t cache_eviction_state; int64_t cache_eviction_worker_evicting; int64_t cache_eviction_force_fail; int64_t cache_eviction_walks_active; diff --git a/src/include/wiredtiger.in b/src/include/wiredtiger.in index f915f9de3d6..10141cd34c1 100644 --- a/src/include/wiredtiger.in +++ b/src/include/wiredtiger.in @@ -4289,15 +4289,15 @@ extern int wiredtiger_extension_terminate(WT_CONNECTION *connection); #define WT_STAT_CONN_CACHE_EVICTION_QUEUE_NOT_EMPTY 1042 /*! cache: eviction server evicting pages */ #define WT_STAT_CONN_CACHE_EVICTION_SERVER_EVICTING 1043 -/*! cache: eviction server populating queue, but not evicting pages */ -#define WT_STAT_CONN_CACHE_EVICTION_SERVER_NOT_EVICTING 1044 /*! cache: eviction server skipped very large page */ -#define WT_STAT_CONN_CACHE_EVICTION_SERVER_TOOBIG 1045 +#define WT_STAT_CONN_CACHE_EVICTION_SERVER_TOOBIG 1044 /*! cache: eviction server slept, because we did not make progress with * eviction */ -#define WT_STAT_CONN_CACHE_EVICTION_SERVER_SLEPT 1046 +#define WT_STAT_CONN_CACHE_EVICTION_SERVER_SLEPT 1045 /*! cache: eviction server unable to reach eviction goal */ -#define WT_STAT_CONN_CACHE_EVICTION_SLOW 1047 +#define WT_STAT_CONN_CACHE_EVICTION_SLOW 1046 +/*! cache: eviction state */ +#define WT_STAT_CONN_CACHE_EVICTION_STATE 1047 /*! cache: eviction worker thread evicting pages */ #define WT_STAT_CONN_CACHE_EVICTION_WORKER_EVICTING 1048 /*! cache: failed eviction of pages that exceeded the in-memory maximum */ diff --git a/src/support/stat.c b/src/support/stat.c index 49cb3bebc07..8927be862c1 100644 --- a/src/support/stat.c +++ b/src/support/stat.c @@ -562,10 +562,10 @@ static const char * const __stats_connection_desc[] = { "cache: eviction server candidate queue empty when topping up", "cache: eviction server candidate queue not empty when topping up", "cache: eviction server evicting pages", - "cache: eviction server populating queue, but not evicting pages", "cache: eviction server skipped very large page", "cache: eviction server slept, because we did not make progress with eviction", "cache: eviction server unable to reach eviction goal", + "cache: eviction state", "cache: eviction worker thread evicting pages", "cache: failed eviction of pages that exceeded the in-memory maximum", "cache: files with active eviction walks", @@ -805,10 +805,10 @@ __wt_stat_connection_clear_single(WT_CONNECTION_STATS *stats) stats->cache_eviction_queue_empty = 0; stats->cache_eviction_queue_not_empty = 0; stats->cache_eviction_server_evicting = 0; - stats->cache_eviction_server_not_evicting = 0; stats->cache_eviction_server_toobig = 0; stats->cache_eviction_server_slept = 0; stats->cache_eviction_slow = 0; + /* not clearing cache_eviction_state */ stats->cache_eviction_worker_evicting = 0; stats->cache_eviction_force_fail = 0; /* not clearing cache_eviction_walks_active */ @@ -905,9 +905,9 @@ __wt_stat_connection_clear_single(WT_CONNECTION_STATS *stats) stats->log_write_lsn = 0; stats->log_write_lsn_skip = 0; stats->log_sync = 0; - stats->log_sync_duration = 0; + /* not clearing log_sync_duration */ stats->log_sync_dir = 0; - stats->log_sync_dir_duration = 0; + /* not clearing log_sync_dir_duration */ stats->log_writes = 0; stats->log_slot_consolidated = 0; /* not clearing log_max_filesize */ @@ -1049,13 +1049,12 @@ __wt_stat_connection_aggregate( WT_STAT_READ(from, cache_eviction_queue_not_empty); to->cache_eviction_server_evicting += WT_STAT_READ(from, cache_eviction_server_evicting); - to->cache_eviction_server_not_evicting += - WT_STAT_READ(from, cache_eviction_server_not_evicting); to->cache_eviction_server_toobig += WT_STAT_READ(from, cache_eviction_server_toobig); to->cache_eviction_server_slept += WT_STAT_READ(from, cache_eviction_server_slept); to->cache_eviction_slow += WT_STAT_READ(from, cache_eviction_slow); + to->cache_eviction_state += WT_STAT_READ(from, cache_eviction_state); to->cache_eviction_worker_evicting += WT_STAT_READ(from, cache_eviction_worker_evicting); to->cache_eviction_force_fail += diff --git a/src/txn/txn_ckpt.c b/src/txn/txn_ckpt.c index 9a062611422..5406367c372 100644 --- a/src/txn/txn_ckpt.c +++ b/src/txn/txn_ckpt.c @@ -732,7 +732,7 @@ __txn_checkpoint(WT_SESSION_IMPL *session, const char *cfg[]) WT_ERR(__wt_epoch(session, &fsync_stop)); fsync_duration_usecs = WT_TIMEDIFF_US(fsync_stop, fsync_start); WT_STAT_FAST_CONN_INCR(session, txn_checkpoint_fsync_post); - WT_STAT_FAST_CONN_INCRV(session, + WT_STAT_FAST_CONN_SET(session, txn_checkpoint_fsync_post_duration, fsync_duration_usecs); WT_ERR(__checkpoint_verbose_track(session, diff --git a/test/csuite/wt2834_join_bloom_fix/main.c b/test/csuite/wt2834_join_bloom_fix/main.c index f080be09163..1e2d919d3c7 100644 --- a/test/csuite/wt2834_join_bloom_fix/main.c +++ b/test/csuite/wt2834_join_bloom_fix/main.c @@ -44,7 +44,7 @@ void (*custom_die)(void) = NULL; #define N_RECORDS 100000 #define N_INSERT 1000000 -void *populate(TEST_OPTS *opts); +void populate(TEST_OPTS *opts); int main(int argc, char *argv[]) @@ -110,8 +110,6 @@ main(int argc, char *argv[]) testutil_check(opts->conn->open_session( opts->conn, NULL, NULL, &session)); - testutil_check(session->begin_transaction(session, - "isolation=snapshot")); testutil_check(session->open_cursor(session, posturi, NULL, NULL, &postcur)); testutil_check(session->open_cursor(session, @@ -161,7 +159,7 @@ main(int argc, char *argv[]) return (0); } -void *populate(TEST_OPTS *opts) +void populate(TEST_OPTS *opts) { WT_CURSOR *maincur; WT_SESSION *session; @@ -198,5 +196,4 @@ void *populate(TEST_OPTS *opts) } maincur->close(maincur); session->close(session, NULL); - return (NULL); } diff --git a/tools/wtstats/stat_data.py b/tools/wtstats/stat_data.py index b93f2449c63..1eb60c9d513 100644 --- a/tools/wtstats/stat_data.py +++ b/tools/wtstats/stat_data.py @@ -7,6 +7,7 @@ no_scale_per_second_list = [ 'cache: bytes currently in the cache', 'cache: bytes not belonging to page images in the cache', 'cache: eviction currently operating in aggressive mode', + 'cache: eviction state', 'cache: files with active eviction walks', 'cache: hazard pointer maximum array length', 'cache: maximum bytes configured', @@ -20,6 +21,8 @@ no_scale_per_second_list = [ 'cache: tracked dirty pages in the cache', 'connection: files currently open', 'data-handle: connection data handles currently active', + 'log: log sync time duration (usecs)', + 'log: log sync_dir time duration (usecs)', 'log: maximum log file size', 'log: number of pre-allocated log files to create', 'log: total log buffer size', @@ -100,6 +103,7 @@ no_clear_list = [ 'cache: bytes currently in the cache', 'cache: bytes not belonging to page images in the cache', 'cache: eviction currently operating in aggressive mode', + 'cache: eviction state', 'cache: files with active eviction walks', 'cache: maximum bytes configured', 'cache: maximum page size at eviction', @@ -111,6 +115,8 @@ no_clear_list = [ 'cache: tracked dirty pages in the cache', 'connection: files currently open', 'data-handle: connection data handles currently active', + 'log: log sync time duration (usecs)', + 'log: log sync_dir time duration (usecs)', 'log: maximum log file size', 'log: number of pre-allocated log files to create', 'log: total log buffer size', |