2024-05-19 22:07:58 +03:00
|
|
|
|
/// \copyright SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
/// \author Леонид Юрьев aka Leonid Yuriev <leo@yuriev.ru> \date 2015-2024
|
|
|
|
|
|
|
|
|
|
#include "internals.h"
|
|
|
|
|
|
2024-12-11 21:22:04 +03:00
|
|
|
|
int iov_init(MDBX_txn *const txn, iov_ctx_t *ctx, size_t items, size_t npages, mdbx_filehandle_t fd,
|
|
|
|
|
bool check_coherence) {
|
2024-05-19 22:07:58 +03:00
|
|
|
|
ctx->env = txn->env;
|
|
|
|
|
ctx->ior = &txn->env->ioring;
|
|
|
|
|
ctx->fd = fd;
|
|
|
|
|
ctx->coherency_timestamp =
|
2024-12-11 21:22:04 +03:00
|
|
|
|
(check_coherence || txn->env->lck->pgops.incoherence.weak) ? 0 : UINT64_MAX /* не выполнять сверку */;
|
|
|
|
|
ctx->err = osal_ioring_prepare(ctx->ior, items, pgno_align2os_bytes(txn->env, npages));
|
2024-05-19 22:07:58 +03:00
|
|
|
|
if (likely(ctx->err == MDBX_SUCCESS)) {
|
|
|
|
|
#if MDBX_NEED_WRITTEN_RANGE
|
|
|
|
|
ctx->flush_begin = MAX_PAGENO;
|
|
|
|
|
ctx->flush_end = MIN_PAGENO;
|
|
|
|
|
#endif /* MDBX_NEED_WRITTEN_RANGE */
|
|
|
|
|
osal_ioring_reset(ctx->ior);
|
|
|
|
|
}
|
|
|
|
|
return ctx->err;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-11 21:22:04 +03:00
|
|
|
|
static void iov_callback4dirtypages(iov_ctx_t *ctx, size_t offset, void *data, size_t bytes) {
|
2024-05-19 22:07:58 +03:00
|
|
|
|
MDBX_env *const env = ctx->env;
|
|
|
|
|
eASSERT(env, (env->flags & MDBX_WRITEMAP) == 0);
|
|
|
|
|
|
|
|
|
|
page_t *wp = (page_t *)data;
|
|
|
|
|
eASSERT(env, wp->pgno == bytes2pgno(env, offset));
|
|
|
|
|
eASSERT(env, bytes2pgno(env, bytes) >= (is_largepage(wp) ? wp->pages : 1u));
|
|
|
|
|
eASSERT(env, (wp->flags & P_ILL_BITS) == 0);
|
|
|
|
|
|
|
|
|
|
if (likely(ctx->err == MDBX_SUCCESS)) {
|
|
|
|
|
const page_t *const rp = ptr_disp(env->dxb_mmap.base, offset);
|
|
|
|
|
VALGRIND_MAKE_MEM_DEFINED(rp, bytes);
|
|
|
|
|
MDBX_ASAN_UNPOISON_MEMORY_REGION(rp, bytes);
|
|
|
|
|
osal_flush_incoherent_mmap(rp, bytes, globals.sys_pagesize);
|
|
|
|
|
/* check with timeout as the workaround
|
|
|
|
|
* 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 */
|
2024-12-11 21:22:04 +03:00
|
|
|
|
if ((MDBX_FORCE_CHECK_MMAP_COHERENCY || ctx->coherency_timestamp != UINT64_MAX) &&
|
2024-05-19 22:07:58 +03:00
|
|
|
|
unlikely(memcmp(wp, rp, bytes))) {
|
|
|
|
|
ctx->coherency_timestamp = 0;
|
|
|
|
|
env->lck->pgops.incoherence.weak =
|
2024-12-11 21:22:04 +03:00
|
|
|
|
(env->lck->pgops.incoherence.weak >= INT32_MAX) ? INT32_MAX : env->lck->pgops.incoherence.weak + 1;
|
2024-05-19 22:07:58 +03:00
|
|
|
|
WARNING("catch delayed/non-arrived page %" PRIaPGNO " %s", wp->pgno,
|
|
|
|
|
"(workaround for incoherent flaw of unified page/buffer cache)");
|
|
|
|
|
do
|
2024-12-11 21:22:04 +03:00
|
|
|
|
if (coherency_timeout(&ctx->coherency_timestamp, wp->pgno, env) != MDBX_RESULT_TRUE) {
|
2024-05-19 22:07:58 +03:00
|
|
|
|
ctx->err = MDBX_PROBLEM;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
while (unlikely(memcmp(wp, rp, bytes)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (likely(bytes == env->ps))
|
|
|
|
|
page_shadow_release(env, wp, 1);
|
|
|
|
|
else {
|
|
|
|
|
do {
|
|
|
|
|
eASSERT(env, wp->pgno == bytes2pgno(env, offset));
|
|
|
|
|
eASSERT(env, (wp->flags & P_ILL_BITS) == 0);
|
|
|
|
|
size_t npages = is_largepage(wp) ? wp->pages : 1u;
|
|
|
|
|
size_t chunk = pgno2bytes(env, npages);
|
|
|
|
|
eASSERT(env, bytes >= chunk);
|
|
|
|
|
page_t *next = ptr_disp(wp, chunk);
|
|
|
|
|
page_shadow_release(env, wp, npages);
|
|
|
|
|
wp = next;
|
|
|
|
|
offset += chunk;
|
|
|
|
|
bytes -= chunk;
|
|
|
|
|
} while (bytes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void iov_complete(iov_ctx_t *ctx) {
|
|
|
|
|
if ((ctx->env->flags & MDBX_WRITEMAP) == 0)
|
|
|
|
|
osal_ioring_walk(ctx->ior, ctx, iov_callback4dirtypages);
|
|
|
|
|
osal_ioring_reset(ctx->ior);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int iov_write(iov_ctx_t *ctx) {
|
|
|
|
|
eASSERT(ctx->env, !iov_empty(ctx));
|
|
|
|
|
osal_ioring_write_result_t r = osal_ioring_write(ctx->ior, ctx->fd);
|
|
|
|
|
#if MDBX_ENABLE_PGOP_STAT
|
|
|
|
|
ctx->env->lck->pgops.wops.weak += r.wops;
|
|
|
|
|
#endif /* MDBX_ENABLE_PGOP_STAT */
|
|
|
|
|
ctx->err = r.err;
|
|
|
|
|
if (unlikely(ctx->err != MDBX_SUCCESS))
|
|
|
|
|
ERROR("Write error: %s", mdbx_strerror(ctx->err));
|
|
|
|
|
iov_complete(ctx);
|
|
|
|
|
return ctx->err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int iov_page(MDBX_txn *txn, iov_ctx_t *ctx, page_t *dp, size_t npages) {
|
|
|
|
|
MDBX_env *const env = txn->env;
|
|
|
|
|
tASSERT(txn, ctx->err == MDBX_SUCCESS);
|
|
|
|
|
tASSERT(txn, dp->pgno >= MIN_PAGENO && dp->pgno < txn->geo.first_unallocated);
|
|
|
|
|
tASSERT(txn, is_modifable(txn, dp));
|
|
|
|
|
tASSERT(txn, !(dp->flags & ~(P_BRANCH | P_LEAF | P_DUPFIX | P_LARGE)));
|
|
|
|
|
|
|
|
|
|
if (is_shadowed(txn, dp)) {
|
|
|
|
|
tASSERT(txn, !(txn->flags & MDBX_WRITEMAP));
|
|
|
|
|
dp->txnid = txn->txnid;
|
|
|
|
|
tASSERT(txn, is_spilled(txn, dp));
|
|
|
|
|
#if MDBX_AVOID_MSYNC
|
|
|
|
|
doit:;
|
|
|
|
|
#endif /* MDBX_AVOID_MSYNC */
|
2024-12-11 21:22:04 +03:00
|
|
|
|
int err = osal_ioring_add(ctx->ior, pgno2bytes(env, dp->pgno), dp, pgno2bytes(env, npages));
|
2024-05-19 22:07:58 +03:00
|
|
|
|
if (unlikely(err != MDBX_SUCCESS)) {
|
|
|
|
|
ctx->err = err;
|
|
|
|
|
if (unlikely(err != MDBX_RESULT_TRUE)) {
|
|
|
|
|
iov_complete(ctx);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
err = iov_write(ctx);
|
|
|
|
|
tASSERT(txn, iov_empty(ctx));
|
|
|
|
|
if (likely(err == MDBX_SUCCESS)) {
|
2024-12-11 21:22:04 +03:00
|
|
|
|
err = osal_ioring_add(ctx->ior, pgno2bytes(env, dp->pgno), dp, pgno2bytes(env, npages));
|
2024-05-19 22:07:58 +03:00
|
|
|
|
if (unlikely(err != MDBX_SUCCESS)) {
|
|
|
|
|
iov_complete(ctx);
|
|
|
|
|
return ctx->err = err;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
tASSERT(txn, ctx->err == MDBX_SUCCESS);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
tASSERT(txn, txn->flags & MDBX_WRITEMAP);
|
|
|
|
|
#if MDBX_AVOID_MSYNC
|
|
|
|
|
goto doit;
|
|
|
|
|
#endif /* MDBX_AVOID_MSYNC */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if MDBX_NEED_WRITTEN_RANGE
|
2024-12-11 21:22:04 +03:00
|
|
|
|
ctx->flush_begin = (ctx->flush_begin < dp->pgno) ? ctx->flush_begin : dp->pgno;
|
|
|
|
|
ctx->flush_end = (ctx->flush_end > dp->pgno + (pgno_t)npages) ? ctx->flush_end : dp->pgno + (pgno_t)npages;
|
2024-05-19 22:07:58 +03:00
|
|
|
|
#endif /* MDBX_NEED_WRITTEN_RANGE */
|
|
|
|
|
return MDBX_SUCCESS;
|
|
|
|
|
}
|