SQL Server 2014 Hekaton: тестируем In-Memory OLTP на синтетической нагрузке
Проверяем In-Memory OLTP (Hekaton) в SQL Server 2014 на write-intensive синтетике: прирост производительности впечатляет, но аппетит к RAM и ограничения T-SQL требуют осторожности.
SQL Server 2014 с Hekaton In-Memory OLTP - первая версия MS SQL с нативными in-memory таблицами и нативно компилируемыми хранимыми процедурами
SQL Server 2014 вышел в апреле, и главная фича, которую Microsoft продвигала на каждой конференции - это In-Memory OLTP, он же Hekaton. Идея понятная: таблицы живут в памяти, доступ к ним через lock-free структуры данных, нативно компилируемые хранимые процедуры превращаются прямо в машинный код. Маркетинговые обещания - производительность в разы, а то и на порядки выше по сравнению с обычными дисковыми таблицами.
Мы взяли тестовый стенд и проверили, насколько это соответствует реальности. Без продакшн-нагрузки, но на синтетике, максимально приближенной к тому, что встречается в DWH-проектах: много коротких вставок, конкурентные обновления, очереди событий.
Что такое Hekaton и как он устроен
In-Memory OLTP - это не просто «положить таблицу в кеш». Это отдельный движок внутри SQL Server с принципиально другой архитектурой.
Memory-optimized таблицы хранятся в специальном пуле памяти, структурированы через хеш-индексы или range-индексы без блокировок на строках. Версионность данных реализована через MVCC - каждая строка имеет временные метки начала и конца жизни, транзакции видят свой срез данных без взаимных блокировок.
Нативно компилируемые хранимые процедуры - отдельная история. T-SQL код в таких процедурах компилируется при создании в C, затем в машинный код через Visual C++ компилятор. Накладные расходы интерпретатора уходят полностью. Ограничение - не весь T-SQL поддерживается внутри нативных процедур, об этом ниже.
Durability настраивается: таблицы могут быть SCHEMA_ONLY (при рестарте сервера данные теряются, но скорость максимальная) или SCHEMA_AND_DATA (данные логируются через checkpoint-файлы и восстанавливаются после рестарта).
Стенд и синтетика
Сервер: 16 ядер, 128 GB RAM, SSD под данные. SQL Server 2014 Enterprise - без него In-Memory OLTP не работает, Standard не поддерживает.
Синтетический сценарий: очередь событий, которая активно пишется из нескольких десятков параллельных потоков. Типовая картина для ETL-staging или для систем реального времени, где приходит поток событий и его нужно быстро принять в СУБД.
Три варианта таблицы:
- обычная таблица с кластерным индексом на диске
- memory-optimized таблица с hash-индексом, durability =
SCHEMA_AND_DATA - memory-optimized таблица с durability =
SCHEMA_ONLY
В каждом случае - одинаковый INSERT через нативно компилируемую процедуру (для memory-optimized) или обычную процедуру (для дисковой).
Что показала нагрузка
Для write-intensive сценария разница ощутимая. Дисковая таблица при высокой конкурентности начинает давать заметные задержки на latch-конкуренции за страницы - особенно когда потоков много и все пишут в один конец таблицы. Memory-optimized с SCHEMA_AND_DATA работает значительно быстрее, прирост по throughput - кратный. SCHEMA_ONLY ещё быстрее, но это только для данных, которые не критичны при сбое.
Latency отдельных операций в нативных процедурах - действительно микросекунды, не миллисекунды. Для приложений, которые делают тысячи коротких транзакций в секунду, это меняет картину.
При этом read-heavy нагрузка - запросы по диапазонам, аналитика - прироста почти нет или он незначительный. Для DWH-запросов, которые гребут миллионы строк с агрегациями, In-Memory OLTP не то место, где искать выигрыш.
Где споткнулись
RAM под таблицы. Memory-optimized таблицы полностью живут в памяти - весь объём данных. Если таблица 20 GB - нужно 20 GB RAM под неё, плюс запас под версионирование (при активной нагрузке overhead на версии может быть существенным). Для нашего стенда с 128 GB это терпимо, но для сервера с 32 GB это уже ограничение, которое надо планировать заранее.
Ограничения T-SQL в нативных процедурах. Внутри нативно компилируемой процедуры нельзя использовать значительную часть привычных конструкций. Нет DISTINCT, нет OUTER JOIN, нет подзапросов в ряде позиций, нет временных таблиц с #, нет TRY/CATCH в привычном виде, нет EXEC для динамического SQL. Мы несколько раз натыкались на ошибки компиляции именно из-за этого - казалось бы простой запрос не собирается, потому что использует конструкцию из белого листа ограничений.
Смешанный доступ. Memory-optimized таблицы можно читать и писать и из обычных T-SQL-запросов (не только из нативных процедур), но при этом теряется часть преимущества по производительности. Полный эффект - только когда весь горячий путь идёт через нативные процедуры.
Мониторинг. Стандартные DMV и инструменты мониторинга работают, но часть специфической информации по In-Memory OLTP лежит в отдельных DMV: sys.dm_db_xtp_memory_consumers, sys.dm_db_xtp_gc_cycle_stats и похожих. Мы немного повозились, прежде чем получили внятную картину потребления памяти под версии строк.
Не все типы данных поддерживаются. VARCHAR(MAX), VARBINARY(MAX), XML, GEOGRAPHY - не поддерживаются в memory-optimized таблицах. Для наших staging-таблиц это не проблема, но при переносе реальной схемы придётся смотреть внимательно.
Где это практически применимо
По итогам теста у нас сложилась достаточно конкретная картина: In-Memory OLTP имеет смысл для узких, высоконагруженных write-точек, где конкуренция за блокировки реально является узким местом. Очереди событий, staging-таблицы под быстрый приём потока, счётчики и агрегаторы реального времени - вот где это работает.
Для типового DWH с ночной загрузкой по CDC, который мы описывали в посте про инкрементальные пакеты, In-Memory OLTP - избыточное решение. Там узкое место не в lock-конкуренции INSERT, а в объёме перекачиваемых данных и производительности ETL-пакетов.
Интересно было бы попробовать на реальных staging-таблицах клиента, где в пиковые моменты несколько источников одновременно льют данные. Но это уже следующий шаг - пока результаты синтетики достаточно убедительны, чтобы держать Hekaton в голове как инструмент для конкретного класса задач.
Работаем с SQL Server в рамках DWH и аналитики. Если у вас есть write-интенсивные узкие места - стоит посмотреть, что даст In-Memory OLTP именно на вашей нагрузке, синтетика синтетикой.