Предлагается к рассмотрению метод динамического определения форматов хранения и типов индексации в таблицах аналитической базы данных Dimension DB. На основе метаданных из источников данных (SQL-запросов, таблиц или представлений) определяется формат хранения или представления данных, а по текущему распределению данных, автоматически определяется тип индексирования.

Формат хранения данных Dimension DB
Формат хранения данных Dimension DB

Как известно, в нагруженных системах стараются разделять системы по профилю нагрузки. Для оперативных систем OLTP контур, для отчетной нагрузки DWH. Реализовано это бывает по разному, все зависит от решаемых задач, текущих ограничений и наличия доступных ресурсов. Обычно нормализованные схемы данных из OLTP систем переносят в хранилища данных в "плоские" таблицы, для ускорения выполнения запросов.

Данная тема достаточно хорошо изучена, в основном на больших объемах стараются разделять транзакционную и отчетную нагрузку. Есть специализированные аналитические базы данных, например ClickHouse. Нередка гибридизация, 2 в 1 – когда классические системы дорабатываются для решения задач ускорения обработки данных: движки для колоночного хранения данных на диске, преобразование табличного формата в колоночный в оперативной памяти, различные виды горизонтального и вертикального масштабирования.

И в классическом подходе и в гибридном варианте обычно проектируют схему данных, наиболее подходящую для быстрой работы запросов, определяют форматы хранения данных, типы индексов. В нашем случае, для локального хранения данных временных рядов разработана Dimension DB, в которой реализован полностью автоматический подход по определению форматов хранения и типов индексации.

Описание системы

Dimension DB — аналитическая база данных блочно-колоночного типа для хранения данных временных рядов. Написана на языке программирования Java. В качестве backend для локального хранения данных используется Berkley DB Java Edition.

Для доступа к данным временных рядов локально, разработано API для записи и чтения данных, которое обращается к локальному хранилищу данных типа «ключ‑значение» Berkley DB. Для внешних систем хранения данных с доступом через SQL интерфейс, доступно только чтение данных. При работе с внешними источниками данных по JDBC используется автоматическая генерация SQL запросов.

Архитектура Dimension DB
Архитектура Dimension DB

Для записи в базу данных Dimension DB поддерживается несколько вариантов API:

  • Direct — вставка данных в локальную БД с использованием промежуточной Java структуры данных табличного вида;

  • JDBC — вставка данных в локальную БД с использованием данных, получаемых из внешнего источника данных по JDBC;

  • JDBC Batch — пакетная загрузка в локальную БД с использованием данных, получаемых из внешнего источника данных по JDBC, подходит для больших объемов данных;

  • CSV Batch — пакетная загрузка из CSV-файлов, для переноса больших объемов данных из файлов в локальную БД (пока в статусе экспериментальном).

Для чтения данных из Dimension DB используется API, который оптимизирован для соответствующих типов запросов:

  • Stacked — получение данных на основе функций агрегации, таких как COUNT SUM и AVG, по сути расчет моды по измерению за определенный период времени;

  • Gantt — сложный расчёт распределения с использованием агрегации COUNT по двум уровням. В этом случае расчитывается мода для анализа зависимостей и связей между двумя выбранными измерениями;

  • Raw — извлечение исходных данных в табличном формате, для просмотра детальной информации по каждому показателю;

  • BatchResultSet — извлечение исходных данных в табличном формате с построчным доступом, для получения только части строк из результирующего набора в БД.

Метаданные по наборам данных хранятся локально, используются для запросов к источникам данных по JDBC, CSV-файлам и табличным Java структурам backend-a Berkley DB.

В данный момент реализована поддержка следующих БД:

Таблица № 1. Поддерживаемые БД для работы с API в Dimension DB

№ п.п.

Поддержка API Dimension DB

База данных

Тип базы данных

1

Чтение/Запись

Berkley DB

ключ-значение

2

Чтение

ClickHouse

аналитическая

3

Чтение

Oracle

реляционная

4

Чтени��

PostgreSQL

реляционная

5

Чтение

Microsoft SQL Server

реляционная

6

Чтение

MySQL

реляционная

7

Чтение

CSV файлы

файловая

Хранение данных

Поддерживаются три формата хранения данных:

  • RAW — данные загружаются в Berkley DB виде ключа Java-типа int для строковых данных и подходящий Java-тип для остальных. Если тип данных размерности int или меньше — то он сохраняется как есть. Если Long, Float или Double — также как и для строковых значений, сохраняется его ключ в БД в специальной справочной структуре;

  • ENUM — хранятся в виде ключа Java-типа byte значения. Карта соответствия того или иного значения byte хранится в заголовке блоке. Значения ключа Java-типа int получается аналогично как и при сохранении данных в формате RAW;

  • HISTOGRAM — сохраняются начальный и конечный индекс повторяющихся данных из диапазона и само значение в виде ключа Java-типа int. Начальный и конечный индекс тоже типа int.

База данных колоночного типа, данные разделены по столбцам таблицы. Внутри колоночного формата, разделение на блоки. Размер блока данных определяется а��томатически по входящему набору данных или вручную, при пакетной вставке через параметр batchSize для JDBC Batch и CSV Batch или в DIRECT режиме регулируя объем входящих данных через промежуточную табличную структуру данных (грузим данные небольшими порциями). Дополнительно есть сжатие блочных структур, реализовано с использованием библиотеки snappy-java. Помогает, если где-то ошиблись с определением типа индексирования в ручном режиме или автоматика не сработала как надо.

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

Глобальное и локальное индексирование

Привычная работа администратора базы данных — определение типов данных и структур индексирования в БД. Типы данных, индексы. Для определенного профиля нагрузки нужны свои настройки. Потом смотрим нагрузку, делаем корректировки. Если корректировки невозможны, правим статистики по таблицам, при перекосах в данных собираем гистограммы.

Формат хранения данных Dimension DB
Формат хранения данных Dimension DB

Тут практически тоже самое, но в случае глобального индексирования, за типам данных не следим – вся информация подгружается из метаданных. Определяем только тип индексирования — выбираем из трех вариантов: RAW, ENUM или HISTOGRAM.

Если различающихся значений в данных немного и они распределены достаточно равномерно, то есть низкая кардинальность данных и нет значительных повторяющихся значений — выбираем ENUM.

Также, низкая кардинальность, но есть значительные повторяющиеся диапазоны данных. Для этого типа данных больше подходит HISTOGRAM, храним начало и окончание того или иного диапазона.

Для всех остальных случаев, для данных с высокой кардинальностью – выбираем RAW.

Если что-то сделали неправильно, есть опция сжатия — можно сильно не беспокоится об этом.

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

Сейчас доступны три варианта настройки определения индекса для опции локального индексирования:

  • ON_LOAD — Данные анализируются при первом получении данных, и на их основе определяется тип индексирования по каждому столбцу таблицы;

  • FULL_PASS_ONCE — Данные анализируются при первом получении данных, затем при следующих запусках последовательно анализируется каждый столбец таблицы один раз и на основе этой информации определяется тип индексирования;

  • FULL_PASS_EACH — Тоже же самое что и FULL_PASS_ONCE, только анализируется каждый столбец таблицы при каждом вызове API записи данных. Каждый вызов API - анализируется один столбец и так по кругу.

В первый запуск тип индексирования — RAW, если что‑то поменялось — перестраиваем существующие данные на новый тип индексирования и обновляем статистики, которые хранятся в памяти.

Тестирование производительности

Для тестирования производительности возьмем базу данных заказов такси Нью-Йорка в ClickHouse и загрузим в Dimension DB данные за 2016 год (~ 78 млн. записей). Сравним различные параметры профилей загрузки: время загрузки, объем данных на диске, время на выполнение тестовых запросов к Gantt API для глобального и локального индексирования. Комбинаций будет достаточно много, возьмем для сравнения четыре профиля со сжатием и посмотрим что получается на среднем ПК.

Таблица № 2. Профили загрузки

№ п.п.

TType

IType

AType

Сжатие

Load (min)

Size (GB)

1

TIME_SERIES

GLOBAL

ON_LOAD

true

99,52

9,132

2

TIME_SERIES

LOCAL

ON_LOAD

true

26,12

12,070

3

TIME_SERIES

LOCAL

FULL_PASS_ONCE

true

26,24

11,407

4

TIME_SERIES

LOCAL

FULL_PASS_EACH

true

26,20

11,881

Таблица № 3. Тесты производительности gantt API

Test name

№ 1 ON_LOAD

№ 2 ON_LOAD

№ 3 PASS_ONCE

№ 4 PASS_EACH

1

getGanttRawRaw

14,0 / 10,5

15,8 / 10,6

17,2 / 10,8

16,3 / 10,5

2

getGanttEnumEnum

4,4 / 2,6

11,2 / 8,0

11,3 / 8,2

10,9 / 7,8

3

getGanttHistHist

2,3 / 1,2

13,6 / 9,8

13,9 / 10,2

14,3 / 9,7

4

getGanttHistRaw

9,9 / 7,5

12,4 / 9,2

12,3 / 9,2

12,0 / 8,9

5

getGanttHistEnum

3,4 / 2,1

13,8 / 10,0

13,9 / 10,3

13,6 / 9,9

6

getGanttEnumRaw

9,5 / 7,0

15,5 / 11,3

16,1 / 11,7

15,6 / 12,2

7

getGanttEnumHist

4,2 / 2,7

16,5 / 12,7

16,5 / 12,6

16,5 / 12,1

8

getGanttRawHist

10,7 / 7,8

15,4 / 11,0

15,7 / 11,6

16,0 / 11,4

9

getGanttRawEnum

9,6 / 7,0

15,4 / 11,3

15,6 / 11,2

15,4 / 11,1

В таблице № 3 первое число — время время исполнения в один поток, вторая — в два потока, в секундах.

Тесты Gant API в один поток
Тесты Gant API в один поток
Тесты Gant API в два потока
Тесты Gant API в два потока

Повторить все можно на своем оборудовании, подробности в README и в тестах (см. исходные коды в пакете integration).

Выводы

В целом, подход по автоматическому определению формата хранения данных работает.

На данный момент в наличии прототип системы, который позволяет достаточно комфортно работать с локальными данными порядка десятков миллионов записей при среднем размере строки около 100 байт.

И тут формулировка «комфортно» — требует пояснения, что имеется в виду. Речь про то, чтобы максимально быстро извлечь данные (насколько это позволяет доступный hardware и текущая реализация software) и представить их в удобном для многомерного анализа данных временных рядов виде (с использованием текущей реализации user interface). Например, из последних интересных обновлений Dimension UI, который использует Dimension DB в качестве backend‑a — это реализация фильтрации.

Скринкаст фильтрации в интерфейсе Dashboard в режиме real-time
Фильтрация в интерфейсе Dashboard в режиме real-time
Фильтрация в интерфейсе Dashboard в режиме real-time
Скринкаст фильтрации в интерфейсе Dashboard в режиме просмотра истории
Фильтрация в интерфейсе Dashboard в режиме просмотра истории
Фильтрация в интерфейсе Dashboard в режиме просмотра истории
Скринкаст фильтрации на Ad-Hoc
Фильтрация в интерфейсе Ad-Hoc
Фильтрация в интерфейсе Ad-Hoc

Да, Dimension DB медленнее чем ClickHouse. Те же запросы на том же среднем ПК выполняются в пределах 500 миллисекунд. Но есть потенциал по ускорению выполнения запросов, снижению объемов обрабатываемых данных. В текущей реализации достаточно большая избыточность в хранении данных, можно использовать более сжатые структуры, где это позволяют данные. Добавить еще форматов хранения (например битовые карты), что позволит уменьшить занимаемый на диск объем данных и ускорить выполнение запросов.

Исходники проекта

Dimension DB — аналитическая база данных блочно-колоночного типа для хранения данных временных рядов.

Если есть вопросы — можно обратиться к документации по проекту. Подробная инструкция по сборке приложения в README.

Разработано при поддержке «Фонда содействия инновациям».

Вроде все, спасибо за внимание!