Архитектура данных · OLTP, OLAP, Lakehouse, CDC
Финальный урок раздела, карта мира за пределами одной БД. До этого ты учился держать Postgres под нагрузкой и подкладывать рядом Redis с ClickHouse. Теперь увеличиваем масштаб: что происходит, когда у компании появляются аналитики, дашборды для CEO, ML-инженеры и регуляторы, которым нужны исторические выгрузки за три года. Postgres всё ещё в центре, но вокруг него вырастает целая экосистема.
Этот урок отвечает на четыре вопроса, которые рано или поздно ловит каждый Senior backend:
- Куда положить данные так, чтобы и приложение работало быстро, и аналитика не падала по таймауту?
- Что такое data lake, чем lakehouse от него отличается, и почему все вдруг заговорили про Iceberg?
- Как тащить изменения из Postgres в аналитический контур, не убивая прод?
- Когда обработка должна быть batch, когда streaming, и кто на самом деле перерабатывает Kafka.
К концу занятия ты сможешь:
- объяснить, почему нельзя посадить аналитиков напрямую в прод-Postgres, даже если очень хочется;
- развести в голове lake, warehouse и lakehouse, выбрать одно из трёх под конкретный сценарий;
- настроить CDC из Postgres через Debezium и понять, какие гарантии и какие подводные камни ты при этом получаешь;
- набросать архитектуру аналитики BatSchool на следующие два года, сознательно отказавшись от лишнего.
0. Два мира одной компании
В любой компании, у которой есть и продукт, и метрики, со временем возникают два разных мира с разными требованиями к БД.
OLTP мир, это твой продакшен. Менти открыл урок, поставил галочку на ДЗ, сервер сходил в Postgres за секунду, обновил homework_submissions, вернул ответ. Запросы короткие, точечные, по индексу. Их много, тысячи в минуту. Каждый трогает доли строк. Главная метрика, p99 latency.
OLAP мир, это аналитика. CEO хочет понять, сколько менти за квартал дошли до уровня S, какой раздел отваливает больше всего, как меняется retention по когортам. Один такой запрос трогает миллионы строк, делает несколько JOIN, GROUP BY по дате, городу, грейду. Бежит секунды, иногда минуты. Их мало, штучные.
| Свойство | OLTP | OLAP |
|---|---|---|
| Кто запускает | приложение, тысячи раз в минуту | человек или дашборд, единицы раз |
| Типовой запрос | SELECT ... WHERE id = ? | SELECT date, COUNT(*) FROM ... GROUP BY date |
| Доля прочитанных строк | 0.001% | 10-100% |
| Колонок в запросе | большая часть row | 3-10 из сотни |
| Запись | конкурентные UPDATE/INSERT | bulk-загрузка раз в N минут |
| Latency, на которую целятся | миллисекунды | секунды, минуты |
| Метрика, по которой бьются | p99 | пропускная способность по строкам/сек |
Главный соблазн Junior-инженера: посадить аналитиков прямо в прод. Дать им read-replica Postgres и пусть пишут что хотят. Это работает ровно до первого тяжёлого запроса, который положит планировщик в Nested Loop на полмиллиарда строк и съест IO-канал реплики.
Поэтому индустрия пришла к разделению. У продукта своя БД, оптимизированная под OLTP. У аналитиков своя, оптимизированная под OLAP. Между ними data pipeline, через который текут изменения. Этот урок про то, как этот pipeline устроен и какие у него варианты.
1. Row vs column store, ещё раз
В уроке 05 ты уже встречал ClickHouse и знаешь, что он “колоночный”. Давай ещё раз, что это значит физически и почему это важно для архитектуры.
Postgres хранит таблицу страницами по 8 КБ, в каждой странице лежат целые строки одна за другой. Это row store. Если запрос трогает одну строку целиком, это идеально: один seek, одна страница, готово.
ClickHouse хранит данные по колонкам: сначала все значения колонки lesson_id подряд, потом все user_id, потом все ts. Это column store. Запрос SELECT date, COUNT(*) FROM events GROUP BY date читает физически только две колонки из тридцати. Плюс ClickHouse сжимает каждую колонку отдельно, и сжатие даёт 5-10x: соседние значения date одинаковы, соседние lesson_id повторяются.
| Сценарий | Row store | Column store |
|---|---|---|
SELECT * FROM users WHERE id = 42 | один disk seek, один tuple | надо собрать строку из N разных файлов |
SELECT SUM(amount) FROM events | сканирует страницы со всеми колонками | сканирует только колонку amount |
UPDATE users SET email = ... WHERE id = 42 | одна страница, чистое MVCC-обновление | надо переписать tuple в каждой колоночной части |
| Сжатие | страница сжимается целиком, плохо | колонка сжимается под свой тип, отлично |
Отсюда фундаментальный закон: одна и та же СУБД редко идеальна для обоих сценариев. Можно делать компромиссы (Postgres + JSONB + colstore extension вроде Citus columnar, ClickHouse + ReplacingMergeTree для апсертов), но они всегда упираются в выбор: точечные UPDATE или сканы по колонкам, не оба сразу.
2. HTAP и почему мечта
Соблазн соединить оба мира в одной системе называется HTAP. Один кластер, одна копия данных, оба профиля нагрузки.
На практике HTAP-системы делятся на два лагеря.
Два движка под одной обёрткой. TiDB держит row-store TiKV для транзакций и колоночную TiFlash для аналитики, синхронизирует через Raft. Аналитический запрос едет в TiFlash, транзакционный в TiKV. С точки зрения SQL это одна БД, физически две.
Один движок с гибридным хранением. SingleStore хранит горячие данные в row-store, фоновый процесс переносит холодные в column. Запрос автоматически читает из обоих.
Почему мечта не стала индустриальным стандартом:
- HTAP-системы дороже, чем Postgres + ClickHouse, в инженерах и в железе.
- Чтобы аналитика реально шла на той же копии, что и прод, нужно изолировать ресурсы (CPU, IO, память). Это сделано во всех HTAP, но всегда хуже, чем физическое разделение.
- Аналитики хотят сложные ad-hoc запросы и не хотят отвечать “ой, я сейчас положил прод”. Психологически проще, когда контуры разные.
- Главное: 90% продакшен-команд могут спокойно сделать read replica Postgres + ночной выгрузкой в ClickHouse и закрыть все нужды. HTAP появляется на повестке, когда выручка измеряется миллиардами и требуется real-time OLAP по операционным данным.
Запоминай простую эвристику. Сначала Postgres-first. Когда упёрся в аналитику, ставь рядом ClickHouse или DWH. К HTAP идёшь только если есть жёсткое требование “видеть транзакцию в дашборде через секунду на петабайтных данных”, и есть бюджет.
3. Data lake и lakehouse
Когда данных становится много, на сцену выходит ещё один слой: storage отдельно от compute. Это другой способ организовать аналитику, конкурирующий с классическим OLAP-DWH.
Data warehouse
Data warehouse, это классическая модель: ETL-процесс берёт данные из источников, прибивает их к стройной схеме (звезда из фактов и измерений), грузит в специализированную колоночную СУБД. Сверху сидит BI-инструмент: Tableau, Looker, Metabase.
Сильная сторона DWH, предсказуемость. Известна схема, известны индексы, известны типичные запросы. Слабая, всё, что не структурированно (логи, события, JSON-документы), приходится либо приводить к схеме, либо хранить где-то ещё.
Data lake
Параллельно DWH в 2010-х оформилась идея data lake: положить всё в S3 как есть, в открытых форматах, без обязательной схемы. Compute подключается сверху по требованию.
Lake выигрывает по трём осям:
- Стоимость хранения. S3 стоит копейки за гигабайт, дешевле любого managed DWH.
- Гибкость формата. Полу-структурированные JSON, raw-логи, parquet-таблицы, изображения, эмбеддинги, всё лежит рядом.
- Разделение storage и compute. Можно поднять Spark-кластер, прочитать терабайты, потушить кластер. Без долгого pre-provisioning.
Минус классического lake: data swamp. Без транзакций, без атомарного UPDATE, без понятной схемы. Параллельный writer и reader легко поломают друг друга.
Lakehouse
Lakehouse, это попытка взять плюсы lake и плюсы warehouse одновременно. Файлы по-прежнему в S3, формат по-прежнему открытый (Parquet). Но поверх лежит метаслой, который превращает кучу файлов в нормальную таблицу.
Три основных открытых формата:
- Apache Iceberg, родом из Netflix, сегодня поддерживается большинством движков (Spark, Trino, Snowflake, BigQuery, ClickHouse, Databricks). Открытый, нейтральный, спецификация в открытом доступе.
- Delta Lake, родом из Databricks, тоже открытый, но исторически Databricks-центричный. Сильная интеграция со Spark.
- Apache Hudi, родом из Uber, силён в incremental processing и upsert по индексу.
Все три устроены похоже. Данные лежат в parquet-файлах. Рядом manifest-файлы, в которых описано: какие parquet входят в текущий snapshot таблицы, какие схемы у колонок, какие партиции. Каждый INSERT/UPDATE/DELETE создаёт новый snapshot, старые остаются. Отсюда фишки:
- ACID-транзакции. Параллельные writer’ы не ломают друг друга, потому что фиксация снапшота атомарна.
- Schema evolution. Добавить колонку, переименовать, удалить, всё без перезаписи данных.
- Time travel. Прочитать таблицу на состояние двух недель назад, чтобы воспроизвести вчерашний дашборд или разобрать инцидент.
- UPDATE и DELETE по условию. Делается через copy-on-write (перезапись затронутых файлов) или merge-on-read (отдельные delete-файлы, склейка при чтении).
| Свойство | Data lake (сырой parquet) | Lakehouse (Iceberg/Delta) | Data warehouse |
|---|---|---|---|
| Где живут данные | S3, файлы | S3, файлы + метаслой | проприетарный сторадж |
| Транзакции | нет | да | да |
| UPDATE/DELETE | перезаписать партицию | по условию | да |
| Schema evolution | руками | да, через id-колонки | DDL |
| Time travel | нет | да | редко |
| Compute | внешний (Spark, Trino) | внешний | встроенный |
| Vendor lock-in | нет | минимальный (открытый формат) | сильный |
Запоминай суть: lakehouse, это не отдельная СУБД, это формат таблицы в файлах. Compute (Spark, Trino, ClickHouse, DuckDB) подключается к нему сверху как читатель и писатель.
4. Engines поверх lake
Если данные лежат в Iceberg-таблице на S3, как ты их читаешь? Через query engine.
| Engine | Сильная сторона | Когда брать |
|---|---|---|
| Spark | масштаб, петабайтные ETL | большие batch-pipeline, ML-фичи, тяжёлые трансформации |
| Trino / Presto | интерактивный SQL поверх lake, federated queries | дашборды, ad-hoc от аналитиков |
| DuckDB | локальная аналитика, embedded SQL | разработка pipeline, ноутбуки, edge-аналитика |
| ClickHouse с S3 engine | колоночная агрегация, real-time | hot path аналитики, дашборды с тысячами запросов |
| BigQuery, Snowflake, Athena | managed, serverless | когда не хочется DevOps |
Главное наблюдение: разные engines на одном lake. Один и тот же Iceberg-датасет читают Spark для тяжёлого ETL, Trino для интерактива, DuckDB для разработки. Compute стал товаром, который ты подбираешь под сценарий, не переписывая хранение.
Это и есть архитектурный сдвиг, который lakehouse принёс индустрии: decoupling storage and compute.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.