mirror of
https://github.com/isar/libmdbx.git
synced 2024-12-30 01:54:13 +08:00
mdbx: переработка контроля "некогерентности" для уменьшения накладных расходов.
Существует проблема https://libmdbx.dqdkfa.ru/dead-github/issues/269, которая проявляется только при специфической неупорядоченности внутри ядра ОС, когда страницы, записанные в файл отображенный в память, становятся видны в памяти посредством работы unified page cache: - если записанная последней мета-страница "обгоняет" ранее записанные, т.е. когда записанное в файл позже становится видимым в отображении раньше, чем записанное ранее. Теперь, вместо постоянной полной сверки записываемых страниц, выполняется легковесная проверка при старте транзакций, с переключением в режим "как раньше" при обнаружении проблемы. В результате, в некоторых сценариях возвращается 5-10% производительности, а в отдельных синтетических тестах до 30%.
This commit is contained in:
parent
0498114469
commit
a06fe4f168
82
src/core.c
82
src/core.c
@ -4546,10 +4546,15 @@ typedef struct iov_ctx {
|
||||
|
||||
__must_check_result static int iov_init(MDBX_txn *const txn, iov_ctx_t *ctx,
|
||||
size_t items, size_t npages,
|
||||
mdbx_filehandle_t fd) {
|
||||
mdbx_filehandle_t fd,
|
||||
bool check_coherence) {
|
||||
ctx->env = txn->mt_env;
|
||||
ctx->ior = &txn->mt_env->me_ioring;
|
||||
ctx->fd = fd;
|
||||
ctx->coherency_timestamp =
|
||||
(check_coherence || txn->mt_env->me_lck->mti_pgop_stat.incoherence.weak)
|
||||
? 0
|
||||
: UINT64_MAX /* не выполнять сверку */;
|
||||
ctx->err = osal_ioring_prepare(ctx->ior, items,
|
||||
pgno_align2os_bytes(txn->mt_env, npages));
|
||||
if (likely(ctx->err == MDBX_SUCCESS)) {
|
||||
@ -4582,9 +4587,63 @@ static void iov_callback4dirtypages(iov_ctx_t *ctx, size_t offset, void *data,
|
||||
MDBX_ASAN_UNPOISON_MEMORY_REGION(rp, bytes);
|
||||
osal_flush_incoherent_mmap(rp, bytes, env->me_os_psize);
|
||||
/* check with timeout as the workaround
|
||||
* for https://libmdbx.dqdkfa.ru/dead-github/issues/269 */
|
||||
if (unlikely(memcmp(wp, rp, bytes))) {
|
||||
* for https://libmdbx.dqdkfa.ru/dead-github/issues/269
|
||||
*
|
||||
* Проблема проявляется только при неупорядоченности: если записанная
|
||||
* последней мета-страница "обгоняет" ранее записанные, т.е. когда
|
||||
* записанное в файл позже становится видимым в отображении раньше,
|
||||
* чем записанное ранее.
|
||||
*
|
||||
* Исходно здесь всегда выполнялась полная сверка. Это давало полную
|
||||
* гарантию защиты от проявления проблемы, но порождало накладные расходы.
|
||||
* В некоторых сценариях наблюдалось снижение производительности до 10-15%,
|
||||
* а в синтетических тестах до 30%. Конечно никто не вникал в причины,
|
||||
* а просто останавливался на мнении "libmdbx не быстрее LMDB",
|
||||
* например: https://clck.ru/3386er
|
||||
*
|
||||
* Поэтому после серии экспериментов и тестов реализовано следующее:
|
||||
* 0. Посредством опции сборки MDBX_FORCE_CHECK_MMAP_COHERENCY=1
|
||||
* можно включить полную сверку после записи.
|
||||
* Остальные пункты являются взвешенным компромиссом между полной
|
||||
* гарантией обнаружения проблемы и бесполезными затратами на системах
|
||||
* без этого недостатка.
|
||||
* 1. При старте транзакций проверяется соответствие выбранной мета-страницы
|
||||
* корневым страницам b-tree проверяется. Эта проверка показала себя
|
||||
* достаточной без сверки после записи. При обнаружении "некогерентности"
|
||||
* эти случаи подсчитываются, а при их ненулевом счетчике выполняется
|
||||
* полная сверка. Таким образом, произойдет переключение в режим полной
|
||||
* сверки, если показавшая себя достаточной проверка заметит проявление
|
||||
* проблемы хоты-бы раз.
|
||||
* 2. Сверка не выполняется при фиксации транзакции, так как:
|
||||
* - при наличии проблемы "не-когерентности" (при отложенном копировании
|
||||
* или обновлении PTE, после возврата из write-syscall), проверка
|
||||
* в этом процессе не гарантирует актуальность данных в другом
|
||||
* процессе, который может запустить транзакцию сразу после коммита;
|
||||
* - сверка только последнего блока позволяет почти восстановить
|
||||
* производительность в больших транзакциях, но одновременно размывает
|
||||
* уверенность в отсутствии сбоев, чем обесценивает всю затею;
|
||||
* - после записи данных будет записана мета-страница, соответствие
|
||||
* которой корневым страницам b-tree проверяется при старте
|
||||
* транзакций, и только эта проверка показала себя достаточной;
|
||||
* 3. При спиллинге производится полная сверка записанных страниц. Тут был
|
||||
* соблазн сверять не полностью, а например начало и конец каждого блока.
|
||||
* Но при спиллинге возможна ситуация повторного вытеснения страниц, в
|
||||
* том числе large/overflow. При этом возникает риск прочитать в текущей
|
||||
* транзакции старую версию страницы, до повторной записи. В этом случае
|
||||
* могут возникать крайне редкие невоспроизводимые ошибки. С учетом того
|
||||
* что спиллинг выполняет крайне редко, решено отказаться от экономии
|
||||
* в пользу надежности. */
|
||||
#ifndef MDBX_FORCE_CHECK_MMAP_COHERENCY
|
||||
#define MDBX_FORCE_CHECK_MMAP_COHERENCY 0
|
||||
#endif /* MDBX_FORCE_CHECK_MMAP_COHERENCY */
|
||||
if ((MDBX_FORCE_CHECK_MMAP_COHERENCY ||
|
||||
ctx->coherency_timestamp != UINT64_MAX) &&
|
||||
unlikely(memcmp(wp, rp, bytes))) {
|
||||
ctx->coherency_timestamp = 0;
|
||||
env->me_lck->mti_pgop_stat.incoherence.weak =
|
||||
(env->me_lck->mti_pgop_stat.incoherence.weak >= INT32_MAX)
|
||||
? INT32_MAX
|
||||
: env->me_lck->mti_pgop_stat.incoherence.weak + 1;
|
||||
WARNING("catch delayed/non-arrived page %" PRIaPGNO " %s", wp->mp_pgno,
|
||||
"(workaround for incoherent flaw of unified page/buffer cache)");
|
||||
do
|
||||
@ -5074,7 +5133,8 @@ __cold static int txn_spill_slowpath(MDBX_txn *const txn, MDBX_cursor *const m0,
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
txn->mt_env->me_overlapped_fd ? txn->mt_env->me_overlapped_fd :
|
||||
#endif
|
||||
txn->mt_env->me_lazy_fd);
|
||||
txn->mt_env->me_lazy_fd,
|
||||
true);
|
||||
if (unlikely(rc != MDBX_SUCCESS))
|
||||
goto bailout;
|
||||
|
||||
@ -8530,6 +8590,11 @@ static bool coherency_check(const MDBX_env *env, const txnid_t txnid,
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
if (unlikely(!ok) && report)
|
||||
env->me_lck->mti_pgop_stat.incoherence.weak =
|
||||
(env->me_lck->mti_pgop_stat.incoherence.weak >= INT32_MAX)
|
||||
? INT32_MAX
|
||||
: env->me_lck->mti_pgop_stat.incoherence.weak + 1;
|
||||
return ok;
|
||||
}
|
||||
|
||||
@ -8579,11 +8644,16 @@ static int coherency_check_written(const MDBX_env *env, const txnid_t txnid,
|
||||
const bool report = !(timestamp && *timestamp);
|
||||
const txnid_t head_txnid = meta_txnid(meta);
|
||||
if (unlikely(head_txnid < MIN_TXNID || (head_txnid < txnid))) {
|
||||
if (report)
|
||||
if (report) {
|
||||
env->me_lck->mti_pgop_stat.incoherence.weak =
|
||||
(env->me_lck->mti_pgop_stat.incoherence.weak >= INT32_MAX)
|
||||
? INT32_MAX
|
||||
: env->me_lck->mti_pgop_stat.incoherence.weak + 1;
|
||||
WARNING("catch %s txnid %" PRIaTXN " for meta_%" PRIaPGNO " %s",
|
||||
(head_txnid < MIN_TXNID) ? "invalid" : "unexpected", head_txnid,
|
||||
bytes2pgno(env, ptr_dist(meta, env->me_map)),
|
||||
"(workaround for incoherent flaw of unified page/buffer cache)");
|
||||
}
|
||||
return coherency_timeout(timestamp, 0);
|
||||
}
|
||||
return coherency_check_readed(env, head_txnid, meta->mm_dbs, meta, timestamp);
|
||||
@ -11678,7 +11748,7 @@ int mdbx_txn_commit_ex(MDBX_txn *txn, MDBX_commit_latency *latency) {
|
||||
|
||||
iov_ctx_t write_ctx;
|
||||
rc = iov_init(txn, &write_ctx, txn->tw.dirtylist->length,
|
||||
txn->tw.dirtylist->pages_including_loose, fd);
|
||||
txn->tw.dirtylist->pages_including_loose, fd, false);
|
||||
if (unlikely(rc != MDBX_SUCCESS)) {
|
||||
ERROR("txn-%s: error %d", "iov-init", rc);
|
||||
goto fail;
|
||||
|
@ -622,6 +622,11 @@ typedef struct pgop_stat {
|
||||
MDBX_atomic_uint64_t prefault; /* Number of prefault write operations */
|
||||
MDBX_atomic_uint64_t mincore; /* Number of mincore() calls */
|
||||
|
||||
MDBX_atomic_uint32_t
|
||||
incoherence; /* number of https://libmdbx.dqdkfa.ru/dead-github/issues/269
|
||||
caught */
|
||||
MDBX_atomic_uint32_t reserved;
|
||||
|
||||
/* Статистика для профилирования GC.
|
||||
* Логически эти данные может быть стоит вынести в другую структуру,
|
||||
* но разница будет сугубо косметическая. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user