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` в специфических многопоточных
сценариях использования.
Проверка срабатывала только в отладочных сборках, при специфическом
наложении во времени читающей и пишущей транзакции в разных потоках,
одновременно с изменением размера БД.
Кроме срабатывание проверки, каких-либо других последствий не возникало.
Цель в том, чтобы уменьшить кол-во условных и безусловных переходов при
сравнениях равно/неравно, в том числе избегать вызовов задаваемых
кастомных компаратаров и memcmp() для коротких ключей/значений.
Существует проблема https://libmdbx.dqdkfa.ru/dead-github/issues/269,
которая проявляется только при специфической неупорядоченности внутри
ядра ОС, когда страницы, записанные в файл отображенный в память,
становятся видны в памяти посредством работы unified page cache:
- если записанная последней мета-страница "обгоняет" ранее записанные,
т.е. когда записанное в файл позже становится видимым в отображении
раньше, чем записанное ранее.
Теперь, вместо постоянной полной сверки записываемых страниц,
выполняется легковесная проверка при старте транзакций, с переключением
в режим "как раньше" при обнаружении проблемы.
В результате, в некоторых сценариях возвращается 5-10%
производительности, а в отдельных синтетических тестах до 30%.
Два существенных изменения:
1. Инкремент и обновление LRU происходит при изменении страницы,
но не при доступе к ней.
2. Устранен регресс, из-за которого страницы в стеке курсора хоть
помечались, но могли быть ошибочно пролиты на диск,
так как dpl_age() возвращал не 0.
Это вынужденный читинг для "починки" сравнительных бенчмарков при
размещении БД в /dev/shm.
Проблема в том, что актуальные ядра Linux для файлов размещенных в tmpfs
возвращают mincore=false. В результате, в простейших бенчмарках видно
двукратное снижение производительности, просто из-за вызовов write()
выполняемых для prefault.
Из-за этого, в таких синтетических тестах, новая libmdbx становится
существенно медленнее предыдущих версий, в том числе LMDB.
Ошибка не была замечена ранее из-за много-ходового сценария воспроизведения:
1. Создаём экземпляр MDBX_env посредством mdbx_env_create();
2. Пытаемся открыть БД посредством mdbx_env_open() в режиме
чтения-записи и эта попытка должны быть неудачной;
3. Не освобождая экземпляр MDBX_env повторно открываем его в режиме
только-чтение;
4. Закрываем среду посредством mdbx_env_close().
Падение происходит на пункте 4, либо на пункте 3, если попытка
повторного открытия будет не успешной.
Причина в том, что внутренний экземпляр osal_ioring_t инициализировался
только для режимов чтения-записи, а разрушался всегда. При этом после
первого разрушения намеренно оставался в состоянии вызывающем падение
при использовании без инициализации.
[Simon Leier](https://t.me/leisim) сообщал об этой проблеме (теперь
понятно что это было), но из-за сложности сценария проблему не удалось
воспроизвести и идентифицировать.
Ранее упущенный не очевидный момент: При работе БД в режимах
не-синхронной/отложенной фиксации на диске, все процессы-писатели должны
иметь одинаковый режим MDBX_WRITEMAP.
В противном случае, сброс на диск следует выполнять дважды: сначала
msync(), затем fdatasync(). При этом msync() не обязан отрабатывать в
процессах без MDBX_WRITEMAP, так как файл в память отображен только для
чтения. Поэтому, в общем случае, различия по MDBX_WRITEMAP не позволяют
выполнить фиксацию данных на диск, после их изменения в другом процессе.
В режиме MDBX_UTTERLY_NOSYNC позволять совместную работу с MDBX_WRITEMAP
также не следует, поскольку никакой процесс (в том числе последний) не
может гарантированно сбросить данные на диск, а следовательно не должен
помечать какую-либо транзакцию как steady.
В результате, требуется либо запретить совместную работу процессам с
разным MDBX_WRITEMAP в режиме отложенной записи, либо отслеживать такое
смешивание и блокировать steady-пометки - что контрпродуктивно.
Исправление регрессии после коммита db72763de049d6e4546f838277fe83b9081ad1de.
После отключения затратой поддержки списка "грязных" страниц логика
page_retire_ex() оказалась не полной и требовала доработки. Из-за этого
страницы добавленные или клонированные-и-измененные в текущей
транзакции, которые становились не нужными, не возвращались к доступным
для немедленного использования, а помещались в retired-список
становящихся доступными в последующих транзакциях.
В результате, в некоторых сценариях, особенно с интенсивным расщеплением
страниц из-за вставки ключей, происходило необоснованно сильное
потребление/выделение страниц БД. В свою очередь, это приводило к
использованию излишнего кол-ва страниц, увеличению GC, росту RSS и
размеру БД.