Два последних десятилетия развития СУБД характеризуются переходом от классических реляционных к принципиально новым специализированным системам. Это движение было столь массовым, что порой казалось — отказ от реляционного подхода дело времени. Однако использование современных подходов к разработке высокопроизводительных приложений и учет особенностей оборудования способны вывести классические реляционные системы на новый уровень — такие системы способны демонстрировать отличные характеристики и уверенно сохранить свои позиции.

Большинство современных реляционных СУБД имеют схожую хорошо изученную архитектуру, а многие вопросы их внутренней архитектуры рассматриваются в классических университетских курсах. Конечно, каждая РСУБД имеет свои особенности реализации, которые для проприетарных систем можно отследить по публикациям и официальной документации, для экспериментальных систем — по статьям в открытых источниках, а для систем с открытым исходным кодом — по их исходному коду. Используемые в конкретной СУБД технические решения часто могут быть весьма оригинальными, что не меняет ни реляционной сути системы, ни концепций ее внутренней реализации.

Многие реляционные продукты (PostgreSQL, MySQL и пр.) проектировались в технологических реалиях своего времени, однако технологии не стоят на месте, предоставляя разработчикам СУБД новые возможности [1]. Но подобные новшества, например, распараллеливание на основе сопрограмм (coroutine), синхронизация на основе LockFree, а также учет особенностей работы кэшей процессора обычно сложно применять в уже работающих системах — требуется кардинально менять существующую архитектуру и переписывать значительную часть кода. Это очень долгий и сложный процесс, а для коммерческих систем — не только дорогой, но еще и рискованный.

В области специализированных СУБД (ключ-значение, документоориентированные, графовые и т. д.) сложилась противоположная ситуация — все разработки изначально создавались с учетом доступных современных технологических возможностей. Это было неявно продиктовано тем, что специализированные СУБД появились на волне критики реляционных, которые попросту не справлялись с нагрузкой. От новых систем требовались прежде всего максимальные количественные показатели (скорость работы и объемы обрабатываемых данных), что привело к качественным изменениям в сторону от реляционных СУБД: более простые модели данных, иные способы хранения данных, обязательные возможности масштабирования и пр. Полный список требований достаточно большой, но в него непременно входит требование максимально эффективного использования возможностей современного аппаратного обеспечения.

Новые технологические возможности

С точки зрения влияния на развитие СУБД можно выделить наиболее значимые изменения в технологиях.

Многоядерные процессоры. Использование нескольких ядер процессора обеспечивается на уровне ОС, и классические СУБД, как и другие приложения, используют многопоточные возможности ОС, а потому предполагается, что автоматически задействуют возможности многоядерных процессоров. Вместе с тем стали доступны новые приемы параллельного программирования, например LockFree, которые действуют «в обход» возможностей стандартных ОС и позволяют добиться гораздо большей производительности.

Многопроцессорные решения. Вычислительные системы с несколькими процессорами существовали давно, но сегодня они стали доступнее. Учет особенностей их работы, например NUMA (Non Uniform Memory Access), стал уже не абстрактным, академическим объектом лабораторных исследований, а вполне конкретным промышленным способом повышения производительности системы. В некоторых случаях это может быть даже критически важно [2].

Объем памяти. Принципиально выросли объемы доступной оперативной памяти, и, одновременно, снизилась ее стоимость. Аналогичная ситуация произошла и с внешней памятью, причем, благодаря появлению SSD, существенно увеличилась еще и скорость доступа к ней. Все это привело к тому, что для многих приложений стало возможным уместить в память сразу всю базу данных и в разы сократить «вклад» операций ввода-вывода в деградацию производительности.

Асинхронное программирование. Накоплен большой практический опыт асинхронного программирования. Если раньше оно считалось достаточно экзотическим приемом и использовалось в основном для организации высокопроизводительного ввода-вывода, то теперь асинхронное программирование используется повсеместно. Яркий тому пример — популярный высокопроизводительный полностью асинхронный веб-сервер nginx.

Кооперативная многозадачность. Еще одним современным подходом к написанию высокопроизводительных параллельных приложений стало использование сопрограмм. Особый эффект достигается за счет их сочетания с асинхронным вводом-выводом — пока происходит обмен данными с внешним устройством, система может выполнять другие задачи, причем, во -первых, без каких-либо затрат на потребности операционной системы (переключение потоков и т. п.); во -вторых, самостоятельно планируя параллельную работу с учетом специфики решаемой задачи (в случае СУБД — с учетом активных транзакций и запросов клиентских приложений).

JIT-компиляция. Появились доступные средства (в частности, LLVM), позволяющие значительно ускорить выполнение простых операций в ходе исполнения запроса к базе данных. Во многих случаях исходный запрос может быть целиком откомпилирован в машинный код.

Скорость работы по сети. Скорость сетевого взаимодействия выросла кардинально, в результате чего происходит пересмотр сложившихся подходов и даже отказ от потокового протокола TCP [3]. В области СУБД это приводит к изменению протоколов сетевого взаимодействия клиентского приложения с сервером и, что более важно, протоколов репликации данных между серверами.

GPU и векторные вычисления. Эти инструменты крайне эффективны для шаблонной переработки большого количества числовых данных, что важно при решении аналитических задач. Однако пока нельзя утверждать, что GPU так же эффективны и в классических OLTP-задачах, где основными операциями являются не вычисления, а поиск данных и разграничение транзакций.

СУБД «ЛИНТЕР СОКОЛ»

«ЛИНТЕР СОКОЛ» — высокопроизводительная масштабируемая реляционная СУБД нового поколения, архитектура которой пришла на смену архитектуры СУБД «ЛИНТЕР БАСТИОН/СТАНДАРТ» [4], устаревшей на настоящий момент, поскольку была предложена в начале 1990-х годов и учитывала возможности аппаратного обеспечения того времени.

Новая СУБД должна была полностью удовлетворять требованиям к классической реляционной СУБД: соответствие стандарту языка SQL, поддержка ACID-транзакций и общепринятых уровней изоляции, наличие языка хранимых процедур, поддержка распространенных программных интерфейсов и т. д. Все это гарантирует как простоту использования в новых приложениях, так и возможность миграции уже существующих приложений на новую систему.

Если база данных полностью умещается в оперативной памяти, то производительность СУБД должна быть сопоставима с системами класса in-memory. А если база не умещается в памяти, то кэширование должно быть максимально эффективным (т. е. операции ввода-вывода минимальны), а по скорости работы система не должна уступать существующим классическим СУБД.

Архитектура и принципы реализации СУБД должны обеспечивать максимально эффективное использование возможностей современного аппаратного обеспечения, учитывать все новейшие технологические возможности.

В итоге возникла СУБД «ЛИНТЕР СОКОЛ», построенная на классической архитектуре, но переработанная через призму современных технологических возможностей.

Технологическая платформа

В основе СУБД лежит собственная технологическая платформа — низкоуровневая подсистема, предоставляющая базовые механизмы, позволяющие абстрагироваться от операционной системы и определяющие стиль реализации всех вышестоящих подсистем: ввод-вывод, журнал упреждающей записи, и пр., призванная максимально эффективно использовать современные многоядерные процессоры (рис. 1).

Рис. 1. Общая архитектура СУБД «ЛИНТЕР СОКОЛ»

В СУБД используется собственный механизм корутин и планировщик их исполнения — вся параллельная работа строится исключительно средствами этого механизма.

Платформа предоставляет комплекс примитивов для реализации как LockFree, так и классических блокирующих подходов. В подсистемах более высокого уровня (ввод-вывод, журнал транзакций и т. д.) все компоненты для оптимистических сценариев реализованы на основе LockFree. Блокирующие механизмы разрешается использовать только для пессимистичных сценариев и сценариев, которые объективно не могут быть реализованы как LockFree. Существуют возможности для перехода из оптимистических (LockFree-реализация) алгоритмов в пессимистические (блокирующие подходы в реализации).

Платформа предоставляет базовые примитивы ввода-вывода, взаимодействия с ОС, управления памятью, работы с сетью и т. д. Логика «срабатывания» примитивов (например, окончание операции ввода-вывода или факт выделения памяти) приводит к запуску/пробуждению соответствующей сопрограммы, а она, в свою очередь, может выполнять действия, приводящие к пробуждению других сопрограмм, и так далее. Можно сказать, что работа системы строится не сверху вниз (вышестоящие модули обращаются к нижестоящим и ждут окончания операций), а, наоборот, снизу вверх (нижестоящие модули вызывают вышестоящие по мере выполнения запрошенных операций).

К реализации сопрограмм предъявляются особые требования: запрещено переключать контекст выполнения текущей задачи, выполнять любые системные вызовы, производить управление памятью системными средствами. Количество активных потоков не превышает количества свободных ядер в ОС, отведенных для платформы. Потоки используются исключительно для исполнения сопрограмм. Допускается использование только возможностей технологической платформы, которая минимизирует затраты на системные вызовы, одновременно обеспечивая переносимость. Сейчас поддерживаются работа с ОС Windows и Linux на 64-разрядной архитектуре Intel, возможен перенос и на другие 64-разрядные платформы (например, «Эльбрус»).

Похожий подход не нов и встречается в архитектуре многих программ: выделяется прослойка, которая позволяет высокоуровневым модулям абстрагироваться от особенностей операционной системы, однако в «ЛИНТЕР СОКОЛ» этот подход выполнен более радикально, что и позволило получить ощутимый результат. Собственный планировщик оказывается эффективнее системного, поскольку, во -первых, работает с учетом специфики СУБД, во -вторых, минимизирует затраты на переключение контекста исполнения потоков ОС (а это достаточно медленная операция), в -третьих, существенно дешевле разрешает конфликты и имеет меньшие затраты на «обслуживание» многозадачности.

Ввод-вывод

Работа с внешней памятью построена по классической схеме и состоит из двух компонентов — пул страниц, отвечающий за кэширование данных, и журнал упреждающей записи WAL (Write Ahead Log), отвечающий за быстрое гарантированное сохранение изменений. Вместе с тем есть и существенные отличия.

Поскольку подсистема ввода-вывода строится поверх собственной технологической платформы, то все алгоритмы асинхронны и неблокируемы. Ожидание возможно только в том случае, если реально происходит ввод-вывод на диск, при этом во время ожидания СУБД может выполнять какие-либо другие действия.

На уровне взаимодействия с ОС используются только низкоуровневые вызовы, исключающие использование системного кэша и т. п, что минимизирует накладные расходы со стороны ОС.

В платформе имеется планировщик операций ввода-вывода, который упорядочивает все действия с диском и ограничивает параллельную нагрузку, поскольку они «мешают» друг другу. Выделено несколько приоритетов операций ввода-вывода, при этом запись в журнал WAL наиболее приоритетна, а предупреждающее чтение страниц в кэш — наименее.

Есть возможность для максимальной загрузки высокопроизводительных устройств хранения, которые требуют использования нескольких ядер процессора.

Хранилище и управление транзакциями

СУБД «ЛИНТЕР СОКОЛ» имеет открытую архитектуру, позволяющую встраивать различные хранилища (способы хранения) данных. На текущий момент реализован пока основной способ: хранилище построено на основе B+-деревьев, при этом их реализация почти полностью «не блокирующая»; один и тот же механизм B+-деревьев может использоваться как для хранения таблиц, так и для хранения вторичных индексов; реализованы известные современные способы оптимизации деревьев (префиксное сжатие ключей, оптимизация поиска с учетом работы кэша процессора, хранение дельт изменений вместо полных версий и т. д); для обеспечения отказоустойчивости хранилище интегрировано с журналом WAL — сначала все изменения записываются в журнал, а потом в основную базу.

На уровне хранения данных поддерживаются транзакции, при этом разграничение доступа к данным обеспечивается каждым хранилищем самостоятельно. В частности, существующее хранилище использует многоверсионность (multiversion concurrency control, MVCC), однако другие хранилища могут использовать собственные механизмы.

Разграничение доступа к объектам базы данных основано на блокировках и обеспечивается самой СУБД.

Все алгоритмы и вспомогательные структуры, связанные с хранением данных и разграничением транзакций, реализованы в рамках собственной технологической платформы — они являются неблокирующими, асинхронными и реализованы в виде сопрограмм. Единственное дополнение состоит в том, что блокировка доступа транзакции к данным может приводить к «засыпанию» сопрограмм. Сопрограмма блокирует выполнение других транзакций и связанных с ними сопрограмм, но не блокирует работу этого потока исполнения. При ее завершении или откате происходит освобождение ресурсов, что приводит к «пробуждению» сопрограмм и возобновлению работы других транзакций.

Исполнение запросов

В СУБД реализована полноценная схема исполнения SQL-запросов в несколько этапов: синтаксический анализ, логическая оптимизация, построение физического (со стоимостной оптимизацией) плана и исполнение. Принципиальные отличия от остальных СУБД заключаются в наличии обязательной кодогенерации (на основе физического плана строится код исполнения запроса). По умолчанию кодогенерация осуществляется в байт-код, но опционально байт-код может быть преобразован в JIT-код (компиляция байт-кода в машинный код или в другой формат непосредственно во время работы программы).

Итоговая скорость исполнения запроса зависит от множества факторов.

Выполняемая работа в принципе требует затрат процессорного времени. Для его сокращения происходит генерация исполняемого кода, осуществляющего рутинные действия (вычисление выражений, сравнение значений и т. д.).

Количество пересылок данных между этапами физического исполнения запроса исполнения запроса. В СУБД «ЛИНТЕР СОКОЛ» код строится таким образом, чтобы минимизировать количество лишних действий — происходит концентрация кода вокруг данных (data centric model), а не наоборот (operator centric model).

Может потребоваться хранение промежуточных данных (например, для сортировки), и тогда логика работы будет полностью интегрирована в общую технологическую платформу и действовать согласно ее требованиям: асинхронность, неблокирующие алгоритмы, сопрограммы и т. д.

Взаимодействие с клиентскими приложениями

Предполагается несколько возможностей взаимодействия с клиентским приложением (TCP, локальный сокет и т. д.), но здесь важен прикладной протокол, который реализован по новой схеме. Если в клиент-серверном взаимодействии был принят подход, когда клиентское приложение запрашивает данные, а сервер — отвечает, то в СУБД «ЛИНТЕР СОКОЛ» при выполнении SQL-запроса сервер самостоятельно начинает отправлять данные, не дожидаясь дополнительных «команд» от клиентского приложения. Вместе с тем сервер не перегружает канал — он периодически ждет от клиентской стороны подтверждения о получении данных и только после этого начинает отправлять новые порции. Подобная схема уже используется в общей практике сетевого взаимодействия, столкнувшейся с проблемами существующих протоколов потоковой передачи данных, например задержки по времени из-за необходимости запроса следующей порции данных.

Итого

Одно из ключевых требований к СУБД «ЛИНТЕР СОКОЛ» — соответствие стандартам классических реляционных СУБД, поэтому для замеров производительности был использован тест HammerDB, а для оценки полученных результатов в качестве «эталона» используется СУБД PostgreSQL версии 15. Тестирование осуществлялось на одинаковом оборудовании при одинаковых внешних условиях.

Первое требование к производительности СУБД «ЛИНТЕР СОКОЛ» — эффективная работа в случае, если база данных полностью умещается в оперативной памяти. В этом случае не происходит чтения страниц базы данных, но осуществляется запись модифицированных страниц и журнала WAL. На рис. 2 приведены результаты замеров.

Рис. 2. Производительность при неограниченном объеме памяти (вся база данных умещается в оперативную память)

На графике (рис. 2) по оси X указано количество виртуальных клиентов, каждый из которых эмулирует работу пользовательского приложения и осуществляет транзакции. По оси Y показано количество проведенных транзакций.

Если база данных не умещается в память, то система не должна уступать по производительности классическим СУБД (Oracle, Microsoft SQL Server, PostgreSQL, MySQL). Для оценки этого показателя был выполнен ряд тестов при различном соотношении размера памяти и размера базы данных (рис. 3).

Рис. 3. Производительность при ограниченном объеме памяти (в память умещается только часть базы данных)

На графике (рис. 3) по оси X показано соотношение объема оперативной памяти и размера базы данных, а по оси Y — количество проведенных транзакций.

В режиме «база полностью в памяти» методы параллельной работы в СУБД «ЛИНТЕР СОКОЛ» оказываются эффективнее, а в режиме «база частично в памяти» на производительность оказывают влияние два фактора. Во-первых, реально используемый кэш страниц оказывается больше из-за меньших расходов на многозадачность и исполнительную систему. Во-вторых, планировщик операций ввода-вывода работает более эффективно, чем встроенные средства операционной системы, что сокращает расходы процессорного времени на служебные операции.

Интересным опытом была оптимизация СУБД «ЛИНТЕР СОКОЛ» для платформы «Эльбрус», в ходе которой удалось добиться кратно большей производительности по сравнению с другими реляционными СУБД. Причина — собственный планировщик исполнения, который оптимизирован с учетом особенностей платформы «Эльбрус». В других СУБД такая оптимизация невозможна из-за использования стандартных механизмов распараллеливания, предоставляемых операционной системой.

***

Классические реляционные СУБД еще достаточно долго будут использоваться: простота, доступность, распространенность и, что не менее важно — наличие подготовленных специалистов. Во многих случаях реляционные СУБД удовлетворяют потребностям приложений и нет причин переходить на иные системы. Многие новые аппаратные возможности автоматически используются в реляционных СУБД, позволяя наращивать производительность системы за счет улучшения аппаратуры, однако пример СУБД «ЛИНТЕР СОКОЛ» показывают, что перепроектирование и реализация СУБД на основе новых подходов дают еще больший эффект, позволяя на том же оборудовании достигать более высокой производительности.

Сложившееся положение дел существенно изменится лишь тогда, когда появятся неопровержимые доводы против реляционного подхода: невозможность или крайняя неэффективность представления данных в виде таблиц (аудио, видео, биометрия, семантические онтологии), отказ от ACID-транзакций (невозможность распределенных транзакций с большим количеством узлов XX), принципиальная неспособность эффективно использовать новое аппаратное обеспечение (энергонезависимая память) и пр. Многие из перечисленных факторов проявляются уже сейчас, но пока не носят подавляющего характера.

Литература

1. Константин Селезнев. Особенности выбора современных СУБД // Открытые системы.СУБД. — 2017. — № 3. — С. 33–35. URL: https://www.osp.ru/os/2017/03/13052702 (дата обращения: 21.05.2023).

2. Сияньяо Ю., Джордж Безерра, Эндрю Павло, Сринивас Девадас, Майкл Стоунбрейкер. Вглядываясь в бездну: оценка схем управления параллелизмом на процессоре с тысячью ядер. Перевод: С. Д. Кузнецов. URL: http://citforum.ru/database/articles/abyss (дата обращения: 21.06.2023).

3. Александр Тоболь. TCP против UDP, или будущее сетевых протоколов. URL: https://habr.com/ru/companies/oleg-bunin/articles/461829 (дата обращения: 21.05.2023).

4. Виталий Максимов, Л. Козленко, С. Маркин, И. Бойченко. Защищенная реляционная СУБД Линтер // Открытые системы.СУБД. — 1999. — № 11–12. — С. 69–79. URL: https://www.osp.ru/os/1999/11-12/177904 (дата обращения: 21.06.2023).

Константин Селезнев (konstantin_seleznyov@relex.ru) — главный специалист, группа компаний «РЕЛЭКС» (Воронеж).