From 6c56ed97bbd8ca46abac61886a113ba31e5f1291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B5=D0=BE=D0=BD=D0=B8=D0=B4=20=D0=AE=D1=80=D1=8C?= =?UTF-8?q?=D0=B5=D0=B2=20=28Leonid=20Yuriev=29?= Date: Wed, 6 Nov 2024 17:10:50 +0300 Subject: [PATCH] =?UTF-8?q?mdbx:=20=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0/=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B8=20=D0=BF=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=8F=D0=BD=D0=BD=D0=B0=D1=8F=20=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=B2=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BA=D0=BE=D1=80=D1=80=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=B8=D1=80=D1=83=D1=8E=D1=89=D0=B5=D0=B9=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=82=D0=BD=D0=BE=D0=B9=20=D1=81=D0=B2=D1=8F?= =?UTF-8?q?=D0=B7=D0=B8=20=D0=BF=D1=80=D0=B8=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8=20GC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit При обновлении GC, с помещением/возвратом страниц, возникает рекурсивная зависимость, так как страницы, необходимые для CoW-модификации GC и размещения списков возвращаемых страниц, берутся/выделяются из этих-же списков и/или из GC. Эта рекуррентная зависимость разрешается путём подготовки необходимого запаса страниц и двух-стадийным заполнением списков, с повторением всего цикла при изменении ситуации/расклада, плюс применение некоторых эвристик и поправок. Кроме корректной работы, принципиально важным тут является минимизация количества повторов/рестартов процесса, в том числе исключение возможности бесконечного зацикливания. Существующая реализация многократно/итеративно дорабатывалась. Поэтому она неплохо обкатана и стабильна, но одновременно сложна и запутана. Тем не менее, до последнего момента для текущей реализации были известны условия/сценарии, в которых сходимость итеративного процесса обновления GC нарушалась и при фиксации транзакции возвращалась ошибка MDBX_PROBLEM. Эти условия/сценарии очень специфичны и далеки от реальных практических случаев, поэтому этот недостаток не мешал использованию библиотеки. Этим коммитом добавляется и активируется еще один механизм нацеленный на улучшение сходимости и минимизацию повторов/рестартов. Суть механизма в формировании и учета поправки, которая на следующем цикле позволит учесть все переходные процессы/затраты вне зависимости от их природы, и этим обеспечить моментальную сходимость. В текущем понимании, описанный выше недостаток полностью устраняется/исправляется этим коммитом. --- src/gc-put.c | 80 ++++++++++------------------------------------------ src/gc.h | 12 ++------ 2 files changed, 17 insertions(+), 75 deletions(-) diff --git a/src/gc-put.c b/src/gc-put.c index a9388143..be740cbf 100644 --- a/src/gc-put.c +++ b/src/gc-put.c @@ -454,6 +454,7 @@ static rid_t get_rid_for_reclaimed(MDBX_txn *txn, gcu_t *ctx, (MDBX_PNL_GETSIZE(txn->tw.gc.reclaimed) - ctx->reused_slot) * txn->env->maxgc_large1page) { if (unlikely(ctx->rid <= MIN_TXNID)) { + ctx->dense = true; if (unlikely(MDBX_PNL_GETSIZE(txn->tw.gc.reclaimed) <= ctx->reused_slot)) { NOTICE("** restart: reserve depleted (reused_gc_slot %zu >= " @@ -480,10 +481,10 @@ static rid_t get_rid_for_reclaimed(MDBX_txn *txn, gcu_t *ctx, goto return_error; } const txnid_t gc_first = unaligned_peek_u64(4, key.iov_base); - if (unlikely(gc_first <= MIN_TXNID)) { - DEBUG("%s: no free GC's id(s) less than %" PRIaTXN - " (going dense-mode)", - dbg_prefix(ctx), ctx->rid); + if (unlikely(gc_first <= INITIAL_TXNID)) { + NOTICE("%s: no free GC's id(s) less than %" PRIaTXN + " (going dense-mode)", + dbg_prefix(ctx), ctx->rid); ctx->dense = true; goto return_restart; } @@ -590,19 +591,11 @@ int gc_update(MDBX_txn *txn, gcu_t *ctx) { txn->cursors[FREE_DBI] = &ctx->cursor; int rc; - // tASSERT(txn, MDBX_PNL_GETSIZE(txn->tw.retired_pages) || - // ctx->cleaned_slot < - // (txn->tw.gc.reclaimed ? - // MDBX_PNL_GETSIZE(txn->tw.gc.reclaimed) : 0) - // || ctx->cleaned_id < txn->tw.gc.last_reclaimed); - /* txn->tw.relist[] can grow and shrink during this call. * txn->tw.gc.last_reclaimed and txn->tw.retired_pages[] can only grow. * But page numbers cannot disappear from txn->tw.retired_pages[]. */ -#if MDBX_ENABLE_GC_EXPERIMENTAL retry_clean_adj: ctx->reserve_adj = 0; -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ retry: ctx->loop += ctx->prev_first_unallocated == txn->geo.first_unallocated; TRACE(">> restart, loop %u", ctx->loop); @@ -629,7 +622,8 @@ retry: ctx->reserved = 0; ctx->cleaned_slot = 0; ctx->reused_slot = 0; - ctx->amount = ctx->fill_idx = ~0u; + ctx->amount = 0; + ctx->fill_idx = ~0u; ctx->cleaned_id = 0; ctx->rid = txn->tw.gc.last_reclaimed; while (true) { @@ -746,9 +740,7 @@ retry: env->maxgc_large1page / 2)) { TRACE("%s: reclaimed-list changed %zu -> %zu, retry", dbg_prefix(ctx), ctx->amount, MDBX_PNL_GETSIZE(txn->tw.relist)); -#if MDBX_ENABLE_GC_EXPERIMENTAL ctx->reserve_adj += ctx->reserved - MDBX_PNL_GETSIZE(txn->tw.relist); -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ goto retry; } ctx->amount = MDBX_PNL_GETSIZE(txn->tw.relist); @@ -772,7 +764,6 @@ retry: if (unlikely(rc != MDBX_SUCCESS)) goto bailout; } -#if MDBX_ENABLE_GC_EXPERIMENTAL const size_t left = ctx->amount - ctx->reserved - ctx->reserve_adj; TRACE("%s: amount %zu, reserved %zd, reserve_adj %zu, left %zd, " "lifo-reclaimed-slots %zu, " @@ -780,15 +771,6 @@ retry: dbg_prefix(ctx), ctx->amount, ctx->reserved, ctx->reserve_adj, left, txn->tw.gc.reclaimed ? MDBX_PNL_GETSIZE(txn->tw.gc.reclaimed) : 0, ctx->reused_slot); -#else - const size_t left = ctx->amount - ctx->reserved; - TRACE("%s: amount %zu, reserved %zd, left %zd, " - "lifo-reclaimed-slots %zu, " - "reused-gc-slots %zu", - dbg_prefix(ctx), ctx->amount, ctx->reserved, left, - txn->tw.gc.reclaimed ? MDBX_PNL_GETSIZE(txn->tw.gc.reclaimed) : 0, - ctx->reused_slot); -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ if (0 >= (intptr_t)left) break; @@ -911,9 +893,7 @@ retry: TRACE("%s", " >> filling"); /* Fill in the reserved records */ -#if MDBX_ENABLE_GC_EXPERIMENTAL size_t excess_slots = 0; -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ ctx->fill_idx = txn->tw.gc.reclaimed ? MDBX_PNL_GETSIZE(txn->tw.gc.reclaimed) - ctx->reused_slot @@ -924,15 +904,17 @@ retry: tASSERT(txn, dpl_check(txn)); if (ctx->amount) { MDBX_val key, data; - key.iov_len = data.iov_len = 0; /* avoid MSVC warning */ + key.iov_len = data.iov_len = 0; key.iov_base = data.iov_base = nullptr; size_t left = ctx->amount, excess = 0; if (txn->tw.gc.reclaimed == nullptr) { tASSERT(txn, is_lifo(txn) == 0); rc = outer_first(&ctx->cursor, &key, &data); - if (unlikely(rc != MDBX_SUCCESS)) - goto bailout; + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc != MDBX_NOTFOUND) + goto bailout; + } } else { tASSERT(txn, is_lifo(txn) != 0); } @@ -943,36 +925,29 @@ retry: MDBX_PNL_GETSIZE(txn->tw.relist)); if (txn->tw.gc.reclaimed == nullptr) { tASSERT(txn, is_lifo(txn) == 0); - fill_gc_id = unaligned_peek_u64(4, key.iov_base); + fill_gc_id = + key.iov_base ? unaligned_peek_u64(4, key.iov_base) : MIN_TXNID; if (ctx->fill_idx == 0 || fill_gc_id > txn->tw.gc.last_reclaimed) { -#if MDBX_ENABLE_GC_EXPERIMENTAL if (!left) break; -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ NOTICE("** restart: reserve depleted (fill_idx %zu, fill_id %" PRIaTXN " > last_reclaimed %" PRIaTXN ", left %zu", ctx->fill_idx, fill_gc_id, txn->tw.gc.last_reclaimed, left); -#if MDBX_ENABLE_GC_EXPERIMENTAL ctx->reserve_adj = (ctx->reserve_adj > left) ? ctx->reserve_adj - left : 0; -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ goto retry; } ctx->fill_idx -= 1; } else { tASSERT(txn, is_lifo(txn) != 0); if (ctx->fill_idx >= MDBX_PNL_GETSIZE(txn->tw.gc.reclaimed)) { -#if MDBX_ENABLE_GC_EXPERIMENTAL if (!left) break; -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ NOTICE("** restart: reserve depleted (fill_idx %zu >= " "gc.reclaimed %zu, left %zu", ctx->fill_idx, MDBX_PNL_GETSIZE(txn->tw.gc.reclaimed), left); -#if MDBX_ENABLE_GC_EXPERIMENTAL ctx->reserve_adj = (ctx->reserve_adj > left) ? ctx->reserve_adj - left : 0; -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ goto retry; } ctx->fill_idx += 1; @@ -1001,12 +976,10 @@ retry: excess += delta; TRACE("%s: chunk %zu > left %zu, @%" PRIaTXN, dbg_prefix(ctx), chunk, left, fill_gc_id); -#if MDBX_ENABLE_GC_EXPERIMENTAL if (!left) { excess_slots += 1; goto next; } -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ if ((ctx->loop < 5 && delta > (ctx->loop / 2)) || delta > env->maxgc_large1page) data.iov_len = (left + 1) * sizeof(pgno_t); @@ -1022,10 +995,8 @@ retry: NOTICE("** restart: reclaimed-list changed (%zu -> %zu, loose +%zu)", ctx->amount, MDBX_PNL_GETSIZE(txn->tw.relist), txn->tw.loose_count); -#if MDBX_ENABLE_GC_EXPERIMENTAL if (ctx->loop < 5 || (ctx->loop > 10 && (ctx->loop & 1))) goto retry_clean_adj; -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ goto retry; } @@ -1061,23 +1032,16 @@ retry: goto bailout; } -#if MDBX_ENABLE_GC_EXPERIMENTAL next: -#else - if (left == 0) - break; -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ if (txn->tw.gc.reclaimed == nullptr) { tASSERT(txn, is_lifo(txn) == 0); rc = outer_next(&ctx->cursor, &key, &data, MDBX_NEXT); if (unlikely(rc != MDBX_SUCCESS)) { -#if MDBX_ENABLE_GC_EXPERIMENTAL if (rc == MDBX_NOTFOUND && !left) { rc = MDBX_SUCCESS; break; } -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ goto bailout; } } else { @@ -1086,14 +1050,12 @@ retry: } if (excess) { -#if MDBX_ENABLE_GC_EXPERIMENTAL size_t n = excess, adj = excess; while (n >= env->maxgc_large1page) adj -= n /= env->maxgc_large1page; ctx->reserve_adj += adj; TRACE("%s: extra %zu reserved space, adj +%zu (%zu)", dbg_prefix(ctx), excess, adj, ctx->reserve_adj); -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ } } @@ -1105,27 +1067,15 @@ retry: goto retry; } -#if MDBX_ENABLE_GC_EXPERIMENTAL if (unlikely(excess_slots)) { const bool will_retry = ctx->loop < 5 || excess_slots > 1; NOTICE("** %s: reserve excess (excess-slots %zu, filled-slot %zu, adj %zu, " - "loop %zu)", + "loop %u)", will_retry ? "restart" : "ignore", excess_slots, ctx->fill_idx, ctx->reserve_adj, ctx->loop); if (will_retry) goto retry; } -#else - if (unlikely(ctx->fill_idx != (txn->tw.gc.reclaimed - ? MDBX_PNL_GETSIZE(txn->tw.gc.reclaimed) - : 0))) { - const bool will_retry = ctx->loop < 9; - NOTICE("** %s: reserve excess (filled-idx %zu, loop %u)", - will_retry ? "restart" : "ignore", ctx->fill_idx, ctx->loop); - if (will_retry) - goto retry; - } -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ tASSERT(txn, txn->tw.gc.reclaimed == nullptr || ctx->cleaned_slot == MDBX_PNL_GETSIZE(txn->tw.gc.reclaimed)); diff --git a/src/gc.h b/src/gc.h index 41e787ba..a8a68a24 100644 --- a/src/gc.h +++ b/src/gc.h @@ -5,19 +5,11 @@ #include "essentials.h" -#ifndef MDBX_ENABLE_GC_EXPERIMENTAL -#define MDBX_ENABLE_GC_EXPERIMENTAL 0 -#elif !(MDBX_ENABLE_GC_EXPERIMENTAL == 0 || MDBX_ENABLE_GC_EXPERIMENTAL == 1) -#error MDBX_ENABLE_GC_EXPERIMENTAL must be defined as 0 or 1 -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ - typedef struct gc_update_context { unsigned loop; pgno_t prev_first_unallocated; bool dense; -#if MDBX_ENABLE_GC_EXPERIMENTAL - intptr_t reserve_adj; -#endif /* MDBX_ENABLE_GC_EXPERIMENTAL */ + size_t reserve_adj; size_t retired_stored; size_t amount, reserved, cleaned_slot, reused_slot, fill_idx; txnid_t cleaned_id, rid; @@ -32,7 +24,7 @@ typedef struct gc_update_context { static inline int gc_update_init(MDBX_txn *txn, gcu_t *ctx) { memset(ctx, 0, offsetof(gcu_t, cursor)); - ctx->dense = txn->txnid < MIN_TXNID; + ctx->dense = txn->txnid <= MIN_TXNID; #if MDBX_ENABLE_BIGFOOT ctx->bigfoot = txn->txnid; #endif /* MDBX_ENABLE_BIGFOOT */