Skip to content

Commit d1023dc

Browse files
committed
mdbx: merge branch devel.
2 parents 0a96b2a + 859c350 commit d1023dc

38 files changed

+2878
-916
lines changed

CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git"
132132
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/preface.h"
133133
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/proto.h"
134134
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/refund.c"
135+
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/rkl.c"
136+
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/rkl.h"
135137
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/sort.h"
136138
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.c"
137139
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/spill.h"
@@ -832,6 +834,8 @@ else()
832834
"${MDBX_SOURCE_DIR}/preface.h"
833835
"${MDBX_SOURCE_DIR}/proto.h"
834836
"${MDBX_SOURCE_DIR}/refund.c"
837+
"${MDBX_SOURCE_DIR}/rkl.c"
838+
"${MDBX_SOURCE_DIR}/rkl.h"
835839
"${MDBX_SOURCE_DIR}/sort.h"
836840
"${MDBX_SOURCE_DIR}/spill.c"
837841
"${MDBX_SOURCE_DIR}/spill.h"

ChangeLog.md

+55-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ ChangeLog
44
English version [by liar Google](https://libmdbx-dqdkfa-ru.translate.goog/md__change_log.html?_x_tr_sl=ru&_x_tr_tl=en)
55
and [by Yandex](https://translated.turbopages.org/proxy_u/ru-en.en/https/libmdbx.dqdkfa.ru/md__change_log.html).
66

7-
## v0.14.1 в активной разработке без конкретизации даты выпуска
7+
## v0.14.1 выпуск запланирован в начале мая
88

99
Первый выпуск в новом кусте/линейке версий с добавлением функционала, расширением API и внутренними переработками.
1010

@@ -19,9 +19,62 @@ and [by Yandex](https://translated.turbopages.org/proxy_u/ru-en.en/https/libmdbx
1919
- [maxc0d3r](https://gitflic.ru/user/maxc0d3r) for bug reporting and testing.
2020
- [Алексею Костюку (aka Keller)](https://t.me/keller18306) за сообщения о проблеме копирования на NFS.
2121

22-
2322
Новое:
2423

24+
- Переработан код обновления GC и возврата страниц при фиксации транзакций.
25+
26+
Возникающая при этом задача алгоритмически сложна, так как список
27+
возвращаемых страниц находится в рекурсивной зависимости от самой
28+
процедуры возврата и связанных с этим операций, а прямые решения во
29+
многих случаях приводят к многократному росту накладных расходов.
30+
Поэтому исторически эта часть кода была запутанным наслоением «сдержек и
31+
противовесов», что создавало препятствие для развития. В ходе этой
32+
доработки, унаследованный из LMDB код связанный с обновлением GC, был
33+
полностью заменен вместе со всеми базирующимися на нём заплатками.
34+
35+
Новая реализация использует контейнеры идентификаторы (aka RKL),
36+
комбинирующие внутри списки элементов и непрерывные интервалы, что
37+
позволяет предельно сократить накладные расходы и упросить реализацию
38+
остальных алгоритмов. Основывается новая реализация на простом
39+
прагматичном подходе «резервирования со взвешенным запасом». Для
40+
подавляющего подмножества сценариев этого достаточно для однопроходного
41+
обновления GC, с общей сложностью от `O(1)` для мелких транзакций, до
42+
`O(log(N))` для огромных. При этом реализованный еще в 0.12.1 подход «Big
43+
Foot» (дробление больших списков retired-страниц) полностью избавляет GC
44+
от потребности в последовательностях смежных/соседствующих страниц и
45+
одновременно позволяет работать новому коду обновления GC только по
46+
самому простому и быстрому пути.
47+
48+
Тем не менее, при намеренном отключении «Big Foot», либо при работы с БД
49+
от старых версий движка без «Big Foot», возможны сложные ситуации, когда
50+
в GC могут огромные списки страниц, которые желательно дробить при
51+
возвращении неиспользованных переработанных остатков. В таких сценариях
52+
для возврата в GC требуется создавать больше записей чем было исходно
53+
переработано, что может приводить к нехватке имеющихся/переработанных
54+
идентификаторов. Тогда в игру вступает следующая часть нового кода,
55+
поиск в GC «дыр» (неиспользуемых промежутков/интервалов в пространстве
56+
ключей GC). Далее, если свободных идентификаторов (неиспользуемого
57+
пространства ключей GC) будет недостаточно, что весьма вероятно в
58+
некоторых сценариях, будет решаться задача родственная «укладке
59+
рюкзака». В конечном итоге, неиспользованные переработанные страницы
60+
будут возвращены в GC, с максимально равномерным
61+
распределением/дроблением и использованием имеющихся последовательностей
62+
смежных/соседствующих страниц, что гарантирует близость к теоретическому
63+
минимуму суммарной стоимости текущих действий и последующих операций.
64+
65+
На данный момент нет известных практических сценариев ведущих к
66+
отказу/неуспеху новой реализации обновления GC. Но гипотетически такие
67+
случаи возможны, как из-за ошибок/недочетов в реализации, так и из-за
68+
использования катастрофически неудачных режимов работы и значений опций
69+
(например `MDBX_opt_rp_augment_limit`). В текущем понимании, в том числе
70+
основываясь на объем тестирования, вероятность проявления
71+
ошибок/недочетов оценивается как крайне низкая, а устраняться замеченные
72+
проблемы будут по мере обнаружения. Однако, полностью автоматическое
73+
решение самых кошмарных и запутанных ситуаций с GC следует ожидать
74+
только при реализации дефрагментации — просто потому что нет иного
75+
рационального способа решения, за вычетом копирования БД с
76+
дефрагментацией.
77+
2578
- Добавлена опция сборки `MDBX_NOSUCCESS_PURE_COMMIT` предназначенная для отладки кода пользователя.
2679
По-умолчанию опция выключена и при фиксации пустых транзакции возвращается `MDBX_SUCCESS`.
2780
При включении опции, фиксация пишущих транзакций без каких-либо изменений считается нештатным поведением, с возвратом из `mdbx_txn_commit()` кода `MDBX_RESULT_TRUE` вместо `MDBX_SUCCESS`.

GNUmakefile

+1
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,7 @@ $(DIST_DIR)/@tmp-internals.inc: $(DIST_DIR)/@tmp-essentials.inc src/version.c $(
722722
-e '/#include "essentials.h"/d' \
723723
-e '/#include "atomics-ops.h"/r src/atomics-ops.h' \
724724
-e '/#include "proto.h"/r src/proto.h' \
725+
-e '/#include "rkl.h"/r src/rkl.h' \
725726
-e '/#include "txl.h"/r src/txl.h' \
726727
-e '/#include "unaligned.h"/r src/unaligned.h' \
727728
-e '/#include "cogs.h"/r src/cogs.h' \

mdbx.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -2775,10 +2775,10 @@ typedef struct MDBX_stat MDBX_stat;
27752775
* Legacy mdbx_env_stat() correspond to calling \ref mdbx_env_stat_ex() with the
27762776
* null `txn` argument.
27772777
*
2778-
* \param [in] env An environment handle returned by \ref mdbx_env_create()
2779-
* \param [in] txn A transaction handle returned by \ref mdbx_txn_begin()
2778+
* \param [in] env An environment handle returned by \ref mdbx_env_create().
2779+
* \param [in] txn A transaction handle returned by \ref mdbx_txn_begin().
27802780
* \param [out] stat The address of an \ref MDBX_stat structure where
2781-
* the statistics will be copied
2781+
* the statistics will be copied.
27822782
* \param [in] bytes The size of \ref MDBX_stat.
27832783
*
27842784
* \returns A non-zero error value on failure and 0 on success. */

src/alloy.c

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "page-ops.c"
4242
#include "pnl.c"
4343
#include "refund.c"
44+
#include "rkl.c"
4445
#include "spill.c"
4546
#include "table.c"
4647
#include "tls.c"

src/api-env.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ __cold int mdbx_env_set_geometry(MDBX_env *env, intptr_t size_lower, intptr_t si
955955
env->basal_txn->wr.troika = meta_tap(env);
956956
eASSERT(env, !env->txn && !env->basal_txn->nested);
957957
env->basal_txn->txnid = env->basal_txn->wr.troika.txnid[env->basal_txn->wr.troika.recent];
958-
txn_snapshot_oldest(env->basal_txn);
958+
txn_gc_detent(env->basal_txn);
959959
}
960960

961961
/* get untouched params from current TXN or DB */

src/api-opts.c

+3
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ void env_options_adjust_dp_limit(MDBX_env *env) {
147147
if (env->options.dp_limit < CURSOR_STACK_SIZE * 4)
148148
env->options.dp_limit = CURSOR_STACK_SIZE * 4;
149149
}
150+
#ifdef MDBX_DEBUG_DPL_LIMIT
151+
env->options.dp_limit = MDBX_DEBUG_DPL_LIMIT;
152+
#endif /* MDBX_DEBUG_DPL_LIMIT */
150153
if (env->options.dp_initial > env->options.dp_limit && env->options.dp_initial > default_dp_initial(env))
151154
env->options.dp_initial = env->options.dp_limit;
152155
env->options.need_dp_limit_adjust = false;

src/api-txn.c

+11-9
Original file line numberDiff line numberDiff line change
@@ -514,23 +514,25 @@ int mdbx_txn_info(const MDBX_txn *txn, MDBX_txn_info *info, bool scan_rlt) {
514514
info->txn_reader_lag = INT64_MAX;
515515
lck_t *const lck = env->lck_mmap.lck;
516516
if (scan_rlt && lck) {
517-
txnid_t oldest_snapshot = txn->txnid;
517+
txnid_t oldest_reading = txn->txnid;
518518
const size_t snap_nreaders = atomic_load32(&lck->rdt_length, mo_AcquireRelease);
519519
if (snap_nreaders) {
520-
oldest_snapshot = txn_snapshot_oldest(txn);
521-
if (oldest_snapshot == txn->txnid - 1) {
522-
/* check if there is at least one reader */
523-
bool exists = false;
520+
txn_gc_detent(txn);
521+
oldest_reading = txn->env->gc.detent;
522+
if (oldest_reading == txn->wr.troika.txnid[txn->wr.troika.recent]) {
523+
/* Если самый старый используемый снимок является предыдущим, т. е. непосредственно предшествующим текущей
524+
* транзакции, то просматриваем таблицу читателей чтобы выяснить действительно ли снимок используется
525+
* читателями. */
526+
oldest_reading = txn->txnid;
524527
for (size_t i = 0; i < snap_nreaders; ++i) {
525-
if (atomic_load32(&lck->rdt[i].pid, mo_Relaxed) && txn->txnid > safe64_read(&lck->rdt[i].txnid)) {
526-
exists = true;
528+
if (atomic_load32(&lck->rdt[i].pid, mo_Relaxed) && txn->env->gc.detent == safe64_read(&lck->rdt[i].txnid)) {
529+
oldest_reading = txn->env->gc.detent;
527530
break;
528531
}
529532
}
530-
oldest_snapshot += !exists;
531533
}
532534
}
533-
info->txn_reader_lag = txn->txnid - oldest_snapshot;
535+
info->txn_reader_lag = txn->txnid - oldest_reading;
534536
}
535537
}
536538

src/audit.c

+13-15
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@ static size_t audit_db_used(const tree_t *db) {
2424
return db ? (size_t)db->branch_pages + (size_t)db->leaf_pages + (size_t)db->large_pages : 0;
2525
}
2626

27-
__cold static int audit_ex_locked(MDBX_txn *txn, size_t retired_stored, bool dont_filter_gc) {
27+
__cold static int audit_ex_locked(MDBX_txn *txn, const size_t retired_stored, const bool dont_filter_gc) {
2828
const MDBX_env *const env = txn->env;
29-
size_t pending = 0;
30-
if ((txn->flags & MDBX_TXN_RDONLY) == 0)
31-
pending = txn->wr.loose_count + MDBX_PNL_GETSIZE(txn->wr.repnl) +
32-
(MDBX_PNL_GETSIZE(txn->wr.retired_pages) - retired_stored);
29+
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
30+
const size_t pending = txn->wr.loose_count + MDBX_PNL_GETSIZE(txn->wr.repnl) +
31+
(MDBX_PNL_GETSIZE(txn->wr.retired_pages) - retired_stored);
3332

3433
cursor_couple_t cx;
3534
int rc = cursor_init(&cx.outer, txn, FREE_DBI);
@@ -40,17 +39,16 @@ __cold static int audit_ex_locked(MDBX_txn *txn, size_t retired_stored, bool don
4039
MDBX_val key, data;
4140
rc = outer_first(&cx.outer, &key, &data);
4241
while (rc == MDBX_SUCCESS) {
43-
if (!dont_filter_gc) {
44-
if (unlikely(key.iov_len != sizeof(txnid_t))) {
45-
ERROR("%s/%d: %s %u", "MDBX_CORRUPTED", MDBX_CORRUPTED, "invalid GC-key size", (unsigned)key.iov_len);
46-
return MDBX_CORRUPTED;
47-
}
48-
txnid_t id = unaligned_peek_u64(4, key.iov_base);
49-
if (txn->wr.gc.retxl ? txl_contain(txn->wr.gc.retxl, id) : (id <= txn->wr.gc.last_reclaimed))
50-
goto skip;
42+
if (unlikely(key.iov_len != sizeof(txnid_t))) {
43+
ERROR("%s/%d: %s %u", "MDBX_CORRUPTED", MDBX_CORRUPTED, "invalid GC-key size", (unsigned)key.iov_len);
44+
return MDBX_CORRUPTED;
5145
}
52-
gc += *(pgno_t *)data.iov_base;
53-
skip:
46+
const txnid_t id = unaligned_peek_u64(4, key.iov_base);
47+
const size_t len = *(pgno_t *)data.iov_base;
48+
const bool acc = dont_filter_gc || !gc_is_reclaimed(txn, id);
49+
TRACE("%s id %" PRIaTXN " len %zu", acc ? "acc" : "skip", id, len);
50+
if (acc)
51+
gc += len;
5452
rc = outer_next(&cx.outer, &key, &data, MDBX_NEXT);
5553
}
5654
tASSERT(txn, rc == MDBX_NOTFOUND);

src/chk.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -1330,9 +1330,9 @@ __cold static int chk_handle_gc(MDBX_chk_scope_t *const scope, MDBX_chk_table_t
13301330
(number + 1) * sizeof(pgno_t), data->iov_len);
13311331
number = data->iov_len / sizeof(pgno_t) - 1;
13321332
} else if (data->iov_len - (number + 1) * sizeof(pgno_t) >=
1333-
/* LY: allow gap up to one page. it is ok
1333+
/* LY: allow gap up to two page. it is ok
13341334
* and better than shink-and-retry inside gc_update() */
1335-
usr->env->ps)
1335+
usr->env->ps * 2)
13361336
chk_object_issue(scope, "entry", txnid, "extra idl space",
13371337
"%" PRIuSIZE " < %" PRIuSIZE " (minor, not a trouble)", (number + 1) * sizeof(pgno_t),
13381338
data->iov_len);

src/cogs.h

+8-2
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,15 @@ MDBX_NOTHROW_PURE_FUNCTION static inline const page_t *data_page(const void *dat
250250

251251
MDBX_NOTHROW_PURE_FUNCTION static inline meta_t *page_meta(page_t *mp) { return (meta_t *)page_data(mp); }
252252

253-
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_numkeys(const page_t *mp) { return mp->lower >> 1; }
253+
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_numkeys(const page_t *mp) {
254+
assert(mp->lower <= mp->upper);
255+
return mp->lower >> 1;
256+
}
254257

255-
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_room(const page_t *mp) { return mp->upper - mp->lower; }
258+
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_room(const page_t *mp) {
259+
assert(mp->lower <= mp->upper);
260+
return mp->upper - mp->lower;
261+
}
256262

257263
MDBX_NOTHROW_PURE_FUNCTION static inline size_t page_space(const MDBX_env *env) {
258264
STATIC_ASSERT(PAGEHDRSZ % 2 == 0);

src/cursor.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -1781,8 +1781,7 @@ __hot csr_t cursor_seek(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, MDBX_cur
17811781
}
17821782
int cmp = mc->clc->k.cmp(&aligned_key, &nodekey);
17831783
if (unlikely(cmp == 0)) {
1784-
/* Probably happens rarely, but first node on the page
1785-
* was the one we wanted. */
1784+
/* Probably happens rarely, but first node on the page was the one we wanted. */
17861785
mc->ki[mc->top] = 0;
17871786
ret.exact = true;
17881787
goto got_node;

src/dpl.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ static inline dpl_t *dpl_sort(const MDBX_txn *txn) {
5353
return likely(dl->sorted == dl->length) ? dl : dpl_sort_slowpath(txn);
5454
}
5555

56-
MDBX_INTERNAL __noinline size_t dpl_search(const MDBX_txn *txn, pgno_t pgno);
56+
MDBX_NOTHROW_PURE_FUNCTION MDBX_INTERNAL __noinline size_t dpl_search(const MDBX_txn *txn, pgno_t pgno);
5757

5858
MDBX_MAYBE_UNUSED MDBX_INTERNAL const page_t *debug_dpl_find(const MDBX_txn *txn, const pgno_t pgno);
5959

@@ -68,7 +68,7 @@ MDBX_NOTHROW_PURE_FUNCTION static inline pgno_t dpl_endpgno(const dpl_t *dl, siz
6868
return dpl_npages(dl, i) + dl->items[i].pgno;
6969
}
7070

71-
static inline bool dpl_intersect(const MDBX_txn *txn, pgno_t pgno, size_t npages) {
71+
MDBX_NOTHROW_PURE_FUNCTION static inline bool dpl_intersect(const MDBX_txn *txn, pgno_t pgno, size_t npages) {
7272
tASSERT(txn, (txn->flags & MDBX_TXN_RDONLY) == 0);
7373
tASSERT(txn, (txn->flags & MDBX_WRITEMAP) == 0 || MDBX_AVOID_MSYNC);
7474

src/dxb.c

+6-5
Original file line numberDiff line numberDiff line change
@@ -1061,16 +1061,17 @@ int dxb_sync_locked(MDBX_env *env, unsigned flags, meta_t *const pending, troika
10611061
#endif /* MADV_DONTNEED || POSIX_MADV_DONTNEED */
10621062

10631063
/* LY: check conditions to shrink datafile */
1064-
const pgno_t backlog_gap = 3 + pending->trees.gc.height * 3;
1064+
const pgno_t stockpile_gap = 3 + pending->trees.gc.height * 3;
10651065
pgno_t shrink_step = 0;
10661066
if (pending->geometry.shrink_pv && pending->geometry.now - pending->geometry.first_unallocated >
1067-
(shrink_step = pv2pages(pending->geometry.shrink_pv)) + backlog_gap) {
1068-
if (pending->geometry.now > largest_pgno && pending->geometry.now - largest_pgno > shrink_step + backlog_gap) {
1067+
(shrink_step = pv2pages(pending->geometry.shrink_pv)) + stockpile_gap) {
1068+
if (pending->geometry.now > largest_pgno &&
1069+
pending->geometry.now - largest_pgno > shrink_step + stockpile_gap) {
10691070
const pgno_t aligner =
10701071
pending->geometry.grow_pv ? /* grow_step */ pv2pages(pending->geometry.grow_pv) : shrink_step;
1071-
const pgno_t with_backlog_gap = largest_pgno + backlog_gap;
1072+
const pgno_t with_stockpile_gap = largest_pgno + stockpile_gap;
10721073
const pgno_t aligned =
1073-
pgno_align2os_pgno(env, (size_t)with_backlog_gap + aligner - with_backlog_gap % aligner);
1074+
pgno_align2os_pgno(env, (size_t)with_stockpile_gap + aligner - with_stockpile_gap % aligner);
10741075
const pgno_t bottom = (aligned > pending->geometry.lower) ? aligned : pending->geometry.lower;
10751076
if (pending->geometry.now > bottom) {
10761077
if (TROIKA_HAVE_STEADY(troika))

src/env.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ retry:;
164164
}
165165
eASSERT(env, head.txnid == recent_committed_txnid(env));
166166
env->basal_txn->txnid = head.txnid;
167-
txn_snapshot_oldest(env->basal_txn);
167+
txn_gc_detent(env->basal_txn);
168168
flags |= txn_shrink_allowed;
169169
}
170170

@@ -524,7 +524,7 @@ __cold int env_close(MDBX_env *env, bool resurrect_after_fork) {
524524
env->defer_free = nullptr;
525525
#endif /* MDBX_ENABLE_DBI_LOCKFREE */
526526

527-
if (!(env->flags & MDBX_RDONLY))
527+
if ((env->flags & MDBX_RDONLY) == 0)
528528
osal_ioring_destroy(&env->ioring);
529529

530530
env->lck = nullptr;

0 commit comments

Comments
 (0)