Data engineering
ELT против ETL: где выполняется Transform и почему индустрия перевернулась
В понедельник тебе пишет финансовый аналитик: «Почему наш счёт за Snowflake на прошлой неделе подскочил на 40%?» Ничего не выкатывали — кроме изменения в пайплайне. Кто-то выставил dbt-модели full-refresh по умолчанию и поставил их по расписанию ежечасно. Каждый запуск пересканирует всю фактовую таблицу на 2 ТБ и пересобирает её с нуля. Трансформация корректна. Баг — это счёт. Это та самая «налоговая ставка ELT», о которой никто не предупредил: когда T делает склад, склад тарифицирует каждый байт, к которому ты прикоснулся.
Развилка пайплайна: где живёт T
Оба паттерна перемещают данные из источников в склад. Единственная реальная разница — порядок средней буквы, и эта одна перестановка меняет всё, что идёт дальше.
ETL (extract, transform, load) родился, когда storage и compute были одной дорогой коробкой. Ты не мог позволить себе сгружать всё в склад, поэтому трансформировал сначала — в отдельном движке (Informatica, кластер Spark/Hadoop, Python-джоб) — чистил, джойнил, фильтровал и агрегировал, а потом грузил только отполированный результат. Склад видел только готовую таблицу.
ELT (extract, load, transform) переставляет два последних шага. Ты грузишь сырые исходные данные в склад первыми, нетронутыми, а потом трансформируешь их на месте в SQL. Трансформация больше не отдельная система — это запросы, выполняющиеся на том же движке, что хранит данные. dbt — доминирующий инструмент для «T»: версионируемые, протестированные, документированные SQL-модели вместо чёрного ящика-пайплайна.
Причина переворота индустрии архитектурная, а не модная. Snowflake и BigQuery разделяют storage и compute. Storage — это дешёвое объектное хранилище; compute — отдельный эластичный ресурс, который ты поднимаешь только когда выполняется запрос. Это полностью ломает старое ограничение — ты можешь позволить себе посадить сырьё, потому что хранить его почти ничего не стоит, а за compute платишь только когда реально трансформируешь.
Почему ELT победил: сырьё реплейабельно
Самая глубокая причина, по которой ELT стал дефолтом, — не цена, а реплейабельность (возможность переиграть). В ETL сырые исходные данные выбрасываются после трансформации; склад держит только трансформированный вывод. Поэтому когда (не если) ты находишь баг в трансформации — неверный ключ джойна, ошибку таймзоны, валюту, которую надо было конвертировать, — ты не можешь починить историю. Сырой ввод исчез. Приходится переэкстрагировать из источника, а источник мог измениться, заблокировать тебя по rate-limit или больше не держать старые строки.
В ELT сырьё живёт в складе постоянно. Баг трансформации — это однострочный SQL-фикс плюс перезапуск по данным, которые у тебя уже есть. Никакого re-extract. Это сердце medallion-архитектуры: слой bronze из сырых, append-only загруженных данных; слой silver из очищенных, приведённых, дедуплицированных таблиц; слой gold из готовых к бизнесу агрегатов и витрин. Каждый слой пересобираем из нижнего, а bronze пересобираем из источника, только если уж совсем нужно. Контракт такой: никогда не мутируй bronze, всегда трансформируй вперёд.
| Измерение | ETL (трансформ до загрузки) | ELT (трансформ в складе) |
|---|---|---|
| Точность сырья | Потеряна — хранится только трансформированный вывод | Сохранена — слой bronze реплейабелен |
| Починка бага трансформации | Re-extract из источника (может исчезнуть) | Правишь SQL, перезапуск по имеющемуся сырью |
| Где тарифицируется compute | Отдельный движок (твой кластер) | Склад — каждый трансформ в счёте |
| Контроль PII / цены до загрузки | Сильный — снять/замаскировать до посадки | Слабый — сырьё (вкл. PII) садится первым |
| Дисциплина схемы | Schema-on-write (на загрузке) | Schema-on-read (ты обеспечиваешь в silver) |
Стоимость переезжает на счёт склада
ELT не делает трансформацию бесплатной — он переносит стоимость с CapEx-кластера, которым ты владел, на тарифицируемую OpEx-строку в счёте склада, и эта строка зверски чувствительна к тому, как ты пишешь SQL. Hook — каноничный провал: модель, материализованная как полная пересборка таблицы, поставленная часто по расписанию, сканирующая всё на каждом запуске. В Snowflake ты платишь за warehouse-секунду; в BigQuery — за просканированные байты. SELECT * по непартиционированной таблице на 2 ТБ, ежечасно, — это уже сам по себе четырёхзначный пункт месячного счёта.
Фикс — инкрементальные модели. Вместо пересборки всей таблицы ты обрабатываешь только новые или изменённые строки с прошлого запуска. В dbt это макрос is_incremental(), оборачивающий фильтр:
{% if is_incremental() %}
where event_time >= (select max(event_time) from {{ this }})
{% endif %}На первом запуске таблица строится полностью; на каждом следующем трогаются только строки новее текущего максимума. Ночной джоб, сканировавший 2 ТБ, теперь сканирует дельту за день — часто несколько ГБ — срезая и время выполнения, и тарифицируемые байты на один-два порядка. Дисциплина сеньора: инкрементально по умолчанию, full refresh только когда меняется логика (dbt run --full-refresh), и отдельный warehouse на команду, чтобы тяжёлый трансформ никогда не голодил BI-дашборды на общем compute. Команды, забывающие auto-suspend на простаивающих warehouse, сливают кредиты за compute, который ничего не делает.
Почему это работает
«Schema-on-read» звучит как свобода, но это отложенный счёт. Schema-on-write в ETL отвергает кривую строку на загрузке — ты узнаёшь сразу. ELT с радостью садит что угодно в bronze; нарушение контракта всплывает позже, в silver, часто как тихий NULL или неверный джойн. ELT не убирает работу со схемой — он сдвигает её ниже по потоку и делает твоей задачей обеспечить её в тестах, а не задачей загрузчика.
Идемпотентность: повтор, который удваивает твои данные
Провал, от которого люди просыпаются ночью, — дублирование данных. EL-инструменты вроде Fivetran и Airbyte, и твои собственные загрузчики, повторяют при временном сбое — это корректное поведение. Но если загрузка не идемпотентна, повтор, перезапускающий частично-успешный батч, вставляет те же строки дважды. Теперь твой итог по выручке завышен, и дашборду никто не верит.
Лекарство — сделать загрузки и инкрементальные трансформации идемпотентными: запустить дважды даёт тот же результат, что запустить один раз. В инкрементальных моделях dbt это стратегия merge с unique_key:
{{ config(materialized='incremental', incremental_strategy='merge', unique_key='event_id') }}На каждой строке merge обновляет, если unique_key уже существует, и вставляет, если нет — это upsert. Повтор того же батча обновляет строки на месте вместо дублирования. Ловушка: инкрементальная модель без unique_key и без фильтра is_incremental() тихо аппендит весь свой вывод на каждом запуске, поэтому она и дублирует, и пересканирует всё — худшее из двух миров. Microbatch-модели идут дальше, трактуя каждый временно́й батч как атомарную, независимо заменяемую единицу, поэтому упавший батч переигрывается чисто, не трогая соседей.
Регулируемый финтех загружает платёжные события с номерами карт (PAN) и PII. Комплаенс запрещает сырым данным держателя карты вообще оказываться в аналитическом складе. Выбери паттерн.
Ты находишь баг с таймзоной в трансформации, которая работала полгода. При ELT с medallion-архитектурой какой быстрый фикс?
Инкрементальная dbt-модель настроена без unique_key и без фильтра is_incremental(). Что происходит на каждом запуске по расписанию?
Расставь стадии современного ELT-пайплайна от источника до дашборда:
- 1 Extract + Load: EL-инструмент (Fivetran/Airbyte) копирует сырые исходные данные в склад
- 2 Bronze: сырые, append-only посаженные данные, никогда не мутируются
- 3 Silver: очищенные, дедуплицированные, приведённые таблицы (dbt-модели, схема обеспечивается здесь)
- 4 Gold: готовые к бизнесу агрегаты и витрины
- 5 BI / дашборды читают из gold
- 01Объясни, почему индустрия перешла от ETL к ELT и что ты отдал в этом обмене.
- 02Повтор продублировал строки в фактовой таблице и завысил выручку. Какой дизайн это бы предотвратил и почему?
ETL и ELT отличаются только тем, где выполняется Transform, и эта одна перестановка решает цену, реплейабельность и точность данных. ETL трансформирует в отдельном движке до загрузки и держит только отполированный вывод — сильно для снятия PII и контроля цены до посадки данных, но сырьё выброшено, поэтому баг трансформации означает re-extract из источника, которого может уже не быть. ELT грузит сырьё в склад первым и трансформирует в SQL (dbt), что облачное разделение storage и compute сделало дешёвым. Его решающее преимущество — реплейабельность через medallion-паттерн: неизменный сырой bronze, очищенный silver, готовый к бизнесу gold, каждый пересобираем из нижнего. Цена в том, что compute трансформа теперь тарифицируется на счёте склада — поэтому идёшь инкрементально (обрабатываешь только дельту, а не полную пересборку) и идемпотентно (merge по unique_key) или получаешь взрыв трат и дублирующиеся строки на повторе. Выбирай ELT по умолчанию; тянись к ETL, когда жёсткое правило говорит, что сырая PII не должна касаться склада.