RKL — сортированный набор txnid, использующий внутри комбинацию
непрерывного интервала и списка. Обеспечивает хранение id записей при
переработке, очистку и обновлении GC, включая возврат остатков
переработанных страниц.
Итератор для RKL — обеспечивает изоляцию внутреннего устройства rkl от
остального кода, чем существенно его упрощает. Фактически именно
использованием rkl с итераторами ликвидируется "ребус" исторически
образовавшийся в gc-update.
--
При переработке GC записи преимущественно выбираются последовательно, но
это не гарантируется. В LIFO-режиме переработка и добавление записей в
rkl происходит преимущественно в обратном порядке, но из-за завершения
читающих транзакций могут быть «скачки» в прямом направлении. В
FIFO-режиме записи GC перерабатываются в прямом порядке и при этом
линейно, но не обязательно строго последовательно, при этом
гарантируется что между добавляемыми в rkl идентификаторами в GC нет
записей, т.е. между первой (минимальный id) и последней (максимальный
id) в GC нет записей и весь интервал может быть использован для возврата
остатков страниц в GC.
Таким образом, комбинация линейного интервала и списка (отсортированного
в порядке возрастания элементов) является рациональным решением, близким
к теоретически оптимальному пределу.
Реализация rkl достаточно проста/прозрачная, если не считать неочевидную
«магию» обмена непрерывного интервала и образующихся в списке
последовательностей. Однако, именно этот автоматически выполняемый без
лишних операций обмен оправдывает все накладные расходы.
В результате рефакторинга и ряда оптимизаций для завершения/гашения
курсоров в читающих и пишущих транзакций стал использоваться общий код.
Причем за основу, был взят соответствующий фрагмент относящийся к
пишущим транзакциям, в которых пользователю не позволяется
использоваться курсоры для DBI=0 и поэтому эта итераций пропускалась.
В результате, при завершении читающих транзакциях, курсоры связанные с
DBI=0 не завершались должным образом, а при их повторном использовании
или явном закрытии после завершения читающей транзакции происходило
обращение к уже освобожденной памяти. Если же такие курсоры
отсоединялись или закрывались до завершения читающей транзакции, то
ошибка не имела шансов на проявление.
Спасибо Илье Михееву (https://github.com/JkLondon) и команде Erigon (https://erigon.tech) за сообщения о проблеме.
Пакетная вставка значений посредством операции `MDBX_MULTIPLE` могла
приводить к падениям и повреждению структуры БД. Ошибка оставалось не
замеченной из-за специфических условий проявления, которые не
реализовались в тестах.
Проблема присутствовала во всех выпусках начиная с v0.13.1, но
соответствующая ошибка не связана с конкретным коммита в истории, а
является следствием нескольких доработок (шагов рефакторинга), которые
суммарно привели к регрессу.
Технически ошибка обусловлена не-обнулением переменной, которая не
обнулялась в некотором пути выполнения и исходно не требовала обнуления,
но такое обнуление потребовалось после ряда этапов оптимизации кода и
рефакторинга.
Основным условием проявления является пакетная вставка multi-значений в
dupsort-таблицу с фиксированным размером значений, при котором набор
значений соответствующий обновляемом ключу, перестаёт помещаться на
вложенной странице и преобразуется/выносится во вложенное дерево
страниц. Если такой вынос/преобразование происходило до исчерпания
переданного набора значений, то при следующей итерации повторно
производились действия соответствующие выносу данных в отдельное дерево
страниц. Что могла приводить как к разыменованию неверных указателей
(повреждению содержимого памяти) и/или к повреждению содержимого страниц
образующих структуру БД.
Исправление свелось к добавлению одной строчки кода, но также были
расширены тесты для покрытия соответствующих сценариев.