Перейти к содержанию
Данные и AI

11 вопросов по теме «Data Engineering: ETL и пайплайны» на собеседовании

В этом материале — 11 вопросов из русской колоды RecallDeck по теме «Data Engineering: ETL и пайплайны». Сначала сформулируйте короткий ответ сами, затем откройте подробный разбор и проверьте примеры, ограничения и отказные случаи.

11 мин чтения11 подробных ответовПроверено 24 августа 2026
Главная мысль

Сначала зафиксируйте grain данных, допущения, метрику и риск leakage, затем обсуждайте модель, инструмент или инфраструктуру.

Вопросы и ответы

11 подробных ответов

01

В чём разница между ETL и ELT, почему ELT победил с приходом облачных хранилищ и когда ETL всё ещё оправдан?

Короткий ответ: ETL трансформирует данные до загрузки (на отдельном движке), ELT сначала грузит сырые данные в хранилище, а трансформации выполняет уже внутри него на SQL. ELT стал стандартом, потому что облачные MPP-хранилища (Snowflake, BigQuery, Redshift) дёшево масштабируют вычисления и разделяют storage/compute.

Подробно:

  1. ETL — трансформация на промежуточном движке (раньше — дорогие ETL-серверы). В хранилище попадают только готовые, «чистые» данные. Минус: логика заперта в инструменте, а сырьё теряется.
  2. ELT — грузим сырой слой как есть, трансформируем через dbt/SQL. Плюс: сырьё сохраняется, трансформации версионируются в Git, масштабирование берёт на себя хранилище.
  3. Почему ELT победил — разделение storage/compute сделало вычисления в хранилище дешёвыми и эластичными; аналитику стало удобнее держать в SQL.
Критерий ETL ELT
Где трансформация На отдельном движке Внутри хранилища
Сырой слой Обычно теряется Сохраняется
Масштабирование Ограничено движком Эластичное (MPP)
Типичный стек Informatica, SSIS Fivetran + dbt + Snowflake

Когда ETL всё ещё нужен: тяжёлые преобразования до попадания в хранилище, маскирование PII/комплаенс (нельзя грузить сырьё), или источник, который проще привести к схеме на лету.

⚠️ Частая ошибка: думать, что ELT — это «без трансформаций». Трансформации никуда не делись, они просто переехали в хранилище и выполняются после загрузки.

02

Пакетная (batch) и потоковая (streaming) обработка: какие компромиссы по задержке, стоимости и сложности и как выбрать?

Короткий ответ: Batch обрабатывает данные порциями по расписанию (задержка минуты–часы, просто и дёшево), streaming обрабатывает события по мере поступления (задержка секунды, но дороже и сложнее в эксплуатации). Выбор диктует требование к свежести данных, а не мода.

Подробно:

  1. Batch — читаем накопленный объём за интервал, гоняем через Airflow/dbt/Spark. Проще отлаживать, легко делать бэкфилл, дешевле. Задержка = размер окна.
  2. Streaming — Kafka + Flink/Spark Structured Streaming обрабатывают события непрерывно. Нужны для фрода, алертов, real-time дашбордов. Требуют работы с состоянием, водяными знаками для опоздавших событий, exactly-once.
  3. Микро-батч — компромисс: обработка маленькими окнами (секунды), проще стриминга при почти той же свежести.
Критерий Batch Streaming
Задержка Минуты–часы Секунды
Стоимость Ниже Выше (24/7)
Сложность Низкая Высокая (state, опоздавшие)
Бэкфилл Тривиальный Сложный
Когда Отчётность, ML-фичи Фрод, алерты, real-time

Как выбрать: начни с вопроса «насколько свежими должны быть данные для решения?». Если ответ «часа достаточно» — batch. Секунды и деньги на кону — streaming.

⚠️ Частая ошибка: строить стриминг, потому что «это современно», хотя бизнесу хватает часовой свежести — вы платите за сложность и деньги без ценности.

03

Что такое идемпотентность в пайплайнах, почему она критична и как сделать загрузку идемпотентной?

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

Подробно:

  1. Overwrite партиции — вместо INSERT целиком перезаписывай партицию за день: INSERT OVERWRITE PARTITION. Повтор просто затирает старое той же порцией.
  2. MERGE / upsert по ключу — сопоставляй по бизнес-ключу и обновляй, а не вставляй вслепую. Повтор обновит те же строки.
  3. Дедупликация по ключу — если источник даёт дубли, оставляй последнюю версию через ROW_NUMBER() OVER (PARTITION BY id ORDER BY updated_at DESC).
-- Идемпотентно: перезапись партиции за конкретную дату
INSERT OVERWRITE TABLE sales PARTITION (dt = '2026-06-30')
SELECT * FROM staging_sales WHERE dt = '2026-06-30';

-- Идемпотентно: upsert по бизнес-ключу
MERGE INTO dim_customer t
USING staging_customer s
ON t.customer_id = s.customer_id
WHEN MATCHED THEN UPDATE SET t.name = s.name, t.updated_at = s.updated_at
WHEN NOT MATCHED THEN INSERT (customer_id, name, updated_at)
  VALUES (s.customer_id, s.name, s.updated_at);

⚠️ Частая ошибка: слепой INSERT INTO ... SELECT без ключа или перезаписи партиции. При ретрае упавшего таска строки вставляются повторно — получаете тихие дубли, которые всплывают в отчётах как задвоенная выручка.

04

Какие есть стратегии инкрементальной загрузки: полная против инкрементальной, водяной знак и CDC?

Короткий ответ: Полная загрузка каждый раз перечитывает весь источник — просто, но не масштабируется. Инкрементальная забирает только новое/изменённое с прошлого прогона по водяному знаку (high-water mark), а CDC ловит сами изменения (insert/update/delete) из лога БД.

Подробно:

  1. Full loadTRUNCATE + перезалив. Работает на маленьких таблицах, честен по удалениям, но дорог и медленен на больших.
  2. Инкрементальная по водяному знаку — храним максимум updated_at/id прошлого прогона и берём строки строго больше него. Дёшево, но не видит физических DELETE.
  3. CDC — читаем redo/WAL-лог (Debezium, Fivetran) и применяем поток изменений, включая удаления. Ближе к real-time, но сложнее в эксплуатации.
Водяной знак (high-water mark):

  прошлый прогон: max(updated_at) = 2026-06-29 23:00


  SELECT * FROM src
  WHERE updated_at > '2026-06-29 23:00'   ← только дельта


  MERGE в целевую таблицу  →  новый watermark = 2026-06-30 23:00

⚠️ Частая ошибка: инкремент по updated_at, когда источник не проставляет его при всех изменениях (или использует нестрогое >=, ловя дубли на границе). Физические удаления так тоже теряются — для них нужен CDC или периодический full-reconcile.

05

Как безопасно сделать бэкфилл пайплайна: переобработка истории, партиционные бэкфиллы и как не задвоить данные?

Короткий ответ: Бэкфилл — это переобработка исторических периодов при изменении логики или заполнении пропусков. Делай его безопасно только через идемпотентную перезапись по партициям: каждый день пересчитывается независимо и затирает свою партицию, а не добавляет строки.

Подробно:

  1. Партиционируй по дате — таск должен принимать дату как параметр и трогать только партицию этого дня (INSERT OVERWRITE PARTITION (dt=...)). Тогда бэкфилл = прогон того же таска за прошлые даты.
  2. Идемпотентность обязательна — перезапись, а не INSERT. Иначе повторный прогон за уже посчитанный день удвоит данные.
  3. Контролируй нагрузку — ограничивай параллелизм (в Airflow max_active_runs), чтобы бэкфилл 2 лет не положил кластер и источник.
Партиционный бэкфилл (переобработка за диапазон дат):

  for dt in 2026-01-01 .. 2026-06-30:
      OVERWRITE partition(dt)  ← каждый день независим и идемпотентен

  ┌────────┬────────┬────────┬────────┐
  │ dt=1   │ dt=2   │ dt=3   │  ...   │   перезапись, не добавление
  └────────┴────────┴────────┴────────┘

⚠️ Частая ошибка: двойной счёт при бэкфилле — запустить пересчёт поверх уже загруженных данных через INSERT вместо OVERWRITE, либо агрегаты, которые суммируют старые и новые строки. Всегда переписывай партицию целиком.

06

Как устроена оркестрация в Airflow (DAG, таски, операторы, расписание) и чем он отличается от Dagster/Prefect?

Короткий ответ: Airflow описывает пайплайн как DAG — граф тасков с зависимостями; каждый таск создаётся оператором и выполняется по расписанию. Ключевой принцип — таски должны быть идемпотентными и ретраебельными, потому что Airflow их перезапускает при сбоях.

Подробно:

  1. DAG — направленный ацикличный граф: узлы это таски, рёбра — зависимости (a >> b). Планировщик запускает таск, когда выполнены его upstream-зависимости.
  2. Операторы — шаблоны тасков: PythonOperator, BashOperator, сенсоры, специфичные под сервисы. Каждый run привязан к logical_date (интервалу), что и делает бэкфилл возможным.
  3. Идемпотентность и ретраи — задаёшь retries; таск обязан безопасно перезапускаться, поэтому пишем через overwrite партиции.
from airflow import DAG
from airflow.operators.python import PythonOperator
import pendulum

with DAG(
    dag_id="daily_sales",
    schedule="@daily",
    start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
    catchup=True,          # позволяет бэкфилл прошлых интервалов
    default_args={"retries": 2},
) as dag:
    extract = PythonOperator(task_id="extract", python_callable=do_extract)
    load = PythonOperator(task_id="load", python_callable=do_load)
    extract >> load        # зависимость: load после extract

Airflow vs Dagster/Prefect: Airflow — зрелый стандарт, ориентирован на таски и расписание. Dagster мыслит ассетами данных и типизацией/тестами; Prefect делает упор на динамические потоки и удобный Python-API. Для новых проектов часто берут Dagster ради data-assets, но Airflow остаётся дефолтом по экосистеме.

⚠️ Частая ошибка: таск, который нельзя перезапустить (например, дописывает в файл или шлёт письмо на каждый run). При первом же ретрае получаете дубли или спам.

07

Что даёт dbt как слой трансформаций в ELT: модели, ref, тесты, материализации — и почему его любят аналитики и инженеры?

Короткий ответ: dbt — это слой T в ELT: ты пишешь трансформации как SQL-SELECT-модели, а dbt строит из них граф зависимостей через ref(), материализует результат в хранилище и прогоняет тесты. Любят за то, что аналитика превращается в версионируемый, тестируемый, документированный код.

Подробно:

  1. Модели и ref — каждая модель это SELECT; ссылки через {{ ref('stg_orders') }} дают dbt DAG и порядок запуска. Никаких ручных CREATE TABLE.
  2. Материализацииview (лёгкая, всегда свежая), table (быстрое чтение), incremental (досчитывает только новое по watermark), ephemeral (CTE). Выбор — это баланс цены пересчёта и скорости чтения.
  3. Тесты и документацияnot_null, unique, relationships прямо в YAML; плюс автогенерация документации и lineage.
# models/marts/orders.yml
models:
  - name: orders
    config:
      materialized: incremental
      unique_key: order_id      # апсерт по ключу при инкременте
    columns:
      - name: order_id
        tests: [unique, not_null]
      - name: customer_id
        tests:
          - relationships:
              to: ref('customers')
              field: customer_id
Материализация Когда
view Лёгкие трансформации, нужна свежесть
table Тяжёлое чтение, частые запросы
incremental Большие таблицы фактов, только дельта

⚠️ Частая ошибка: делать table там, где хватает view, и наоборот гонять incremental без корректного unique_key — тогда инкремент задваивает строки при повторной обработке.

08

Как спроектировать надёжный DAG пайплайна: зависимости, ретраи, алертинг, атомарность и почему нельзя делать один гигантский таск?

Короткий ответ: Хороший DAG разбит на маленькие атомарные идемпотентные таски с явными зависимостями, у каждого — ретраи и алерты. Монолитный таск — антипаттерн: при падении на 90% приходится перезапускать всё с нуля, а точки сбоя не видно.

Подробно:

  1. Дроби на атомарные таски — extract, load, transform, quality-check раздельно. Падение на transform не заставляет заново тянуть extract; ретраи прицельные.
  2. Явные зависимости — рёбра графа отражают реальный порядок данных; никаких скрытых связей через «сработает по времени».
  3. Атомарность записи — таск либо полностью применил результат (overwrite партиции), либо ничего. Никаких полузаписанных состояний.
  4. Ретраи + алертинг — экспоненциальный backoff на транзиентные сбои; алерт в Slack/PagerDuty на финальный fail и на SLA-мисс.
Плохо (монолит)          Хорошо (атомарные таски)
┌──────────────┐         extract ─► load ─► transform ─► test ─► publish
│ do_everything│              │        │         │          │
│  (падает на  │           retry    retry     retry      alert on fail
│   90% → всё  │
│   заново)    │         падение на transform ⇒ ретраится только transform
└──────────────┘

⚠️ Частая ошибка: один «сделай-всё» таск на 500 строк без промежуточных состояний. Любой сбой = полный перезапуск, невозможно понять, где именно сломалось, и нельзя переиспользовать шаги.

09

Планирование и триггеры пайплайнов: cron/интервал против событий и сенсоров, и как обрабатывать опоздавшие данные?

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

Подробно:

  1. Cron / интервал — «каждый день в 03:00». Плюс: предсказуемость и простота. Минус: может стартовать раньше, чем данные реально готовы.
  2. Событийные / сенсоры — таск ждёт триггер: прилетел файл в S3, сообщение в очереди, завершился upstream-DAG. Точнее по готовности, но требует управлять таймаутами и «завис навсегда».
  3. Опоздавшие данные (late-arriving) — данные за вчера пришли сегодня. Решения: идемпотентный перезапуск вчерашней партиции, скользящее окно пересчёта (последние N дней), или водяной знак в стриминге, допускающий opоздание.
Тип запуска Плюсы Минусы
Cron/интервал Просто, предсказуемо Данные могут быть не готовы
Сенсор/событие По факту готовности Таймауты, сложнее отладка

⚠️ Частая ошибка: жёсткий cron без проверки готовности upstream — таск стартует по времени, читает половину данных и тихо выдаёт неполный результат. Либо сенсор без таймаута, который висит и держит слот планировщика.

10

Гарантии доставки в пайплайне: at-least-once против exactly-once и как достичь effectively-once через идемпотентные записи?

Короткий ответ: At-least-once гарантирует, что событие не потеряется, но допускает дубли при ретраях; exactly-once — ровно одна обработка, но дорого и не всегда достижимо в распределёнке. На практике целятся в effectively-once: at-least-once доставка плюс идемпотентная запись, дающая эффект единственной обработки.

Подробно:

  1. At-most-once — максимум одна попытка, при сбое событие теряется. Годится, только если потери не критичны.
  2. At-least-once — ретраим до успеха, потерь нет, но возможны дубли. Дефолт большинства очередей (Kafka, SQS).
  3. Exactly-once — ровно один раз end-to-end. Дорого: нужны транзакции, идемпотентные продюсеры, координация. В чистом виде редко нужен.
  4. Effectively-once — принимаем дубли на доставке, но гасим их на записи: upsert/MERGE по ключу, дедуп по event_id, overwrite партиции. Результат неотличим от exactly-once.
Гарантия Потери Дубли Цена
At-most-once Да Нет Низкая
At-least-once Нет Да Средняя
Exactly-once Нет Нет Высокая
Effectively-once Нет Гасятся на записи Средняя

⚠️ Частая ошибка: обещать «exactly-once», а на деле иметь at-least-once с неидемпотентной записью — дубли из очереди спокойно долетают до витрины и раздувают метрики.

11

Как устроена слоистая архитектура пайплайна (raw/staging/curated, или bronze/silver/gold) и зачем хранить неизменяемый сырой слой?

Короткий ответ: Данные проходят слои: raw/bronze — сырьё как из источника, staging/silver — очищенное и типизированное, curated/gold — бизнес-витрины для потребителей. Неизменяемый сырой слой держат, чтобы можно было переиграть любую трансформацию заново, не обращаясь повторно к источнику.

Подробно:

  1. Bronze (raw) — данные как есть, append-only, без изменений. Это ваша «точка правды» и страховка.
  2. Silver (staging) — очистка, приведение типов, дедупликация, стандартизация ключей. Здесь исправляют качество.
  3. Gold (curated) — агрегаты и витрины под конкретные метрики/дашборды. То, что видит бизнес.
  Источник


  ┌─────────┐   очистка,    ┌─────────┐  агрегация,  ┌────────┐
  │ BRONZE  │─ типизация ──►│ SILVER  │─ бизнес- ───►│  GOLD  │──► BI / ML
  │  (raw,  │   дедуп       │(staging,│   логика     │(витрины│
  │ immutab)│               │ чистый) │              │ метрик)│
  └─────────┘               └─────────┘              └────────┘
   переиграть всё заново можно с bronze, не дёргая источник

Зачем immutable raw: источник может измениться или стать недоступен; при новой ошибке в логике вы пересчитываете silver/gold из bronze. Плюс аудит и воспроизводимость.

⚠️ Частая ошибка: грузить сразу в curated без сырого слоя. Нашли баг в трансформации — переиграть нечем, исторические данные из источника уже не достать, и приходится жить с испорченными витринами.

Источники

Источники и редакционная политика

Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.

От чтения к воспроизведению

Отрепетируйте полный цикл интервью.

RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.

Начать подготовку

Продолжить подготовку

Библиотека собеседований RecallDeck

Подробные русские ответы, разборы этапов найма и планы подготовки для российского IT-рынка.

RSS