Тема 1. Аналитическое хранилище и база приложения
Организация, эксплуатирующая городские инженерные сети, ежечасно получает показания сотен приборов учёта, регистрирует обращения жителей, фиксирует аварии и работы. Данные накапливаются годами, и со временем возникает вопрос: где они должны храниться, чтобы к ним можно было обращаться с содержательными вопросами — о потреблении по районам, о динамике за отопительный сезон, о связи аномальных показаний с последующими отказами оборудования.
Ответ на этот вопрос менялся по мере роста требований, и рассмотрим его в той же последовательности: от файла к базе данных, от базы данных приложения к отдельной аналитической базе, от единственной таблицы к нескольким классам хранилищ, устроенных по-разному. Каждый следующий шаг вызван ограничением предыдущего решения, поэтому порядок изложения повторяет порядок, в котором эти решения возникали на практике.
Файл как способ хранения
Начнём с простейшего. Учебный набор курса содержит файл почасовых показаний приборов учёта: 345 846 строк, около 18 МБ в формате CSV. Такой файл читается одной командой, открывается в текстовом редакторе, передаётся по электронной почте, не требует ни установки программ, ни согласования с кем-либо. Для набора подобного размера файлового подхода достаточно, и тем полезнее установить, где проходят его границы.
Содержимое такого файла — строки текста, разделённые запятыми: первая строка перечисляет имена столбцов, каждая последующая описывает одно измерение — какой прибор, в какой час, какое значение передал.
Четыре ограничения
Первое ограничение — отсутствие схемы. Формат CSV не хранит типов: столбец ts представляет собой текст, и станет ли он отметкой времени, зависит от того, укажет ли читающая программа соответствующий параметр. Столбец value может содержать пустую строку, число с запятой вместо точки или слово NULL — файл примет любое из этих значений. Правил, ограничивающих содержимое, тоже нет: ничто не препятствует записи показания несуществующего прибора или даты из будущего. Проверки приходится повторять в каждой программе, читающей файл, и достаточно одной программы, где о них забыли, чтобы в данные попали некорректные значения.
Второе ограничение — стоимость чтения. Для вычисления среднего по одному столбцу программа обязана прочитать файл целиком: разобрать все 345 846 строк, разделить каждую по запятым, отбросить ненужные поля. Восемнадцать мегабайт читаются за доли секунды, и различие в скорости незаметно. Однако зависимость линейна: на файле в двадцать гигабайт тот же запрос потребует минут и потребует их снова при следующем обращении к тем же данным.
Третье ограничение определяется устройством инструментов обработки. Библиотеки, работающие с табличными данными в памяти, размещают таблицу целиком в оперативной памяти, причём с запасом на промежуточные копии при преобразованиях; практическая оценка потребности — от трёх до десяти объёмов исходного файла. При шестнадцати гигабайтах оперативной памяти это даёт предел порядка двух-трёх гигабайт исходных данных, после которого процесс либо вытесняет память на диск, либо аварийно завершается. Границу можно отодвинуть чтением по частям, но тогда каждое агрегирование приходится программировать как накопление промежуточных итогов.
Четвёртое ограничение — согласованность при коллективной работе. Файл не допускает одновременного изменения из нескольких мест: копия у аналитика, копия у инженера и копия в почтовой рассылке расходятся в течение недели, и вопрос о том, какая версия верна, не имеет технического ответа. Сюда же примыкает утрата истории: файл обычно перезаписывается, поэтому предыдущее состояние данных не восстанавливается, а отчёт, подготовленный месяцем ранее, невоспроизводим.
Почему организационные меры не помогают
Перечисленные ограничения нередко пытаются компенсировать порядком работы: заводят каталоги с датой в имени, договариваются о правилах именования, размещают файлы в общем сетевом хранилище. Ни одна из этих мер не устраняет причины. Организационная дисциплина опирается на аккуратность участников и нарушается при первом отклонении от заведённого порядка, тогда как перечисленные свойства должны выполняться независимо от того, кто именно работает с данными сегодня.
Отсюда следует требование к следующему решению: нужна программа, которая берёт на себя контроль схемы, одновременный доступ и работу с объёмом, превышающим оперативную память. Такая программа существует и называется системой управления базами данных.
База данных
Таблица, схема и ключи
Основная форма организации данных — таблица, фрагмент которой приведён в начале темы. Строка таблицы описывает один объект или одно событие, столбец — одно свойство, одинаковое по смыслу для всех строк. Тип столбца фиксирован: целое число, дробное число, строка, отметка времени. В учебном наборе таблица приборов учёта содержит по строке на прибор со столбцами «модель», «ресурс», «дата установки»; таблица показаний — по строке на одно измерение со столбцами «прибор», «время», «значение».
Перечень таблиц, их столбцов, типов и ограничений называется схемой. Схема объявляется до загрузки данных и действует как контракт: значение, не соответствующее объявленному типу, в таблицу не попадает. Именно этого не хватало файлу.
Строки требуется отличать друг от друга. Столбец или набор столбцов, однозначно определяющий строку, называется первичным ключом; в таблице приборов эту роль играет идентификатор прибора. Ссылка на строку другой таблицы называется внешним ключом: в таблице показаний хранится идентификатор прибора, но не его модель и адрес — они лежат в таблице приборов и не повторяются в каждом из сотен тысяч измерений. Операция, собирающая связанные строки двух таблиц в одну, называется соединением; ей отведена значительная часть темы 2.
СУБД, запрос и транзакция
База данных — организованная по общей схеме совокупность данных. Система управления базами данных (англ. database management system, DBMS) — программа, которая этими данными управляет: размещает их на диске, выполняет запросы, следит за соблюдением схемы, обслуживает одновременные обращения нескольких пользователей. В обиходной речи базой данных называют и сами данные, и управляющую ими программу; далее эти понятия различаются, поскольку одну и ту же схему с данными можно перенести из одной СУБД в другую.
Запрос — сформулированное на специальном языке требование выдать или изменить данные. Для табличных баз таким языком служит SQL (Structured Query Language, SQL).
По способу развёртывания СУБД делятся на клиент-серверные и встраиваемые. Клиент-серверная работает как отдельная постоянно запущенная программа, к которой подключаются по сети; так устроены PostgreSQL и MongoDB. Встраиваемая подключается к программе пользователя как библиотека и не требует ни отдельного процесса, ни администрирования; так устроены SQLite и DuckDB. Различие касается эксплуатации, а не языка запросов и не модели данных.
Транзакция — группа операций, которая выполняется целиком либо не выполняется вовсе. Транзакции необходимы там, где данные постоянно изменяются несколькими пользователями: они не позволяют системе остановиться в промежуточном состоянии, когда одна часть изменений записана, а другая нет.
Что база данных добавляет к файлу
Теперь можно назвать приобретения точно, сопоставив их с четырьмя ограничениями предыдущего раздела.
Схема и типы объявляются однократно и проверяются при загрузке. Значение, не соответствующее объявленному типу, не попадает в таблицу незаметно — загрузка сообщает об ошибке. Контроль качества переносится из множества программ-потребителей в одну точку.
Обращение к данным становится декларативным. Запрос на SQL описывает, что должно получиться, а не как это вычислить: какие строки отобрать, по каким полям сгруппировать, какие агрегаты рассчитать. Порядок действий определяет планировщик запросов: он выбирает последовательность соединений, решает, использовать ли индекс, распределяет чтение по потокам. При обработке средствами языка программирования эти решения принимает автор кода и фиксирует их в тексте программы; при изменении объёма данных программу приходится переписывать, тогда как запрос сохраняет силу.
Снимается ограничение по памяти: СУБД читает данные блоками, использует диск при сортировке, выполняет соединения порциями. Объём обрабатываемых данных ограничен дисковым пространством, а рост времени выполнения при увеличении объёма остаётся плавным. Наконец, одновременная работа нескольких пользователей обеспечивается транзакциями, а не договорённостями между людьми. Сохранения предыдущих состояний данных база приложения при этом не гарантирует: обновление перезаписывает строку. Это требование аналитики, и к нему мы вернёмся при рассмотрении хранилища данных.
Эти преимущества имеют цену. Базу данных требуется развернуть, наполнить и сопровождать, а данные — регулярно в неё загружать. Для набора в двести строк, к которому обращается один человек раз в месяц, файл остаётся оправданным выбором. Критерием служит не размер сам по себе, а сочетание трёх обстоятельств: данные растут, обращения к ним регулярны, потребителей больше одного.
Две роли базы данных
Итак, данные помещены в базу. Возникает следующий вопрос — в какую именно. Как правило, база, содержащая нужные сведения, уже существует: показания принимает и хранит система телеметрии, обращения жителей — портал приёма заявок, сведения об оборудовании — учётная система. Каждая из них работает на собственной базе данных, и естественным выглядит решение задавать аналитические вопросы прямо этим базам. Именно здесь обнаруживается второе различие, и оно проходит уже не между файлом и базой, а между двумя способами использования базы.
Операционная нагрузка
Операционная обработка транзакций (англ. online transaction processing, OLTP) обслуживает работу прикладной системы. Обратимся к порталу приёма обращений: житель отправляет заявку, диспетчер открывает её карточку, исполнитель меняет статус на «в работе», затем на «закрыта». Каждое действие затрагивает одну-две строки и должно выполняться за десятки миллисекунд, поскольку пользователь ожидает отклика интерфейса.
Для этого профиля характерны множество коротких транзакций, точечный доступ по первичному ключу, значительная доля операций записи и десятки одновременно работающих пользователей. Требования к целостности жёсткие: заявка не может потеряться или продублироваться, статус не может измениться самопроизвольно. Под такой профиль оптимизированы классические реляционные СУБД: нормализованная схема 1 исключает противоречия при обновлении, индексы обеспечивают доступ по ключу, транзакции гарантируют атомарность. Ключевые метрики здесь — задержка отклика на одну операцию и число транзакций в секунду.
Аналитическая нагрузка
Аналитическая обработка (англ. online analytical processing, OLAP) отвечает на вопросы о совокупности данных 2. Сколько тепла израсходовано по районам за отопительный сезон. У каких объектов потребление растёт быстрее среднего. Как связаны аномалии в показаниях и последующие аварии. Ни один из этих вопросов не решается чтением одной строки: каждый требует просмотра сотен тысяч записей и возвращает таблицу в несколько строк.
Порядок величин полезно представлять заранее. Вопрос о потреблении тепла по районам на учебном наборе потребует чтения всех 345 846 строк показаний, соединения их со справочниками приборов и объектов и вернёт шесть строк — по одной на район. Отношение прочитанного к возвращённому здесь составляет десятки тысяч, что для аналитического запроса нормально. В операционном контуре соотношение обратное: для отображения карточки заявки система читает единицы строк и возвращает единицы строк.
Профиль нагрузки, таким образом, противоположен операционному. Запросов немного, каждый из них продолжителен, читает почти всю таблицу, но затрагивает лишь несколько столбцов из двадцати. Операций записи почти нет: данные загружаются пакетами по расписанию. Присутствует и требование, отсутствующее у операционной системы, — историчность. Для анализа существенно не текущее состояние, а динамика, поэтому данные не перезаписываются, а накапливаются.
Сопоставление профилей
Различия удобно свести воедино.
Единица работы: транзакция, затрагивающая одну строку, — против запроса, сканирующего таблицу. Форма схемы: нормализованная, минимально избыточная — против удобной для чтения, сознательно избыточной; к этому мы вернёмся в теме 4. Свежесть данных: секунды — против часов или суток, что для аналитики обычно приемлемо. Метрика: задержка отклика — против пропускной способности, то есть объёма данных, обработанного за единицу времени.
Различаются и требования к доступности. Остановка операционной системы означает остановку обслуживания: заявку невозможно принять, статус невозможно изменить. Недоступность аналитической системы в течение часа обычно не имеет последствий — отчёт будет построен позже. Отсюда и разные режимы обслуживания: аналитическую систему допустимо останавливать для перестройки структур хранения, тогда как операционную приходится обновлять без перерыва в работе.
Вернёмся к вопросу, с которого начался раздел. Выполнять аналитические запросы в базе работающего приложения нежелательно по двум причинам. С технической стороны продолжительный запрос-сканирование конкурирует за память, диск и процессор с короткими транзакциями, и время отклика прикладной системы возрастает. Со стороны структуры данных операционная схема, нормализованная ради корректности записи, неудобна для чтения: расчёт потребления по районам требует соединения четырёх-пяти таблиц, что усложняет формулировку запроса и увеличивает время его выполнения. Противоречие не устраняется настройками: два профиля предъявляют к устройству данных несовместимые требования 3. Существуют, впрочем, и промежуточные решения — гибридные системы (англ. hybrid transactional/analytical processing, HTAP), рассчитанные на обе нагрузки, и выполнение аналитики на реплике операционной базы, разводящее конкуренцию за ресурсы, но не различие в структуре.
Практический вывод состоит в разделении: аналитическую нагрузку выносят на отдельную базу данных, наполняемую копиями данных из операционных систем. Такую базу называют хранилищем данных.
Хранилище данных
Термин и его границы
Слово «хранилище» употребляется в двух значениях, и различать их следует сразу. В широком значении хранилище — любое организованное место, откуда данные читают: каталог файлов, объектное хранилище, база данных. В узком, терминологическом значении хранилище данных (англ. data warehouse) — база данных особого назначения, предназначенная для анализа накопленных данных, а не для обслуживания работы приложения.
Существенно, что это не альтернатива базе данных и не отдельный класс программ: хранилище данных — та же база данных, отличающаяся тем, какие данные в ней собраны, как они туда попадают и какие вопросы к ним задают. Одна и та же СУБД способна обслуживать и приложение, и аналитику; различие лежит в организации данных и характере нагрузки, а не в названии продукта. Далее «база данных» употребляется как общий термин, «хранилище данных» — как обозначение аналитической базы, «озеро данных» — как обозначение файлового хранения в объектной системе.
Четыре свойства
Классическое определение хранилища данных принадлежит Инмону и опирается на четыре свойства 4. Оно старше большинства современных технологий, однако описывает не технологию, а замысел, и потому сохраняет силу.
Предметная ориентированность означает, что данные организованы вокруг предмета анализа, а не вокруг обслуживающей их программы. В операционном контуре таблицы именуются по системам: «заявки портала», «журнал телеметрии», «справочник абонентов». В хранилище — по предметам: потребление ресурсов, объекты инфраструктуры, обращения жителей. Для анализа несущественно, из какой системы поступила запись; существенно, к какому объекту и к какому моменту времени она относится.
Интегрированность — наиболее трудоёмкое из четырёх свойств. Данные поступают из разных систем, и каждая ведёт учёт по-своему: в одной объект обозначен инвентарным номером, в другой — адресом, в третьей — условным именем «ЦТП-007». Тепло в одном источнике измеряется в гигакалориях, в другом — в киловатт-часах; отметки времени приходят то в местном времени, то в UTC, то без указания часового пояса. Интеграция означает приведение перечисленного к единым справочникам, единицам и правилам, причём однократно при загрузке, а не в каждом отчёте. В учебном наборе такая работа уже выполнена: показания, инциденты и обращения связаны общими ключами объектов и районов, а погода — датой.
Неизменяемость означает, что загруженные данные не редактируются пользователями; они изменяются только очередной загрузкой, и предыдущее состояние при этом сохраняется. Отсюда следует четвёртое свойство — привязка ко времени: строка отвечает не на вопрос «как обстоит дело сейчас», а на вопрос «как обстояло дело в такой-то момент». Это делает возможным отчёт по состоянию на прошлый квартал и, как будет показано в теме 9, корректную подготовку данных для обучения моделей.
Слои хранения
Перечисленные свойства не возникают сами собой — они обеспечиваются устройством хранилища. Между источниками и потребителем данные проходят несколько слоёв, и каждый имеет собственное назначение.
Сырой слой принимает данные в том виде, в каком они поступили: с исходными именами полей, текстовыми типами, дублями и пропусками. Очистку на входе выполнять не следует. Ошибка в правиле преобразования обнаруживается спустя недели, и если исходные данные не сохранены, восстановить их неоткуда: источник за это время перезаписал свою историю. Сырой слой позволяет исправить такую ошибку задним числом.
Детальный слой содержит те же данные, приведённые в порядок: типы разобраны, дубли сняты, ключи согласованы между источниками, единицы измерения унифицированы. Это основной рабочий уровень аналитика — на нём допустим любой вопрос, пусть и не всегда быстро выполнимый.
Витрины (англ. data mart) собираются под конкретного потребителя: отчёт, информационную панель, обучающую выборку. Витрина заранее агрегирована и денормализована, запрос к ней прост и выполняется быстро; платой служат избыточность и необходимость пересчёта при изменении детального слоя.
Слои — логическое разделение, а не обязательно разные системы. В небольшом контуре все три слоя размещаются в одной базе данных как три группы таблиц, различающиеся правилами наполнения; в крупном сырой слой может лежать в объектном хранилище, детальный — в аналитической СУБД, а витрины — в отдельной базе, обслуживающей отчётность. Существенно не число систем, а то, что данные каждого слоя получены известным преобразованием данных предыдущего.
Учебный набор курса устроен таким же образом. Файл показаний с пропусками, повторными отправками и выбросами относится к сырому слою. Таблица показаний с разобранными типами, снятыми дублями и отброшенными ошибочными значениями — к детальному; правила её построения разбираются в теме 8. Витрина суточных показателей собрана из сырого слоя по явно заданным правилам, поэтому её значения воспроизводимы: витрину всегда можно пересобрать заново, а значит, ошибку в правиле преобразования можно исправить без потери данных.
Классы хранилищ
До сих пор речь шла о таблицах — данных, у которых заранее известен набор столбцов и их типы. Значительная часть данных устроена иначе: ответ веб-сервиса содержит вложенные структуры и переменный набор полей, поток измерений с датчика важен не отдельными записями, а поведением во времени, текстовое описание неудобно искать по точному совпадению слов. Под каждый такой случай сложился свой класс хранилищ.
Реляционные аналитические хранилища работают с таблицами и SQL, но, в отличие от операционных СУБД, хранят данные по столбцам, а не по строкам; этому посвящена тема 3. Документные хранилища принимают записи с нефиксированным набором полей и вложенной структурой (тема 5). Хранилища временных рядов рассчитаны на поток измерений с метками времени (тема 6). Хранилища «ключ — значение» возвращают значение по ключу за микросекунды и служат слоем быстрого доступа рядом с основным. Векторные хранилища ищут не совпадение, а близость — по числовому представлению смысла (тема 7). Озеро данных (англ. data lake) хранит файлы произвольных форматов в объектном хранилище, а надстройка Lakehouse добавляет к ним табличную семантику.
Использование под разные задачи разных хранилищ получило название полиглотного хранения (англ. polyglot persistence) 5. Критерий выбора обычно формулируют через структуру данных и характер запросов; не менее весома третья составляющая — стоимость сопровождения. Каждая дополнительная система в контуре требует резервного копирования, наблюдения за состоянием, обновлений и специалистов, способных её обслуживать. Поэтому обоснованным решением нередко оказывается сохранение имеющейся СУБД до тех пор, пока её возможностей достаточно для поставленных задач.
Ориентиры курса
Изложение опирается на один инструмент — DuckDB 6. Это встраиваемый аналитический движок: он не требует сервера, устанавливается одной командой, хранит базу в единственном файле и читает форматы CSV и Parquet напрямую, без предварительной загрузки. По внутреннему устройству это колоночная система, спроектированная под аналитический профиль нагрузки; по способу применения — библиотека, подключаемая в обычной программе на Python. Там, где встраиваемого движка принципиально недостаточно, используется полноценный сервер: документная модель в теме 5 рассматривается на MongoDB.
Учебный инструмент не следует принимать за промышленный стандарт. В работающих системах аналитическое хранилище — как правило, сервер: ClickHouse, Greenplum, Vertica, PostgreSQL с колоночным расширением или облачная платформа. Отличие от DuckDB состоит не в языке запросов и не в модели данных, а в том, что сервер обслуживает многих пользователей одновременно, разграничивает доступ, резервируется и требует администрирования. Перечисленное существенно для эксплуатации и почти не влияет на формулировку аналитического запроса, поэтому изучение на встраиваемом движке не ведёт к искажению представлений; различия оговариваются по ходу изложения.
Понятие, которым мы будем пользоваться постоянно, — стоимость вопроса. Сравнивая способы хранения, мы будем измерять её тремя величинами: временем выполнения, объёмом фактически прочитанных данных и трудоёмкостью формулировки, то есть объёмом кода, который потребовалось написать. Величины ведут себя независимо: обработка файла средствами языка программирования выигрывает по времени на малых данных и проигрывает на больших, а готовая витрина отвечает мгновенно, но требует предварительной сборки, которую следовало бы включить в оценку. Оценка сразу по трём величинам отличает инженерное решение от произвольного предпочтения.
Дальнейшие темы следуют перечисленным классам хранилищ. Тема 2 разбирает аналитический SQL — язык, на котором задаются вопросы к табличным хранилищам, и с него мы начнём, поскольку сформулировать вопрос точно требуется прежде, чем выбирать, где хранить ответ.
Итоги темы
Ограничения файла — отсутствие схемы, полное чтение, предел оперативной памяти, расхождение копий — снимаются программой, а не порядком работы. СУБД переносит контроль схемы в одну точку и делает доступ к данным декларативным.
Короткие транзакции и сканирование таблиц предъявляют к устройству данных противоположные требования, поэтому анализ выносят в хранилище данных — предметно ориентированное, интегрированное, неизменяемое и привязанное ко времени, с сырым и детальным слоями и витринами. Для данных, не укладывающихся в таблицу, существуют другие классы хранилищ, но каждая новая система увеличивает стоимость сопровождения.
Способы хранения в курсе сравниваются по стоимости вопроса: времени выполнения, объёму прочитанных данных и трудоёмкости формулировки.
Литература
- Codd E. F. A Relational Model of Data for Large Shared Data Banks. — Communications of the ACM, 1970, С. 377–387, DOI: 10.1145/362384.362685.
- Codd E. F., Codd S. B., Salley C. T. Providing OLAP to User-Analysts: An IT Mandate. — Arbor Software Corporation, 1993.
- Chaudhuri S., Dayal U. An Overview of Data Warehousing and OLAP Technology. — ACM SIGMOD Record, 1997, С. 65–74, DOI: 10.1145/248603.248616.
- Inmon W. H. Building the Data Warehouse. — Wiley, 2005.
- Sadalage P. J., Fowler M. NoSQL Distilled: A Brief Guide to the Emerging World of Polyglot Persistence. — Addison-Wesley, 2013.
- Raasveldt M., Mühleisen H. DuckDB: An Embeddable Analytical Database. — Proceedings of the 2019 International Conference on Management of Data (SIGMOD '19), 2019, С. 1981–1984, DOI: 10.1145/3299869.3320212.