Реклама
Селектел, перетяжка, 22.06
Селектел, перетяжка, 22.06
Селектел, перетяжка, 22.06

10 подходов по работе с данными, которые должен знать каждый data-инженер

В статье рассматривает 10 подходов по работе с данными, которые должен знать любой уважающий себя data-инженер.

Обложка: 10 подходов по работе с данными, которые должен знать каждый data-инженер

В статье рассматриваем 10 подходов по работе с данными, которые должен знать любой уважающий себя data-инженер.

10. Star Schema — старая рабочая лошадка (которая разваливается на больших данных)

Наша любимая звездочка — это самая базовая модель для аналитики. Таблица фактов (с метриками, напр. продажи) окружена таблицами измерений.

К примеру, таблица фактов «Продажи», а вокруг нее таблицы измерения: «Клиент» (кому продали), «Продукт» (что продали), «Заказ» и т.д. Но на больших объемах, а также при увеличении объектов в хранилище данных, она может начать подводить. Особенно сложно в нее вносить какие-либо изменения, так как таблицы зависят друг от друга. Звездочка это такой неэластичный монолит.

Проблемы на практике:

Когда таблица fact_sales растёт до сотен миллионов или миллиардов строк, запросы начинают жёстко тормозить. JOIN-ы с несколькими измерениями и GROUP BY приводят к долгим сканированиям. сложно вносить изменения, все может посыпаться

Типичный запрос может выглядеть так:

			SELECT
    d.region,
    d.store_type,
    SUM(f.sales_amount) AS total_sales
FROM fact_sales f
JOIN dim_store d
  ON f.store_id = d.store_id
JOIN dim_date dt
  ON f.date_id = dt.date_id
WHERE dt.date BETWEEN '2025-11-28' AND '2025-11-29'
GROUP BY d.region, d.store_type;
		

И чем больше строк в fact_sales, тем медленнее выполняется такой запрос.

Проще говоря: звёздочка удобна и понятна, но не очень масштабируется.

9. Snowflake Schema — слишком усложнённая и медлительная

Снежинка — это “нормализованная” версия звездочки: таблицы измерений разбиваются на ещё более мелкие части, как ветви снежинки. В результате каждая иерархия измерений (например продукт → категория → бренд) живёт в своей таблице.

Это улучшает хранение (меньше дублирования данных и меньше места на диске), но делает запросы еще более тяжёлыми, потому что для простого отчёта нужно больше JOIN-ов между таблицами. Зато теперь чуточку легче поддерживать и это уже мене похоже на монолит, хотя все равно таковым является.

Короче говоря:

  • Плюсы: меньше избыточности, данные лучше структурированы, нормализовано (хорошо для поддержки целостности).
  • Минусы: запросы становятся сложнее и медленнее (много JOIN-ов), сложнее для понимания тем, кто пишет SQL-аналитику. И все равно монолит, который сложно поддерживать.

п=

			SELECT
    r.region_name,
    c.category_name,
    SUM(f.sales_amount) AS total_sales
FROM fact_sales f
JOIN dim_product p        ON f.product_id = p.product_id
JOIN dim_category c       ON p.category_id = c.category_id
JOIN dim_subcategory sc   ON c.subcategory_id = sc.subcategory_id
JOIN dim_store s          ON f.store_id = s.store_id
JOIN dim_region r         ON s.region_id = r.region_id
GROUP BY r.region_name, c.category_name;

		

8. Data Vault — гибко, масштабируемо… и очень сложно

Эта модель появилась, чтобы строить очень масштабируемые DWH, где можно спокойно переживать постоянные изменения источников данных.

Вместо привычных «фактов и измерений» тут три типа таблиц:

· Hubs — бизнес-сущности (клиент, заказ, продукт).

· Links — связи между ними.

· Satellites — атрибуты и история изменений (фио клиента, наименование товара, стоимость товара).

Можно добавлять новые источники почти без боли и хранить полную историю изменений.

Но где подвох?

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

И во-вторых, даже простой отчёт превращается в цепочку JOIN-ов на пол-экрана SQL.

А в-третьих, новым людям в команде разобраться в этом всём, как отдельный квест.

Data Vault отлично подходит для enterprise-DWH, где важны аудит, история и масштаб. Но для обычной аналитики это как резать колбасу бензопилой.

Пример запроса:

			SELECT
    h_cust.customer_id,
    s_cust.customer_name,
    SUM(s_tx.amount) AS total_spend
FROM hub_customer h_cust
JOIN link_customer_tx l_ct
  ON h_cust.hub_customer_key = l_ct.hub_customer_key
JOIN hub_transaction h_tx
  ON l_ct.hub_transaction_key = h_tx.hub_transaction_key
JOIN sat_customer s_cust
  ON h_cust.hub_customer_key = s_cust.hub_customer_key
 AND s_cust.is_current = true
JOIN sat_transaction s_tx
  ON h_tx.hub_transaction_key = s_tx.hub_transaction_key
 AND s_tx.is_current = true
GROUP BY h_cust.customer_id, s_cust.customer_name;

		

7. Wide Tables / One Big Table (OBT) — для time-series

Это противоположность сложным моделям вроде Data Vault.

Идея максимально простая - берём данные из разных таблиц и склеиваем всё в одну огромную таблицу.

· Запросы супербыстрые, почти без JOIN-ов.

· Очень удобно для BI и дашбордов.

· Понятная структура: «одна строка = один бизнес-объект».

Но за скорость нужно платить следующими недостатками:

· Дублирование данных на каждом шаге,

· Таблица быстро разрастается до сотен колонок,

· Любое изменение логики требует пересборки всей таблицы,

· Легко поймать несогласованность данных.

Короче:

OBT — это как кеш. Но все это добро практически невозможно поддерживать.

Часто применяется в рамках time-seriesанализа: IoT, анализ метрик, логов, clickstream.

Пример запроса:

			SELECT
    user_city,
    product_category,
    SUM(amount)        AS revenue,
    COUNT(order_id)    AS orders_cnt
FROM orders_obt
WHERE order_ts >= CURRENT_DATE - INTERVAL '7 day'
  AND status = 'PAID'
GROUP BY
    user_city,
    product_category
ORDER BY revenue DESC;
		

6. Graph Models (Neo4j, TigerGraph) — для связей

Когда ценность данных именно в связях (например, в мошеннических схемах, социальном влиянии или цепочках переходов по сети). В таком случае куча join-ов просто перестают работать, особенно при всякого рода рекурсиях.

С этим помогают графовые базы.

Вот к примеру, нужно нам найти всех друзей наших друзей:

			-- Найти друзей друзей (2 перехода)
SELECT DISTINCT f2.user_id
FROM friendships f1
JOIN friendships f2
  ON f1.friend_id = f2.user_id
WHERE f1.user_id = 123
  AND f2.user_id <> 123;

		
  • Количество JOIN-ов растёт экспоненциально с каждым уровнем.
  • Уже на 3+ переходах запрос становится почти нечитаемым.
  • Кратно падает производительность

Теперь посмотрим, как в графовой базе это будет реализовано:

			MATCH (u:User {id: 123})-[:FRIEND*2..4]->(conn)
RETURN DISTINCT conn.id
LIMIT 100;

		

Отлично подходит для антифрода, соцграфов и рекомендательных систем.

И совершенно не подходит для OLAP-аналитики.

5. Streaming Event Sourcing (Kafka + CDC)

Классический batch-ETL плохо сочетается с системами реального времени. Для real-time аналитики более всего подходит CDC.

CDC превращает изменения в базе данных в события, которые отправляются потребителю (DWH).

Данные становятся не снимком, а временной линией.

Как все это работает

· Базы данных → источники событий

· Kafka → надёжный журнал событий

· Потребители → могут пересобрать состояние в любой момент

Пример (из debezium):

			{
  "op": "u",
  "before": { "status": "CREATED" },
  "after":  { "status": "PAID" },
  "ts_ms": 1735209123123,
  "source": { "table": "orders" }
}

		

Плюсы

· данные в реальном времени

· встроенное восстановление после сбоев

· слабая зависимость между продюсерами и консьюмерами

Минусы

· высокая сложность реализации

· порядок событий и идемпотентность реализовать непросто

· отладка требует видимости на уровне событий

4. Columnar Storage (Parquet, Delta Lake) для дешёвой и быстрой аналитики

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

Колоночный же формат подразумевает, что значения столбцов лежат в разных местах.

Ключевые преимущества

· читаются только нужные колонки

· сильное сжатие (RLE, словарное кодирование)

· векторизованное выполнение

Меньше I/O → быстрее запросы.

Структура таблицы:

			CREATE TABLE sales_parquet (
    order_id BIGINT,
    region   STRING,
    amount   DECIMAL(10,2),
    order_ts TIMESTAMP
)
USING PARQUET
PARTITIONED BY (region, order_date);

		

3. Мульти-модельные (когда SQL и NoSQL одновременно)

В реальной жизни данные редко бывают одного типа.

Современные приложения одновременно работают с:

· реляционными фактами

· полуструктурированным JSON

· связями между сущностями

Мульти-модельные базы позволяют запрашивать всё это в одном месте.

Вот типичный сценарий, когда один из столбцов хранит данные в jsonb:

			CREATE TABLE listings (
    listing_id BIGINT PRIMARY KEY,
    city TEXT,
    price NUMERIC,
    attributes JSONB
);

		

И вот пример запроса к такой таблице:

			SELECT
    city,
    COUNT(*) AS luxury_listings
FROM listings
WHERE price > 300
  AND attributes @> '{"amenities": ["pool", "wifi"]}'
  AND (attributes->>'pet_friendly')::boolean = true
GROUP BY city;

		

Как видно для аналитика это не совсем удобно. Поэтому (как в сценарии с data vault), для аналитиков лучше делать отдельные витрины, где данный json уже разложен на нужные столбцы.

Плюсы

· одна система для разных типов данных

· мощный SQL при гибкой схеме

· меньше ETL и копирования данных

Минусы

· сложнее управлять схемой

· планы запросов могут усложняться

· медленнее специализированных движков

2. Reverse ETL (операционная аналитика, возвращающая данные в приложения)

Классическая аналитика заканчивается дашбордами и отчетами.

Reverse ETL замыкает цикл: данные из хранилища отправляются обратно в операционные системы. Будь то скорректированные данные или какие-то посчитанные в DWH метрики, которые должны также храниться в информационной системе, что отображаться на сайте или еще где-нибудь.

Вот частые примеры использования:

· почти-реальная персонализация

· актуальные метрики здоровья клиента и оттока

· автоматизация продаж и маркетинга (CRM, ESP и т.д.)

1. Единый слой данных (наше будущее)

Вот обычная картина в средней компании: операционные данные хранятся в PostgreSQL, аналитические данные в Greenplum, стриминговые данные в Clickhouse, данные для поиска в Elasticsearch. Четыре системы, между которыми данные нужно синхронизировать и реплицировать.

Unified Serving Layer использует единый логический слой таблиц

Например, Iceberg / Hudi / Delta с разными способами доступа.

То есть, один набор данных, много движков, без ETL.

Данные лежат в одном месте, но читаются разными движками, предназначенными для разных задач.

Что это заменяет

· нет ETL (warehouse → lake → feature store)

· рассинхронизацию данных между системами

· повторную реализацию логики в SQL, Spark и приложениях

Пример таблицы в Iceberg

			CREATE TABLE user_events (
    user_id BIGINT,
    event_type STRING,
    event_ts TIMESTAMP,
    attributes MAP<STRING, STRING>
)
USING ICEBERG
PARTITIONED BY (days(event_ts));

		

OLAP-запрос (Trino / Spark SQL)

			SELECT
    event_type,
    COUNT(*) AS cnt
FROM user_events
WHERE event_ts >= current_date - INTERVAL '7' DAY
GROUP BY event_type;

		

Плюсы

· единый источник истины

· доступ из разных движков (SQL, ML, стриминг)

· ACID, time-travel, эволюция схемы

· отсутствие vendor lock-in

Минусы

· более сложная платформа

· требуется сильное управление метаданными и governance

· не является прямой заменой OLTP-БД (потому что с нагрузкой может не справиться).

Рекомендуем