Собесов

DataLearn DE-101: ETL vs ELT — когда что предпочесть

Кейсы и метрикиETL/ELTЛёгкаяJunior

Условие

Объясните разницу между ETL (Extract → Transform → Load) и ELT (Extract → Load → Transform). Почему в эпоху облачных DWH чаще выбирают ELT? Приведите два use case, где ETL остаётся правильным выбором.

Решение

Подход

ETL (классика 2000-х): данные забирают из источника, трансформируют на промежуточном сервере (Informatica, SSIS, Talend, Python-скрипт) и загружают уже очищенные витрины в DWH. Сырьё в DWH не попадает.

ELT (современность с 2015+): данные забирают и сырьём загружают в DWH (Snowflake, BigQuery, Redshift). Все трансформации делают SQL'ом внутри DWH (часто через dbt). Сырьё остаётся доступным.

Почему ELT победил

  1. Дешёвое хранение в облаке (S3, GCS) — не страшно хранить сырьё.
  2. Эластичный compute у MPP-DWH — трансформации на 10 ТБ выполняются за минуты.
  3. Аналитики знают SQL — не нужен отдельный python/scala-инженер на каждый pipeline.
  4. dbt + git — code review, тесты, документация трансформаций «из коробки».
  5. Время backfill: можно перезапустить только модель, а не весь пайплайн.

Когда ETL всё ещё уместен

  1. Compliance / PII: нельзя загружать персональные данные в DWH в сыром виде (GDPR, 152-ФЗ). Маскируем/хэшируем до загрузки.
  2. Очень большие объёмы при ограниченном бюджете DWH: миллионы JSON в час — дешевле сжать/агрегировать в Spark и положить в DWH уже свёрнутыми.
  3. Real-time pipeline: Kafka → Flink → ClickHouse (трансформации в потоке, в DWH летят готовые события).
  4. Источник с rate-limit API: имеет смысл сразу нормализовать и не хранить «сырьё» (которое всё равно недоступно для перезапроса).

Пример ELT с dbt

-- models/staging/stg_orders.sql
{{ config(materialized='view') }}
 
SELECT
    id            AS order_id,
    user_id,
    created_at::timestamp AS created_at,
    amount::numeric(18, 2) AS amount,
    status
FROM {{ source('raw', 'orders') }}
WHERE created_at >= '2024-01-01';
-- models/marts/fct_orders_daily.sql
{{ config(materialized='incremental', unique_key='dt') }}
 
SELECT
    created_at::date AS dt,
    COUNT(*)         AS n_orders,
    SUM(amount)      AS gmv,
    COUNT(DISTINCT user_id) AS dau
FROM {{ ref('stg_orders') }}
{% if is_incremental() %}
WHERE created_at::date >= (SELECT MAX(dt) FROM {{ this }}) - INTERVAL '3 day'
{% endif %}
GROUP BY 1;

Подводные камни

  1. ELT без quality-тестов = «мусор на входе, мусор на выходе». В dbt обязательны unique, not_null, accepted_values, relationships.
  2. Cost runaway: некомпетентный аналитик может вкинуть SELECT * по 10 ТБ — счёт за день вырастет в 10×.
  3. PII в Bronze: если в сырьё попали паспорта/телефоны без маскирования — это нарушение. Делать masking views поверх.
  4. dbt incremental + late-arriving data: при unique_key без merge строки задним числом не обновятся.
  5. «ELT = всегда лучше» — нет. Для очень больших batch-нагрузок Spark по-прежнему дешевле.
  6. Backfill в incremental-модели dbt: требует --full-refresh или ручного DELETE + ре-run.

Эталонный ответ

ELT — сначала загрузка сырья в DWH, потом трансформации SQL'ом (dbt). Выигрывает за счёт дешёвого хранения, эластичного compute и SQL-навыков аналитиков.

ETL остаётся уместным при: (1) compliance с PII — маскирование до загрузки; (2) очень больших объёмах с дорогим DWH — агрегируем в Spark; (3) real-time потоках; (4) скудных источниках без re-fetch.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти