Курс Essential Architecture #Data
Я уже рассказывал про вводный курс по архитектуре, а также публиковал расшифровку лекции про код. В этой статье будет расшифровка лекции про подходы к организации работы с данными.

План этой лекции достаточно насыщен и представлен ниже.

Начнем мы с того, что вспомним про один из принципов 12 факторного приложения, а именно про принцип номер 6. Суть в том, чтобы делать stateless
приложения, то есть не хранить состояние внутри него. Если получается так спроектировать приложение, то вопросы отказоустойчивости и масштабирования решаются гораздо проще, чем в случае stateful
приложений.

12 factor app
и фактор про stateless приложение”Представим, что мы именно так проектируем свои приложения, но возникает вопрос — а где тогда хранить состояние?

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

По-факту, это один из самых старых способов. Программистам приходилось персистентно сохранять данные на лентах, дискетах, жетских дисках и так далее. Писать их напрямую на носитель информации зачастую неэффективно с точки зрения баланса сложности написания кода и преимуществ, получаемых от низкоуровневой работы, поэтому обычно используются файловые системы ( ntfs
, ext4
, exFAT
, …).
Файловая система определяет формат содержимого и способ физического хранения информации, которую принято группировать в виде файлов. Она связывает носитель информации с одной стороны и API
для доступа к файлам — с другой. Файловая система не обязательно напрямую связана с физическим носителем информации. Существуют виртуальные файловые системы, а также сетевые файловые системы. Практически всегда файлы на дисках объединяются в каталоги.
Хранение информации в виде файлов и каталогов было всем хорошо, но было 2 проблемы:
- формат хранения варьировался от задачи к задаче и требовалось писать отдельно логику того, как сложить данные и как их считать с устройства хранения — в итоге, часто смешивалась бизнес-логика приложения и логика работы с
data layer
- приложения становились многопользовательскими, поэтому было сложно реализовывать параллельную работу с данными внутри файлов
В итоге, для решения этих проблем появились реляционные базы данных.

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

Давайте отдельно обсудим основные характеристики реляционных баз данных, которые сделали их такими популярными в использовании. И начнем мы с нормализации, которая влияет напрямую на проектирование моделей предметной области. Суть в том, чтобы таблицы (отношения), спроектированные в рамках реляционной модели соответствовали определенным критериям, которые называются нормальными формами. На рисунке ниже они представлены снизу вверх по мере ужесточения “критериев нормальности”:)

Интересно для примера рассмотреть первую нормальную форму ( 1NF
), определение которой звучит так
Переменная отношения находится в первой нормальной форме (
1НФ
) тогда и только тогда, когда в любом допустимом значении отношения каждый его кортеж содержит только одно значение для каждого из атрибутов.
В итоге, если мы начинаем складывать в атрибуты целые json
-документы, то мы тем самым нарушаем уже первую нормальную форму. Теперь стоит пойти ко второй характеристики, а именно к SQL
.

Реляционные базы данных принесли с собой декларативный язык программирования SQL
, который разделил вопросы “что” и “как”. Это был большой шаг вперед по сравнению с файлами, где эти вопросы часто были смешаны. SQL
позволял прикладным программистам написать запрос, в котором они указывали какие данные им нужны, а дальше его выполнение было за СУБД (системой управления базами данных). Если интересно узнать про внутренности баз данных, то рекомендую почитать книгу “Database Internals”, в которой есть мясо для техногиков:) А мы идем дальше в сторону того, что позволяет СУБД быстро выбирать данные… правда, не все, а те, которым помогают индексы.

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

Транзакции — это группа последовательных операций с базой данных, которые логически атомарны. Но интересно не просто определение транзакций, а гарантии (ACID
), которые дают СУБД и почему эти гарантии так нравятся прикладным разработчикам.

Первое свойство транзакций atomicity
прямо следует из определения транзакций. Второе свойство consistency
более интересно.

По-факту, СУБД обещает поддерживать не абсолютную целостность модели предметной области, а то, что она контролирует на своем уровне — а именно foreign keys
и constraints
.
Следующее свойство isolation
позволяет организовать относительно просто многопользовательскую работу с данными.

Изолированность транзакций непосредственно связана с разными уровнями, которые начинаются с уровня read uncommited
и заканчиваются уровнем serializable
. В первом случае клиенты могут читать на лету данные, изменяемые другими транзакциями, которые еще не прошли commit
, а во втором случае транзакции эффективно выстраиваются в последовательность и не проявляется проблемных эффектов.
Последний элемент акронима ACID
относится к durability
. Это свойство транзакций позволяет клиентам, получив подтверждение о прохождении транзакции, дальше не беспокоиться о сбое или отказе. Теоретически СУБД после восстановления от сбоя сможет восстановить данные, даже если они еще не были полностью обработаны — главное, что уже прошел fsync
и они оказались на диске в write ahead log
(как делается в Postgres
).

Есть еще пара интересных моментов с реляционными базами данных (и не только ими), которые я хотел тут обсудить. Первая часть связана с репликацией.

Репликация в базах данных — это просто организация нескольких инстансов с одинаковым набором данных (одинаковость может быть не мгновенной как в случае с асинхронной репликаций).
Например, мы можем настроить primary
— secondary
репликацию для повышения отказоустойчивости нашей схемы и для масштабирования потока чтений. В первом случае мы можем во время сбоя на primary
ноде нашей базы данных выбрать одну из secondary
нод и промотировать ее до главной. Эту возможность надо настраивать очень аккуратно. Если это сделать не слишком хорошо, то понять поведение нашей системы во время моргания сети между нодами нашей primary
— secondary
инсталляции будет достаточно нетривиальной задачей. Второй вариант с масштабированием потока чтений тоже имеет свои особенности — важно, чтобы приложение умело читать и писать данные в разные инстансы. Еще есть особенность с тем, что репликации бывают синхронные и асинхронные. Первые сильно замедляют запись, так как мы ждем подтверждения от всех реплик, а вторые — просто немного отстают от primary
ноды и асинхронно ее догоняют. В случае асинхронной репликации нам надо сделать так, чтобы чтения только что записанных данных были минимизированы и четко определены — их нам придется отправлять на primary
ноду.
Последний пункт повестки — это обсуждение шардирования данных.

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

OLAP
( online analytical processing
) — технология обработки данных, заключающаяся в подготовке суммарной (агрегированной) информации на основе больших массивов данных, структурированных по многомерному принципу. В 1993 году Кодд предложил 12 правил, которым должна удовлетворять система аналитической обработки по аналогии с 12 правилами для реляционных баз данных. В итоге, для OLTP
(online transactional processing
) нагрузок предполагалось использовать реляционные базы данных, а для аналитических и Business Intelligence
задач — OLAP
системы, которые используют OLAP
-структуры, созданные из рабочих данных, называемые OLAP
-кубами. Куб создаётся из соединения таблиц с применением схемы звезды или схемы снежинки. В центре схемы звезды находится таблица фактов, которая содержит ключевые факты, по которым делаются запросы. Множественные таблицы с измерениями присоединены к таблице фактов. OLAP
-куб содержит базовые данные и информацию об измерениях (агрегаты).
Гораздо глубже и подробнее изучать OLAP
подходы нужно разработчикам, работающим на построением систем отчетности, а остальным обычно хватает и такого понимания.
А теперь перейдем к вопросам BigData
и апостолу ее Hadoop
.

Hadoop
считается одной из основополагающих технологий «больших данных». Вокруг Hadoop
образовалась целая экосистема из связанных проектов и технологий. Разработка стартовала в 2005 году. Он используется для реализации поисковых и контекстных механизмов многих высоконагруженных веб-сайтов. Разработан на Java
в рамках вычислительной парадигмы MapReduce
, которую чуть раньше описали в своей статье специалисты Google. Согласно этой парадигме приложение разделяется на большое количество одинаковых элементарных заданий, выполнимых на узлах кластера и естественным образом сводимых в конечный результат. Одной из основных целей Hadoop
изначально было обеспечение горизонтальной масштабируемости кластера посредством добавления недорогих узлов.
На самом деле эта экосистема очень интересна, но как и история с OLAP
подходам она обычно нужна тем, кто решает много задач с большими данными, например, отгружая логи в HDFS
, выбирая данные при помощи HBase
или Hive
и выполняя стриминг задачи при помощи Apache Spark
. А в этой статье мы затронули BigData краем и пошли дальше к Object Storage
.

Объектное хранилище — это способ хранения данных без иерархии, который обычно используется в облачной среде. В отличие от остальных способов хранения данных, объектное хранилище не использует дерево каталогов. Отдельные единицы данных (объекты) сосуществуют в пуле данных на одном уровне. Каждый объект имеет уникальный идентификатор, используемый приложением для обращения к нему. Кроме того, каждый объект может содержать метаданные, получаемые вместе с ним. Самое примечательное хранилище, API
которого стало стандартом де–факто, было запущено AWS
в 2006 и называлось Simple Storage Service
или S3
. Дальше объектные хранилища стали стандартным кубиком для построения систем и почти все из них обладают S3
-совместим интерфейсом.
Дальше мы наконец-то переходим к NoSQL
базам данных, которые сейчас во многом используются наравне с реляционными особенно в тех местах, где нам нужно уметь горизонтально масштабироваться и не требуются полный перечень гарантий ACID
.

Всего мы рассмотрим 4 варианта NoSQL решений:
- ключ-значение (
key-value
) - семейство столбцов (
column-family
) - документо-ориентированная (
document-oriented
) - графовая (
graph
)
Все они представлены ниже




Теперь, когда мы знаем какие NoSQL
решения бывают, стоит поговорить о тех гарантиях, которые они обеспечивают. И эти гарантии далеко не ACID
, а BASE
. Но, перед тем как перейти к их рассмотрению, стоит рассмотреть CAP
-теорему, предложенную Эриком Брюером, которая говорит про связь согласованности, доступности и устойчивости к разделению в распределенных системах.

После рассмотрения CAP-теоремы, мы готовы обсудить гарантии BASE, которые говорят о следующем.



Теперь, зная гарантии ACID
реляционных баз данных, гарантии BASE
NoSQL
решений и CAP
-теорему, мы можем подумать о том, как нам правильно применять NoSQL
решение в нашем конкретном случае.

После того, как мы рассмотрели вопросы хранения данных, стоит обсудить и следующий вопрос

Для этого у нас есть 4 подхода, первый из которых называется передача файла (file transfer
) и стар как мир:) Все мы так делаем, когда загружаем фото в облачное хранилище и дальше генерируем общедоступную ссылку, которую потом отдаем друзьям.

Следующий вариант — это общая база данных (shared database
). Этот вариант был достаточно популярен в прошлом и до сих пор существует в legacy
системах. Его проблема в том, что в общей базе данных нет нормальных интерфейсов и инкапсуляции логики за ними (если не считать поддержку views
). В итоге, у нас возникает high coupling
между частями системы, что приводит к феерическому переплетению систем и общему замедлению разработки до полной остановки. Не даром, что в современных подходах проповедуют концепции shared nothing
между сервисами, имея в виду как раз базы данных

Следующие два варианта интеграции, remote procedure call
(PRC
) и messaging
, которые мы рассмотрим в следующей лекции, так как они больше относятся к проектированию распределенных систем.

Теперь настало время подумать, а как мы организуем работу с аналитическими данными по продукту.

Для этого есть достаточно давно педалируемый подход с отгрузкой данных из operational data plane
в data lake
на благо дата сатанистам и аналитикам по данным. Обычно это делают data
инженеры, которые берут данные прямо из oltp
систем и настраивают etl
процессы.

У этого подхода есть проблемы с тем, что мы опять получаем high coupling
между системами, которые используют oltp
базы и нашими витринами, которые работают поверх data lake
. Суть в том, что любые доработки внутри сервисов потенциально могут повлиять на качество отгружаемых через etl
процессы данных. В крупных компаниях контролировать такое очень сложно.
Но на помощь приходит новый подход, называемый Data Mesh
. Основные принципы данного подхода представлены на слайде ниже.

А вот в чем их суть
- Доменно-ориентированная децентрализация владения и архитектуры помогает экосистеме создания и потребления данных масштабироваться при увеличении числа источников данных, сценариев использования и увеличении многообразия способов доступа к данным — ноды сети становятся более автономными.
- Принцип подхода к данным как продукту (data products) позволяет потребителям данных просто найти, понять и использовать данные высокого качества, которые распределены по многим доменам предметной области.
- Self-service платформа для данных позволяет доменным командам создавать и потреблять данные (data products) автономно с использованием платформенных абстракций, скрывая сложность создания, наполнения и поддержки безопасности этих интероперабельных data products.
- Federated computational governance позволяет пользователям данные получить эффект от агрегации и корреляции независимых data products. Data Mesh выступает как экосистема, которая следует общим стандартам интероперабельности (interoperability), которые встроены в self service платформу.
В первом принципе было упоминание про доменно-ориентированнное и децентрализованное владение данными. Возникает вопрос, что это такое и как правильно это делать.

И на этот вопрос нам дает ответ подход, называемый Domain Driven Design
или просто DDD
. Его основные моменты изложены ниже

В итоге, мы пробежались галопом по Европам по темам
- Где хранить состояние приложения
- Файловые системы
- Реляционные базы данных
- Объектное хранилище
-OLAP
-BigData
-NoSQL
- Как обмениваться данными (варианты интеграции)
- Как организовывать работу с аналитическими данными
(Data Lake
vsData Mesh
) - Как проектировать доменную модель
Если вас заинтересовала одна из тем, то подробнее ее можно изучить по ссылкам, приведенным ниже.
Источники
- Книга “Designing Data-Intensive Applications”
- Книга “Enterprise Integration Patterns”
- Курс “Databases: Relational Databases and SQL” от Standford на Edx
- Книга “NoSQL Distilled”
- Книга “Database Internals”
- Книга “Big Data”
- Книга “What Is Domain-Driven Design?” и ее обзор
- Статья “Data Mesh Principles and Logical Architecture”
- Книга “Database Internals” и ее первая часть про “Storage Engine”