Все материалы
Продуктовая аналитикаматериалстарт

ETL: что это простыми словами, этапы и чем ETL отличается от ELT

ETL — процесс, который забирает данные из источников, приводит их в порядок и загружает в хранилище. Этапы extract, transform, load на примере продукта, разница ETL и ELT, инкрементальная загрузка, идемпотентность и SQL-проверки качества данных.

КейсПрактика24 сентября 2026 г.10 мин

Понедельник, утро. Продакт присылает скриншот дашборда: в воскресенье 30 августа активных пользователей 223, а неделей раньше, тоже в воскресенье, было 495. Продукт упал? Нет. Выгрузку закрыли в 13:00, и на дашборд приехала только первая половина дня. Такие вопросы аналитик разбирает постоянно, и часто ответ лежит не в продукте, а в конвейере, который возит данные. ETL (extract, transform, load) — это процесс, который забирает данные из источников, приводит их к единому виду и загружает в хранилище, откуда их читают отчёты и дашборды.

Коротко

  • ETL — три шага: извлечь данные из источников, преобразовать их и загрузить в хранилище.
  • В ELT порядок другой: сырые данные сначала загружают в хранилище, а преобразуют уже там, обычно SQL-запросами.
  • Большинство конвейеров грузит данные не целиком, а приращениями: только то, что появилось после прошлого запуска.
  • Хороший конвейер идемпотентен: повторный запуск за тот же период не создаёт дублей.
  • После каждой загрузки нужны проверки: свежесть, объём, пустые поля, дубли, «осиротевшие» строки.
  • Если метрика резко изменилась в последний день, сначала проверьте, закончился ли этот день в данных.

Что такое ETL простыми словами

Данные о продукте рождаются в разных местах. Приложение пишет события: пользователь открыл экран, создал отчёт, отправил приглашение. Биллинг хранит оплаты. CRM знает, из какой компании клиент. Каждая система хранит данные так, как удобно ей, и ни одна не собиралась отвечать на вопрос «сколько выручки принёс канал за август».

ETL-процесс собирает эти данные в одно место и делает их пригодными для анализа. Extract — извлечь: забрать новые записи из базы приложения, API биллинга, файлов. Transform — преобразовать: привести типы и часовые пояса, убрать дубли, склеить оплату с пользователем, посчитать дневные показатели. Load — загрузить результат в аналитическое хранилище.

Слово «ETL» используют в двух смыслах. В узком — это именно порядок шагов, где преобразование идёт до загрузки. В широком — любой конвейер данных: «ETL упал ночью» обычно значит «данные не доехали», как бы ни был устроен процесс внутри.

Этапы ETL на примере продуктовой аналитики
ЭтапЧто происходитПример
ExtractЗабираем новые данные из источниковсобытия приложения за прошедший час, оплаты из биллинга за день
TransformЧистим, приводим к единому виду, соединяемвремя в одном часовом поясе, дубли событий удалены, у оплаты есть канал пользователя
LoadЗаписываем в хранилищетаблицы events, payments, дневная витрина с DAU и выручкой
ПотреблениеЧитаем готовые таблицыдашборд, отчёт для продакта, запрос аналитика

Чем ETL отличается от ELT

В ELT шаги переставлены: extract, load, transform. Сырые данные загружают в хранилище почти как есть, а чистят и соединяют уже внутри него. Преобразования пишут как SQL-модели: одна модель читает сырые события и убирает дубли, следующая строит из неё дневные показатели. Так работает, например, dbt.

ELT стал распространённым вместе с облачными хранилищами. Когда хранилище умеет быстро считать большие объёмы и его мощность можно нарастить, выгоднее один раз загрузить сырые данные и пересчитывать из них всё остальное. Если в логике нашли ошибку, модель исправляют и пересобирают витрины из сырья, не выгружая данные из источников заново.

У классического ETL остаются свои причины. Если в сырых данных есть то, что нельзя хранить в аналитике, например лишние персональные данные, их вычищают до загрузки. Если данных очень много, а нужна небольшая агрегированная часть, возить всё сырьё бессмысленно. На практике многие конвейеры смешанные: лёгкая очистка на входе, основная логика в хранилище.

Что сравниваемETLELT
Порядокизвлечь → преобразовать → загрузитьизвлечь → загрузить → преобразовать
Где идёт преобразованиевне хранилища, в отдельном процессевнутри хранилища, обычно на SQL
Сырые данные в хранилищечасто нет, только результатда, их можно пересчитать заново
Исправить ошибку в логикеперевыгрузить данные из источникапересобрать модели из уже загруженного сырья
Когда уместенчувствительные данные, большие объёмы при малом нужном срезеаналитическое хранилище с запасом мощности, частые изменения логики

Пакетная загрузка и потоковая: когда данные нужны сразу

Пакетная загрузка (batch) запускается по расписанию: раз в сутки, раз в час. Она проще в поддержке и подходит для большинства продуктовых отчётов: дневной DAU, недельный retention, выручка за месяц.

Потоковая обработка (streaming) обрабатывает события по мере поступления, с задержкой в секунды. Она нужна там, где решение принимают сразу: антифрод, мониторинг ошибок оплаты, рекомендации в текущей сессии. Цена — сложнее инфраструктура и сложнее исправлять ошибки задним числом.

Практический вопрос звучит так: что изменится, если цифра появится через час, а не через минуту? Если ничего, пакетной загрузки достаточно.

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

Перезаливать всю историю при каждом запуске дорого, поэтому конвейер запоминает, докуда он дочитал. Эту отметку называют водяным знаком (watermark): чаще всего это максимальное updated_at или время события, которое уже загружено. Следующий запуск забирает только записи новее отметки.

В учебной базе SQL-курса последнее событие за 29 августа случилось в 22:59. Если прошлый запуск остановился на отметке 23:00, следующий заберёт 226 событий — всё, что пришло 30 августа до закрытия выгрузки. Запрос ниже показывает, что произойдёт, если этот запуск по ошибке повторить и дописать пачку ещё раз.

Пачка после водяного знака, загруженная дважды
with batch as (
  -- всё, что новее отметки прошлого запуска
  select *
  from events
  where event_time > timestamp '2026-08-29 23:00'
),
loaded_twice as (
  -- запуск повторили, и пачку дописали второй раз
  select * from batch
  union all
  select * from batch
)
select
  count(*) as rows_loaded,
  count(distinct event_id) as unique_events
from loaded_twice;
Результат: 452 строки на 226 событий

DAU по distinct user_id такой дубль не заметит, а число событий, выручка и конверсии «на событие» удвоятся за этот день. Поэтому загрузку делают идемпотентной: повторный запуск за тот же период даёт тот же результат, а не добавляет строки.

Что такое идемпотентность и как её добиваются

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

Два самых частых приёма. Первый — перезапись периода: удалить из целевой таблицы все строки за день и вставить их заново. Второй — слияние по ключу (merge или insert ... on conflict): строка с уже известным event_id обновляется, а не дублируется. Оба требуют стабильного ключа. Если у события нет надёжного идентификатора, дубли приходится ловить по логическому ключу, например по пользователю, названию события и времени.

Отдельная ловушка — опоздавшие данные. Мобильное приложение может отправить событие через час после того, как оно случилось, и его event_time окажется раньше водяного знака. Конвейер, который читает строго «новее отметки», такое событие потеряет. Поэтому окно чтения обычно берут с запасом, например перечитывают последние сутки, а дубли убирают по ключу.

Какие проверки качества данных ставят после загрузки

Конвейер может отработать без ошибок и при этом привезти неправильные данные. Поэтому после загрузки запускают проверки. Первая — свежесть: до какого момента дошла каждая таблица.

Результат на учебной базе (DuckDB)
sourcelast_record
users2026-08-29
events2026-08-30 12:59:00
payments2026-08-30
subscriptions2026-08-30
Свежесть: последняя запись в каждой таблице
select 'users' as source, max(signup_date)::varchar as last_record from users
union all
select 'events', max(event_time)::varchar from events
union all
select 'payments', max(paid_at)::varchar from payments
union all
select 'subscriptions', max(started_at)::varchar from subscriptions;

Объём, дубли и пустые поля: ещё две проверки

Вторая проверка — объём. Сравните число строк за день с обычным уровнем: если пришло в три раза меньше, почти наверняка что-то не догрузилось. Здесь базой служит среднее за семь предыдущих дней.

dayeventsavg_prev_7pct_of_baseline
2026-08-26672586114,7
2026-08-27655592110,7
2026-08-28690600114,9
2026-08-2958861096,4
2026-08-3022661936,5
Объём событий за день против среднего за прошлую неделю
with daily as (
  select event_time::date as day, count(*) as events
  from events
  group by 1
),
with_baseline as (
  select
    day,
    events,
    avg(events) over (order by day rows between 7 preceding and 1 preceding) as avg_prev_7
  from daily
)
select
  day,
  events,
  round(avg_prev_7) as avg_prev_7,
  round(100.0 * events / avg_prev_7, 1) as pct_of_baseline
from with_baseline
where day >= date '2026-08-26'
order by day;

Набор правил в одном запросе

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

На учебной базе пять правил из шести проходят. У 55 пользователей из 4613 не заполнена страна. Это не обязательно ошибка конвейера: страну могли не определить при регистрации. Но теперь это известно заранее, и разрез по странам не удивит пустой строкой в отчёте.

check_nameproblem_rowsstatus
events: дубли логических событий0ok
events: пустой user_id или event_time0ok
events: событие раньше регистрации0ok
payments: оплата без пользователя0ok
payments: сумма не больше нуля0ok
users: пустая страна55проверить
Проверки качества после загрузки
with checks as (
  select 'events: дубли логических событий' as check_name,
         count(*) - count(distinct (user_id, event_name, event_time)) as problem_rows
  from events
  union all
  select 'events: пустой user_id или event_time',
         count(*) filter (where user_id is null or event_time is null)
  from events
  union all
  select 'events: событие раньше регистрации', count(*)
  from events as e
  join users as u on u.user_id = e.user_id
  where e.event_time::date < u.signup_date
  union all
  select 'payments: оплата без пользователя', count(*)
  from payments as p
  left join users as u on u.user_id = p.user_id
  where u.user_id is null
  union all
  select 'payments: сумма не больше нуля', count(*) filter (where amount <= 0)
  from payments
  union all
  select 'users: пустая страна', count(*) filter (where country is null)
  from users
)
select check_name, problem_rows,
       case when problem_rows = 0 then 'ok' else 'проверить' end as status
from checks;

Где аналитик встречается с ETL: сломанный дашборд

Вернёмся к воскресенью 30 августа. На графике DAU выглядит как обвал: 530 в субботу, 223 в воскресенье. Проверка объёма уже подсказала причину: событий за день пришло 36,5% от обычного уровня. Последнее событие — 12:59.

Чтобы убедиться, сравните одинаковые окна. В воскресенье 23 августа до 13:00 было 249 событий и 231 активный пользователь, 30 августа за то же время — 226 событий и 223 пользователя. Половина дня идёт вровень с прошлой неделей, обвала нет, есть недогруженный день.

Вторая деталь видна в проверке свежести. Регистрации в таблице users заканчиваются 29 августа, а события и оплаты есть и за 30-е. Дашборд, который соединит эти таблицы по дате, покажет за 30 августа ноль регистраций. По одной базе нельзя отличить «никто не зарегистрировался» от «регистрации ещё не доехали»: об этом говорит только время последней загрузки каждого источника.

DAU по дням, 23–30 августа 2026

Учебная база SQL-курса, DuckDB. * 30 августа — неполный день: выгрузка закрыта в 13:00.

DAU
  • Цифра упала только за последний день — проверьте свежесть таблицы и время последнего события.
  • Цифра удвоилась за один день — ищите повторную загрузку и дубли по ключу.
  • Одна метрика обнулилась, остальные живы — источник этой метрики не обновился или сменилось название события.
  • Цифры за прошлые дни изменились задним числом — конвейер пересчитал период или догрузил опоздавшие данные. Это нормально, если об этом знают.

Какие инструменты используют для ETL

Инструменты делятся по задачам, а не по буквам в аббревиатуре. Оркестратор запускает шаги по расписанию, следит за зависимостями и повторяет упавшие задания: самый известный из открытых — Apache Airflow. Коннекторы забирают данные из баз и сервисов в хранилище. Инструменты преобразований вроде dbt превращают SQL-запросы в модели с зависимостями, тестами и документацией. Для очень больших объёмов используют распределённые движки, например Spark, для потоков — брокеры сообщений вроде Kafka.

Для небольшой команды всё это может заменить набор SQL-скриптов по расписанию. Инструмент важен меньше, чем три привычки: загрузка идемпотентна, проверки запускаются после каждого прогона, а у каждой таблицы известны источник и время последнего обновления.

Что читать дальше

Куда ETL загружает данные и как из них собирают витрины, разобрано в соседнем материале. Проверки и дедупликацию можно отработать на той же учебной базе.

Продолжить чтение
Вся библиотека
Продуктовая аналитика24 сентября 2026 г.10 мин
Слои полупрозрачных плит один над другим, верхняя разделена на аккуратные ячейки

DWH, витрина данных и data lake: что это и чем они отличаются

DWH — хранилище данных для анализа, витрина — готовая таблица под конкретную задачу, data lake — хранилище сырых файлов. Чем DWH отличается от базы приложения, из каких слоёв состоит, как выглядит витрина на SQL и почему в двух витринах бывают разные цифры.

Читать материал
Продуктовая аналитика24 сентября 2026 г.10 мин
Горизонтальные полосы-строки слева и вертикальные столбцы справа, между ними поток точек

OLAP и OLTP: в чём разница простыми словами

OLTP — системы, которые обслуживают работу продукта мелкими транзакциями, OLAP — системы для тяжёлых аналитических запросов. Чем они отличаются по запросам, схеме и хранению, почему отчёты не строят на рабочей базе и что такое OLAP-куб.

Читать материал
Продуктовая аналитика24 августа 2026 г.13 мин

Дубликаты в SQL: как найти и убрать дубли событий, не потеряв реальные действия

Как отличить повторную доставку события от повторного действия пользователя, выбрать ключ дедупликации и окно, оставить одну строку через ROW_NUMBER и проверить, что после очистки не исчезли настоящие заказы.

Читать материал