После предыдущей серии доработок весной 2021 года, функция `rebalance()`
обеспечивала слияние мало заполненной страницы с менее заполненной
соседней, одновременно пытаясь не вовлекать соседних страниц, если те
еще не были скопированы/клонированы/изменены в текущей транзакции.
В целом, реализованная тактика представляется успешной. Однако, при
обновлении GC она иногда приводила к исчерпанию подготовленного резерва
извлеченных из GC страниц. Это не является проблемой, если не считать
вероятность срабатывания `assert(txn->mt_flags & MDBX_TXN_DRAINED_GC)`
в отладочных сборках.
Тем не менее, из этой ситуации можно сделать вывод, что поведение
`rebalance()`, как минимум, может быть обогащено опцией уменьшения WAF
ценой меньшей сбалансированности дерева. Технически при этом слияние
выполняется преимущественно с грязной страницей, если на ней достаточно
места и соседняя страница с другой стороны еще чистая.
Соответствующая опция в `enum MDBX_option_t` будет добавлена чуть позже.
Тезисно:
- Использование DUPFIXED (включая INTEGERDUP) могло приводить к
повреждению БД и/или потере данных. Этот коммит устраняет эту угрозу.
- Вероятность проявления существенно увеличивается с увеличением
размера/длины мульти-значений/дубликатов (не ключей).
- В MDBX проблема унаследована от LMDB, где существует более 11 лет,
начиная с коммита ccc4d23e74
и до настоящего времени.
Для вложенных страниц типа LEAF2 (которые содержат только значения
одинаковой длины, без таблицы смещений к ним), упомянутым выше коммитом,
было добавлено резервирование места (что в целом спорно, но в некоторых
сценариях позволяет уменьшить накладные расходы). Ошибка была в том, что
в коде не исключалась возможность превышения размера страницы БД, что
далее приводило к арифметическому переполнению, повреждению БД и/или
просписи памяти.
Устранение упущения приводящего к нелогичной ситуации `me_dxb_mmap.curren > me_dxb_mmap.limit` при "дребезге" размера БД.
В текущем понимании, последствий кроме срабатывания assert-проверки нет, а вероятность проявления близка к нулю.
Повреждение БД и/или потери данных не происходило, проблема лишь в
возврате ложной ошибки.
Благодарю пользователя/разработчика @Dvirsw (https://t.me/Dvirsw) за
сообщения о проблеме и предоставление минимального/оптимального сценария
воспроизведения.
--
Проблема была из-за излишнего условия при контроле внутренего поля
mp_upper в ходе проверки структуры страниц БД.
Поле mp_upper указывает на нижнуюю границу заполнения страницы от конца
к началу. Вследствие того, что значения ключей выравниваетня на четную
границу, это поле четно во всех случаях за исключением LEAF2-страницы
(листовая страница вложенного дерева для множественных значений
финсированной/одинаковой длины одного ключа), на которой размещено
нечетное количество значений нечетной длины.
Ошибка не проявлялась в большинстве случаев (в том числе в
стохастических тестах), так как штатно лишняя проверка производилась
только при чтении страницы и перебалансировке ключей, но не при каждом
добавлении значения. Тем не менее, сценарии тестов требуют
доработки/расширения для явного добавления нечетных dupfixed-сценариев.
После f0d523c507042cc70eeeb690778c9b2be6a8b33f, при использовании
добавленного API блокировок, возможно ложно-положительное определение
состояние "внутри транзакции".
Когда rp_augment_limit не задан пользователем посредством
`MDBX_opt_rp_augment_limit`, то как и ранее он подстраивается в
зависимости от текущего размера БД (актуального кол-ва страниц).
Теперь-же авто-устанавливаемое значение rp_augment_limit вычисляется
обратно-пропорционально `MDBX_opt_gc_time_limit`:
- Если gc_time_limit == 0, то rp_augment_limit устанавливается в 1/3 от
общего кол-ва страниц БД, но не меньше рационального минимума.
Это соответствует прежнему поведению и обеспечивает достаточно глубокую
переработку GC во всех не-экстремальных сценариях.
- При gc_time_limit >= 16_секунд
rp_augment_limit устанавливается в минимальное значение.
- Когда 0 < gc_time_limit < 16_секунд
rp_augment_limit устанавливается между минимумом и 1/3 от размера БД
пропорционально остатку gc_time_limit до 16 секунд.
Соответственно, при больших значениях gc_time_limit, выбирается меньшее
значение rp_augment_limit, и контроль глубины переработки GC
ограничивается в основном по-времени.
Отложенное освобождение позволяет реализовать безопасное выполнение
fastpath/lockfree при повторном открытии из других потоков/транзакцйий
уже открытых subDB, что и происходит при активации добавленной опции
сборки `MDBX_ENABLE_DBI_LOCKFREE`.
Ранее инициализация в транзакциях структур данных, связанных с
dbi-хендлами и subDb, выполнялась непосредственно при запуске
транзакций. Что в сценариях с большим кол-вом dbi-дексприторов (например
libfpta) порождало заметные накладные расходы, которые расли линейно от
общего кол-ва открытых subDb, а не от реально используемых в транзакции.
При использовании одной-двух сотен хендлов, при старте каждой транзакции
могли копироваться и/или обнуляться десятки килобайт. Теперь этот
недостаток устранен.
Изменена схема инициализации, валидации и импорта хендлов открытых после
старта транзакции:
1) Инициализация теперь выполняется отложенна, а при старте транзации
обнуляется только массив с однобайтовыми статустами dbi-хендлов.
При этом доступнва опция сборки `MDBX_ENABLE_DBI_SPARSE`, при активации
которой используется битовая карты, что снижает объем инициализации
при старте транзакции в 8 раз (CHAR_BIT).
2) Переработана валидация dbi-хендлов на входах API, с уменьшением кол-ва
проверок и ветвлений до теоретического минимума.
3) Переработ импорт dbi-хендов открытых после старта транзакци, теперь
при этом не захватывается мьютекс.
Планируется очистка от функций и возможностей ранее объявленных
устаревшими. В частности, будет удалена поддержка пользовательских
функций сравнения, которые были обьявлены устаревшими начиная с версии
0.9, более 33 месяцев назад.
Стабилизирующий выпуск с исправлением обнаруженных ошибок и устранением недочетов,
в день 100-летия со дня рождения выдающегося советского и российского ученого и конструктора [Влади́мира Фёдоровича У́ткина](https://ru.wikipedia.org/wiki/Уткин,_Владимир_Фёдорович).
Исправления и доработки:
------------------------
- Устранение регресса/ошибки в пути обработки `put(MDBX_MULTIPLE)` при пакетном/оптовом
помещении в БД множественных значений одного ключа (aka multi-value или dupsort).
Проявление проблемы зависит от компилятора и опций оптимизации/кодогенерации, но с большой вероятностью возвращется
ошибка `MDBX_BAD_VALSIZE` (`-30781`), а в отладочных сборках срабатывает проверка `cASSERT(mc, !"Invalid key-size")`.
Сценарии приводящие к другим проявлениям на данный момент не известны.
- Реализована перезапись в `mdbx_put(MDBX_CURRENT)` всех текущих мульти-значений ключа
при отсутствии флага `MDBX_NOOVERWRITE`. Ранее в такой ситуации возвращалась ошибка `MDBX_EMULTIVAL`.
В текущем понимании новое поведение более удобно и не создаёт проблем совместимости с ранее написанным кодом.
- Добавлена возможность использовать `mdbx_cursor_get(MDBX_GET_MULTIPLE)` без предварительной установки
курсора, совмещая операцию пакетного получения данных с позиционированием курсора на передаваемый ключ.
- Микрооптимизация и рефакторинг `cursor_put_nochecklen()` в продолжение исправления
регресса/ошибки в пути обработки `put(MDBX_MULTIPLE)`.
- Уточнение формулировок в описании API, в том числе пояснений о `SIGSEGV`
и недопустимости прямого изменения данных.
Более подробная информация в [ChangeLog](https://libmdbx.dqdkfa.ru/md__change_log.html).
git diff' stat: 24 commits, 18 files changed, 624 insertions(+), 94 deletions(-)
Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
Достаточно запутано:
- Внутри `update_gc()` используется создание записей с резервированием
посредством `put(MDBX_RESERVE)` в циклах с ранним выходом и последующим
заполнением.
- При этом в случае раннего выхода (из цикла из-за изменения набора
страниц) зарезервированное место в добавленных записях остается
незаполненным/неиницилизированным (подкрашенным в Valgrind или ASAN).
- Чтение этих незаполненных/неиницилизированных данных штатно не
происходит, но в отладочных сборках при включении детального уровне
логирования выполняется отладочный вывод значений ключей и данных при
позиционировании курсоров.
- В свою очередь, `update_gc()` либо удаляет, либо заполняет
зарезервированные записи, но для этого требуется позиционирование
курсора, что в отладочных сборках приводит к чтению
незаполненных/неиницилизированных записей и печали Valgrind/ASAN.
Теперь внутри `update_gc()` в отладочных сборках с поддержкой Valgrind
или ASAN место в резервируемых записях явно инициализируется.
- Обеспечении терминирующего нуля даже при нехватке буфера и
опосредованных предупреждений Valgrind из-за чтения внутри strlen()
неинициализированных данных при последующем логировании/печати.
- Ускорение за счет отказа от использования snpruintf().
Достаточно запутанно:
- Для полноценного контроля при использовании Valgrind или ASAN
требуется закрашивать/отравлять отображение файла БД выше границы
распределенных страниц.
- Производить такое подкрашивание/отравление необходимо в синхронизации
с пишущими транзакциями и запросами на изменение геометрии, в том числе
при изменении размера БД и/или геометрии другим процессом.
- Для такой синхронизации логично и проще всего использовать основной
мьютекс/механизм блокировки пишущих транзакций, что и происходит внутри
txn_valgrind().
- Однако, в этой схеме может возникать ошибка EDEADLK, когда
txn_valgrind() вызывается при завершении читающей транзакции
выполняющейся с дополнительной блокировкой пишущих транзакций.
- Как таковая ошибка EDEADLK при этом проблем не создаёт и поэтому
просто игнорируется. Но утилита mdbx_chk при работе в кооперативном
(не эксклюзивном) режиме чтения-записи использует именно такой сценарий,
а возникающую при этом ошибку EDEADLK засчитывает как проблему при
проверке.
= В результате, при использовании Valgrind или ASAN утилита mdbx_chk
запущенная с опциями `-wc` всегда завершается неудачей из-за как минимум
одной проблемы в ходе проверки. Что внешне выглядит как
недочет/ошибка/регресс и создает проблемы при автоматизированном
тестировании.
Добавленный костыль использует atomic-счетчик, который инкремируется до
и декремируется после попытки захвата блокировки изнутри txn_valgrind().
В свою очередь, код обрабатывающий ошибку захвата блокировки, игнорирует
EDEADLK при ненулевом значении счетчика. Активируется костыль только при
сборке с поддержкой Valgrind или включенном ASAN, и не оказывает
никакого влияния в остальных случаях.
Маркер steady/weak в прототипе/заготовке мета-страницы не
инициализировался, но опосредованно читался кодом проверки
когерентности unified buffer/page cache.
Прочитанное не-инициализированное/случайное значение использовалось в
условии одного из ветвлений, но не оказывало какого-либо влияния, так
как в данном контексте все пути приводят к одному инварианту результата.
- удалены переменные-флаги dupdata_flag и do_sub;
- вместо dupdata_flag используется условие dkey.iov_base != nullptr;
- вместо do_sub используется условие flags & F_DUPDATA;
- очищено использование dkey, добавлена инициализация dkey.iov_base в ключевых точках;
- декларация части переменных перенеса ближе к месту использования.
Стабилизирующий выпуск с исправлением обнаруженных ошибок и устранением недочетов,
в день основания международного детского центра [«Арте́к»](https://ru.wikipedia.org/wiki/Артек).
Исправления и доработки:
------------------------
- Исправление опечатки в имени переменной внутри `mdbx_env_turn_for_recovery()`.
- Обходное решение проблем сборки посредством GCC с использование опций `-m32 -arch=i686 -Ofast`.
- Доработка режима "восстановления" БД и переключения на заданную мета-страницу.
Более подробная информация в [ChangeLog](https://libmdbx.dqdkfa.ru/md__change_log.html).
Мелочи:
-------
- Незначительное уточнение CMake-пробника для `std::filesystem`,
проверяющего необходимость линковки с дополнительными библиотеками C++.
- Устранение минорных предупреждений старых компиляторов в тестах.
- Устранение причины ложно-позитивного предупреждения новых версий GCC в C++ API.
- Исправление ссылки на репозиторий бенчмарка ioarena.
- Добавление перекрестных ссылок в doxygen-документацию по C++ API.
- Уточнение ограничений в разделе [Restrictions & Caveats](https://libmdbx.dqdkfa.ru/intro.html#restrictions).
- Исправление ссылок на описание `mdbx_canary_put()`.
14 files changed, 222 insertions(+), 56 deletions(-)
Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
Обходное решение проблем сборки посредством GCC с использование опций `-m32 -arch=i686 -Ofast`.
Проблема обусловлена ошибкой GCC, из-за которой конструкция `__attribute__((__target__("sse2")))`
не включает полноценное использование инструкций SSE и SSE2, если это не было сделано посредством
опций командной строки, но была использована опция `-Ofast`.
В результате сборка заканчивалась сообщением об ошибке:
gcc/i686-buildroot-linux-gnu/12.2.0/include/xmmintrin.h: In function 'diffcmp2mask_sse2':
gcc/i686-buildroot-linux-gnu/12.2.0/include/xmmintrin.h:814:1: error: inlining failed in call to 'always_inline' '_mm_movemask_ps': target specific option mismatch
814 | _mm_movemask_ps (__m128 __A)
Это позволяет обезопасить БД (снизить шанс её разрушения) если
пользователь при попытке восстановления, либо просто в качестве
эксперимента, задал утилите `mdbx_chk` неверную или опасную комбинацию
параметров.
При этом обычная проверка, как и явное переключение мета-страниц,
работают по-прежнему.
Полная сверка геометрии на совпадение (включая geo.next) не является
ошибкой, но может приводить к выводу бессмысленного предупреждения о
пропуске обновлении/перезаписи геометрии при открытии БД в режиме
восстановления (с явным указанием мета-страницы).
Исправление опечатки в имени переменной внутри `mdbx_env_turn_for_recovery()`,
что приводило к неверному поведению в некоторых ситуациях.
С точки зрения пользователя, с учетом актуальных сценариев использования
утилиты `mdbx_chk`, был только один специфический/редкий сценарий
проявления ошибки/проблемы - когда выполнялась проверка и активация
слабой/weak мета-страницы с НЕ-последней транзакцией после системной
аварии машины, где БД использовалась в хрупком/небезопасном режиме.
В сценарии, при успешной проверке целевой страницы и её последующей
активации выводилось сообщение об ошибке, связанной со срабатыванием
механизма контроля не-когерентности кэша файловой системы и отображенных
в ОЗУ данных БД. При этом БД успешно восстанавливалось и не было
каких-либо негативных последствия, кроме самого сообщения об ошибке.
Технически же ошибка проявлялась при "переключении" на мета-страницу,
когда у хотя-бы одной из двух других мета-страниц номер транзакции был
больше:
* Если содержимое других мета-страниц было корректным, а номера
связанных транзакций были больше, то результирующий номер транзакции в
целевой/активируемой мета-страницы устанавливается без учета этих
мета-страниц и мог быть меньше-или-равным.
* В результате, если такие мета-страницы были в статусе слабых/weak, то
при закрытии БД после переключения могла срабатывать защита от
не-когерентности unified buffer/page cache, а в отладочных сборках могла
срабатывать assert-проверка.
* Если же такие мета-страницы были в статусе сильных/steady, то
переключение на новую мета-страницу могло не давать эффекта либо
приводить к появлению двух мета-страниц с одинаковым номером транзакции,
что является ошибочной ситуацией.
Стабилизирующий выпуск с исправлением обнаруженных ошибок и устранением
недочетов, в день 100-летнего юбилея спортивного клуба [«ЦСКА»](https://ru.wikipedia.org/wiki/Центральный_спортивный_клуб_Армии).
Мелочи:
-------
- Обновление патча для старых версий buildroot.
- Использование clang-format-16.
- Использование `enum`-типов вместо `int` для устранения предупреждений GCC 13,
что могло ломать сборку в Fedora 38.
14 files changed, 117 insertions(+), 83 deletions(-)
Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
Стабилизирующий выпуск с исправлением обнаруженных ошибок и устранением
недочетов, в день 100-летнего юбилея спортивного общества [«Динамо»](https://ru.wikipedia.org/wiki/Динамо_(спортивное_общество)).
Благодарности:
--------------
- Max <maxc0d3r@protonmail.com> за сообщение о проблеме экспорта из DSO/DLL
устаревших функций API.
- [`@calvin3721`](https://t.me/calvin3721) за сообщение о проблеме работы
`MainDB` с флагами не по-умолчанию.
Исправления:
------------
- Поправлен экспорт из DSO/DLL устаревших функций,
которые заменены на inline в текущем API.
- Устранено использование неверного компаратора при создании или пересоздании
`MainDB` с флагами/опциями предполагающим использование специфического
компаратора (не по-умолчанию).
Мелочи:
-------
- Удалена дублирующая диагностика внутри `node_read_bigdata()`.
- Исправлены ссылки в описании `mdbx_env_set_geometry()`.
- Добавлен отдельный тест `extra/upsert_alldups` для специфического
сценария замены/перезаписи одним значением всех multi-значений
соответствующих ключу, т.е. замена всех «дубликатов» одним значением.
- В C++ API добавлены варианты `buffer::key_from()` с явным именованием по типу данных.
- Добавлен отдельный тест `extra/maindb_ordinal` для специфического
сценария создания `MainDB` с флагами требующими использования
компаратора не по-умолчанию.
- Рефакторинг проверки "когерентности" мета-страниц.
- Корректировка `osal_vasprintf()` для устранения предупреждений статических анализаторов.
16 files changed, 686 insertions(+), 247 deletions(-)
Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
Стабилизирующий выпуск с исправлением обнаруженных ошибок, устранением
недочетов и технических долгов. Ветка 0.12 считается готовой к
продуктовому использованию, получает статус стабильной и далее будет
получать только исправление ошибок. Разработка будет продолжена в ветке
0.13, а ветка 0.11 становится архивной.
Благодарности:
--------------
- Max <maxc0d3r@protonmail.com> за сообщение о проблеме ERROR_SHARING_VIOLATION
в режиме MDBX_EXCLUSIVE на Windows.
- Alisher Ashyrov <https://t.me/a1is43ras4> за сообщение о проблеме
с assert-проверкой и содействие в отладке.
- Masatoshi Fukunaga <https://gitflic.ru/user/mah0x211> за сообщение о проблеме
`put(MDBX_UPSERT+MDBX_ALLDUPS)` для случая замены всех значений в subDb.
Исправления (без корректировок новых функций):
----------------------------------------------
- Устранен регресс после коммита 474391c83c5f81def6fdf3b0b6f5716a87b78fbf,
приводящий к возврату ERROR_SHARING_VIOLATION в Windows при открытии БД
в режиме MDBX_EXCLUSIVE для чтения-записи.
- Добавлено ограничение размера отображения при коротком read-only файле, для
предотвращения ошибки ERROR_NOT_ENOUGH_MEMORY в Windows, которая возникает
в этом случае и совсем не информативна для пользователя.
- Произведен рефакторинг `dxb_resize()`, в том числе, для устранения срабатывания
assert-проверки `size_bytes == env->me_dxb_mmap.current` в специфических
многопоточных сценариях использования. Проверка срабатывала только в
отладочных сборках, при специфическом наложении во времени читающей и
пишущей транзакции в разных потоках, одновременно с изменением размера БД.
Кроме срабатывание проверки, каких-либо других последствий не возникало.
- Устранена проблема в `put(MDBX_UPSERT+MDBX_ALLDUPS)` для случая замены
всех значений единственного ключа в subDb. В ходе этой операции subDb
становится полностью пустой, без каких-либо страниц и именно эта
ситуация не была учтена в коде, что приводило к повреждению БД
при фиксации такой транзакции.
- Устранена излишняя assert-проверка внутри `override_meta()`.
Что в отладочных сборках могло приводить к ложным срабатываниям
при восстановлении БД, в том числе при автоматическом откате слабых
мета-страниц.
- Скорректированы макросы `__cold`/`__hot`, в том числе для устранения проблемы
`error: inlining failed in call to ‘always_inline FOO(...)’: target specific option mismatch`
при сборке посредством GCC >10.x для SH4.
Ликвидация технических долгов и мелочи:
---------------------------------------
- Исправлены многочисленные опечатки в документации.
- Доработан тест для полной стохастической проверки `MDBX_EKEYMISMATCH` в режиме `MDBX_APPEND`.
- Расширены сценарии запуска `mdbx_chk` из CMake-тестов для проверки как в обычном,
так и эксклюзивном режимах чтения-записи.
- Уточнены спецификаторы `const` и `noexcept` для нескольких методов в C++ API.
- Устранено использование стека под буферы для `wchar`-преобразования путей.
- Для Windows добавлена функция `mdbx_env_get_path()` для получения пути к БД
в формате многобайтных символов.
- Добавлены doxygen-описания для API с широкими символами.
- Устранены предупреждения статического анализатора MSVC,
все они были несущественные, либо ложные.
- Устранено ложное предупреждение GCC при сборке для SH4.
- Добавлена поддержка ASAN (Address Sanitizer) при сборке посредством MSVC.
- Расширен набор перебираемых режимов в скрипте `test/long_stochastic.sh`,
добавлена опция `--extra`.
- В C++ API добавлена поддержка расширенных опций времени выполнения `mdbx::extra_runtime_option`,
аналогично `enum MDBX_option_t` из C API.
- Вывод всех счетчиков page-operations в `mdbx_stat`.
63 files changed, 1161 insertions(+), 569 deletions(-)
Signed-off-by: Леонид Юрьев (Leonid Yuriev) <leo@yuriev.ru>
Никаких значимых изменений, только обход "странностей" в MSVC.
Как оказалось MSVC распространяет действие директивы
`pragma(warning(supppress:#))` строго на следующую строку, даже если эта
строка является продолжением комментария начатого в самой директиве
и/или не содержит синтаксических конструкций языка.
Поэтому большинство из добавленных ранее директив для подавления ложных
предупреждений, перестало работать после переформатирования исходного
кода.
В том числе для устранения проблемы
`error: inlining failed in call to ‘always_inline FOO(...)’: target specific option mismatch`
при сборке посредством GCC >10.x для SH4.
Fixed cursor_put_nochecklen() internals for case when dupsort'ed named subDb
contains a single key with multiple values (aka duplicates), which are replaced
with a single value by put-operation with the `MDBX_UPSERT+MDBX_ALLDUPS` flags.
In this case, the database becomes completely empty, without any pages.
However exactly this condition was not considered and
thus wasn't handled correctly.
Fixes https://gitflic.ru/project/erthink/libmdbx/issue/8
Thanks Masatoshi Fukunaga <https://gitflic.ru/user/mah0x211> for reporting.
Устранение регресса после a484a1f89bcbf38aeb7a81d6080605f86ddc7933.
Проверка `prev_limit_pgno >= used_pgno` правомочна только в части сценариев,
но не в общем случае.
В том числе, для устранения срабатывания assert-проверки
`size_bytes == env->me_dxb_mmap.current` в специфических многопоточных
сценариях использования.
Проверка срабатывала только в отладочных сборках, при специфическом
наложении во времени читающей и пишущей транзакции в разных потоках,
одновременно с изменением размера БД.
Кроме срабатывание проверки, каких-либо других последствий не возникало.
Цель в предотвращении ошибки ERROR_NOT_ENOUGH_MEMORY в Windows, которая
совсем не информативна для пользователя и возникает в этом случае (когда
файл открыт read-only и короче запрошенного размера).