# Почему 87% Big Data проектов проваливаются и как этого избежать

Источник: https://www.youtube.com/watch?v=PHsC_t0j1dU
Канал: freeCodeCamp.org
Опубликовано: 16.01.2024

---

Почти 87% проектов в сфере Big Data терпят крах из-за ненадежной инфраструктуры, превращая амбициозные задачи в кладбища данных. Чтобы не попасть в эту статистику, современный дата-инженер должен пройти путь от ручной сборки Docker-контейнеров до оркестрации пайплайнов в Airflow и dbt. Это руководство по превращению хаоса разрозненных SQL-запросов в масштабируемую систему, где даже ошибки становятся фундаментом для надежного кода.

## 🛠 Введение в Data Engineering и экосистему Docker
[[JUMP:00:00]]

### Фундамент данных: почему индустрии нужны инженеры, а не только аналитики
[[JUMP:01:06]]

Современный мир больших данных сталкивается с парадоксальной ситуацией: несмотря на обилие инструментов, от 85% до 87% Big Data-проектов заканчиваются провалом [01:19]. Как отмечает эксперт Airbyte Джастин Чоу, основная причина кроется не в отсутствии алгоритмов, а в ненадежной инфраструктуре и низком качестве данных [01:19]. Долгое время компании ожидали, что обязанности по созданию инфраструктуры возьмут на себя дата-сайентисты, однако это привело к некорректному моделированию, избыточности работы и колоссальной текучке кадров среди специалистов по данным [01:33]. Именно этот инфраструктурный вакуум и призвана заполнить роль дата-инженера [01:46].

Спрос на специалистов, способных обеспечить бесперебойный поток качественных данных, отражается и на уровне компенсации: в США медианная зарплата дата-инженера варьируется от 90 до 150 тысяч долларов в год [02:05]. Помимо финансовой стороны, роль инженера становится критической в эпоху расцвета генеративного ИИ и LLM, таких как ChatGPT [02:25]. Дата-инженер — это тот, кто гарантирует, что данные для обучения и работы моделей будут:

*   доступны вовремя и без перебоев [03:04];
*   безопасны и защищены [03:04];
*   отформатированы именно так, как того требуют команды аналитиков и исследователей [03:19].

По сути, дата-инженер создает кровеносную систему для бизнеса, позволяя компании принимать решения на основе фактов, а не интуиции, что напрямую влияет на её конкурентоспособность [03:31].

### Docker как стандарт индустрии: образы, контейнеры и решение проблемы «на моей машине работает»
[[JUMP:03:45]]

Первым и фундаментальным инструментом в арсенале дата-инженера является Docker — платформа с открытым исходным кодом, которая радикально упрощает процесс сборки и запуска приложений [03:58]. Главная проблема, которую решает Docker, — это классическая дилемма «на моей машине всё работает, а у коллеги — нет» [04:10]. Вместо того чтобы вручную настраивать окружение, инженер упаковывает приложение вместе со всеми его зависимостями, библиотеками и конфигурациями в изолированный контейнер [04:23].

Для глубокого понимания технологии важно различать три ключевых концепта:

1.  **Dockerfile**: текстовый файл-инструкция или «чертеж», по которому Docker понимает, как именно нужно собрать приложение [06:18].
2.  **Docker Image (Образ)**: исполняемый пакет, созданный на основе Dockerfile. Он включает в себя код, среду выполнения, системные инструменты и настройки [06:32]. Важнейшая характеристика образа — его иммутабельность (неизменяемость) [06:44]. Если вам нужно внести изменения, вы не меняете текущий образ, а создаете новый на основе обновленного Dockerfile [06:57].
3.  **Docker Container (Контейнер)**: это запущенный экземпляр образа [07:05]. Если образ — это чертеж здания, то контейнер — это само здание, в котором можно жить (запускать процессы) [07:05].

Контейнеры полностью изолированы как от хост-системы, так и друг от друга [07:11]. Это позволяет, например, запустить три разных экземпляра одного и того же приложения на одной машине: вы можете остановить или удалить один контейнер, и это никак не затронет остальные [07:37]. Такая легковесная виртуализация обеспечивает воспроизводимость окружения на любом этапе — от локальной разработки до облачных серверов AWS или GCP [05:28].

### Жизненный цикл контейнера: от написания Dockerfile до запуска локального приложения
[[JUMP:08:56]]

Процесс контейнеризации приложения начинается с подготовки рабочего окружения. Для работы необходим Docker Desktop, который уже включает в себя Docker Compose (инструмент, необходимый для более сложных задач, которые будут рассмотрены позже) [08:15]. Практический процесс сборки можно разобрать на примере простого Node.js приложения [10:12]. 

В корневой директории проекта создается Dockerfile, содержимое которого читается почти как обычный английский текст, но требует строгого порядка команд [11:15]. Типичный набор инструкций включает:

*   `FROM`: выбор базового образа (например, `node:18`) [11:02];
*   `WORKDIR`: определение рабочей директории внутри контейнера [11:02];
*   `COPY` и `RUN`: копирование файлов и установка необходимых зависимостей (например, через `yarn install`) [11:15];
*   `EXPOSE`: указание порта, который будет использовать приложение (например, 3000) [11:15];
*   `CMD`: финальная команда для запуска приложения [11:15].

После написания инструкций выполняется сборка образа командой `docker build -t [имя_образа] .`, где флаг `-t` задает тег (имя), а точка указывает на текущую директорию [11:39]. Когда образ готов, он появляется в списке доступных в системе, и его можно запустить в работу [12:19].

Запуск контейнера осуществляется командой `docker run`. Важно использовать флаг `-d` (detached mode), чтобы приложение работало в фоновом режиме, и флаг `-p`, который пробрасывает порты [12:46]. Например, маппинг `127.0.0.1:3000:3000` позволяет обращаться к приложению внутри контейнера через локальный браузер по адресу localhost:3000 [13:10]. Если код приложения меняется, цикл повторяется: старый контейнер удаляется (командой `docker rm -f [id]`), образ пересобирается, и запускается новый контейнер [16:30]. Ранее в разговоре Джастин упоминал, что в реальных проектах данные должны сохраняться даже после удаления контейнера, но детально механизмы персистентности и сетевого взаимодействия между несколькими контейнерами (например, приложением и базой данных SQL) будут разобраны в следующих частях курса [21:17].

## 🛠️ Оркестрация и персистентность: работа с Docker Compose

[[JUMP:25:08]]

### Магия Docker Compose: один конфиг для всей системы
[[JUMP:25:49]]

Работа с отдельными контейнерами вручную требует ввода длинных команд в терминале, что становится неудобным при усложнении архитектуры [26:16]. Для решения этой проблемы используется Docker Compose — инструмент, позволяющий описывать и запускать многоконтейнерные приложения с помощью одного конфигурационного файла [26:03]. Вместо того чтобы запускать отдельно базу данных и отдельно приложение, разработчик создает файл `compose.yaml` (или `docker-compose.yml`), который автоматизирует весь процесс [26:28].

При написании Compose-файла критически важна структура и отступы, так как формат YAML крайне строг к синтаксису [26:40]. Основные элементы конфигурации включают в себя:

*   **Services (Сервисы):** Определение контейнеров, которые будут запущены. В рассматриваемом примере это сервис `app` на базе Node.js 18 и сервис `mysql` восьмой версии [26:53, 27:59].
*   **Command:** Инструкция, которую должен выполнить контейнер при старте, например, `yarn install && yarn run dev` для запуска среды разработки [27:06].
*   **Ports:** Сопоставление портов хоста и контейнера. В учебном примере приложение открывается на порту 3000 [27:20].
*   **Environment (Переменные окружения):** Параметры для подключения к базе данных, такие как хост, имя пользователя, пароль и название БД [27:46].

Docker Compose значительно упрощает сетевое взаимодействие: контейнеры, описанные в одном файле, по умолчанию находятся в одной сети и могут легко обращаться друг к другу [25:49]. Для запуска всей инфраструктуры достаточно выполнить команду `docker compose up`, а добавление флага `-d` (detached mode) позволит запустить процессы в фоновом режиме, не блокируя терминал [29:17]. Если же работу нужно прекратить, команда `docker compose down` не только остановит, но и корректно удалит созданные контейнеры [30:12].

### Персистентность данных: как не потерять базу при перезапуске
[[JUMP:25:21]]

Одной из главных проблем контейнеризации является эфемерность: по умолчанию все данные внутри контейнера стираются после его удаления. Чтобы данные в базе данных MySQL или Postgres сохранялись, используются Docker-тома (volumes) [29:57]. В интерфейсе приложения это подтверждается тем, что задачи, созданные в To-Do приложении, корректно отображаются в базе данных даже после манипуляций с контейнерами [25:36].

При использовании Docker Compose тома описываются на двух уровнях. Сначала конкретный сервис (например, MySQL) указывает путь внутри контейнера, который нужно синхронизировать, а затем на верхнем уровне файла объявляется именованный том, такой как `todo-mysql-data` [28:11]. Это гарантирует, что даже если разработчик полностью удалит контейнер с базой данных, при следующем запуске информация останется нетронутой [29:57]. Ранее в курсе уже упоминалось, что использование томов является фундаментом для работы с любыми системами хранения данных в Docker [17:12]. 

Такой подход позволяет создать надежную среду для разработки. Если у вас есть коллеги, им достаточно получить ваш образ и файл Compose, чтобы развернуть идентичную среду со всеми зависимостями и переменными окружения [30:37]. Это исключает классическую проблему «на моей машине всё работает», позволяя всей команде использовать одинаковые версии инструментов и библиотек [30:50].

### Подготовка инфраструктуры для работы с SQL
[[JUMP:31:02]]

После освоения основ Docker следующим логическим шагом становится настройка среды для работы с языком структурированных запросов (SQL). Вместо локальной установки тяжеловесных СУБД, таких как PostgreSQL, в современной практике Data Engineering используется запуск базы внутри контейнера [31:28]. Процесс подготовки «песочницы» начинается с загрузки образа командой `docker pull postgres`, которая скачивает последнюю версию из Docker Hub [31:43].

Для запуска контейнера с Postgres используются специфические переменные окружения, в частности `POSTGRES_PASSWORD`, без которой база данных не запустится [32:11]. Автор демонстрирует создание контейнера с именем `data-engineering-postgres` и паролем `secret` [32:24]. Важно понимать разницу между запущенным контейнером и конкретной базой данных внутри него: для создания новой БД используется команда `docker exec` с утилитой `createDB` [33:04].

Такая установка позволяет мгновенно подключиться к терминалу базы данных через команду `psql` [33:31]. На этом этапе завершается инфраструктурная часть: теперь у инженера есть готовая, изолированная среда, где можно создавать таблицы (например, `users` или `films`) и наполнять их тестовыми данными [33:45, 40:07]. В дальнейшем обучении этот фундамент Docker позволит переходить к изучению синтаксиса SQL, включая выборку данных через `SELECT`, обновление записей через `UPDATE` и агрегацию информации, что является базовыми навыками для любого аналитика или инженера данных [35:01, 37:22, 44:03].

## 📊 Глава 3. Практика SQL: от базовой выборки к управлению данными
[[JUMP:50:23]]

### Базовые операции CRUD: создание и наполнение таблиц
[[JUMP:51:19]]

В работе дата-инженера умение манипулировать данными на базовом уровне предшествует любой сложной аналитике. Основной фокус в этой части курса смещается на практическое применение команд INSERT и SELECT для управления наборами данных [51:19]. Чтобы продемонстрировать связи между сущностями, инструктор создает две новые таблицы: `actors` (актеры) с полями `actor_id` и `actor_name`, а также связующую таблицу `film_actors` [51:31]. Процесс наполнения данными (INSERT) показан на примере сопоставления имен актеров с конкретными фильмами, которые уже есть в базе [51:45].

Для проверки корректности вставки используется стандартная выборка `SELECT *`, которая позволяет мгновенно увидеть структуру: например, что в таблице `actors` каждому имени присвоен уникальный числовой идентификатор [52:00]. Важность команды `SELECT` подчеркивается при работе с таблицей `Filmore category`, где после выполнения запроса отображается 39 записей, содержащих ID категорий и ID фильмов [50:23]. 

В процессе обучения рассматриваются сценарии изменения состояния базы данных:

*   Добавление новых записей для тестирования логики: так в таблицу `films` вносится вымышленный фильм «Mystical Adventures» [57:44].
*   Работа с пропусками: при добавлении фильма без указания актеров [58:38] или последующей вставке новых имен, таких как Jane Doe и John Smith [59:29].
*   Использование команды `SELECT ALL` (или `SELECT *`) для подтверждения того, что новая запись действительно появилась в самом низу списка под номером 21 [57:58].

Эти базовые манипуляции (часть цикла CRUD) критически важны для понимания того, как данные будут вести себя на следующих этапах — при объединении таблиц или их трансформации, о чем пойдет речь в будущих главах [52:26].

### Псевдонимы (Aliases) и форматирование результатов выборки
[[JUMP:52:39]]

По мере усложнения запросов, когда в выборке участвуют несколько таблиц, SQL-код может стать громоздким. Для решения этой проблемы вводятся псевдонимы (Aliases) [51:05]. Инструктор демонстрирует, как присвоить таблице `films` короткое имя `f`, а таблице `actors` — `a` [53:07]. Это позволяет обращаться к столбцам через точечную нотацию, например `f.title` или `a.actor_name`, что значительно упрощает чтение кода [52:54].

Особое внимание уделяется оператору `AS`, который используется для переименования столбцов в итоговой таблице результатов [1:01:03]. Хотя технически этот оператор является необязательным и его можно опустить, его использование делает намерения инженера более прозрачными: например, запрос `Select title AS name` превратит заголовок колонки «title» в «name» в выводе [1:01:17].

Для организации данных в удобном для анализа виде применяется оператор `ORDER BY` [54:13]. В примерах курса данные сортируются:

1. По числовым значениям: например, по `film_id` в возрастающем порядке (1, 2, 3...), чтобы «Inception» (ID 1) всегда шел первым [56:10].
2. По алфавиту: при объединении списков имен актеров и названий фильмов через оператор UNION (подробно изучаемый позже), итоговый результат выстраивается в алфавитном порядке [1:01:31].

Инструктор подчеркивает, что правильное форматирование и использование псевдонимов — это не просто вопрос эстетики, а необходимый навык для подготовки «чистых» таблиц, которые затем будут использоваться командами аналитиков [57:17].

### Логика фильтрации и подготовка к агрегации данных
[[JUMP:1:04:08]]

Хотя агрегатные функции (такие как `COUNT`, `SUM`, `AVG`, `MAX/MIN`) и оператор `GROUP BY` были впервые упомянуты ранее [43:50], в данном сегменте акцент делается на логике подготовки данных для таких вычислений. Ключевым инструментом здесь выступает фильтрация через предложение `WHERE` в сочетании с оператором `IN` [1:04:08]. 

Инструктор показывает, как извлечь из базы только те фильмы, в которых снимались конкретные люди, например Леонардо Ди Каприо или Том Хэнкс [1:04:21]. Этот запрос возвращает список из трех картин: «Inception», «Forrest Gump» и «Toy Story» [1:04:49]. Подобная фильтрация является фундаментом для агрегации: прежде чем посчитать средний бюджет или количество фильмов (COUNT), инженер должен точно определить выборку данных.

В ходе демонстрации также упоминаются важные аспекты работы с данными:

*   Использование «мостовых» таблиц (bridge tables), таких как `film_actors`, которые содержат только пары ID и служат связующим звеном для фильтрации [54:53].
*   Проверка соответствия данных: например, подтверждение того, что актеру Ди Каприо соответствует `actor_id` 1, а фильму «Inception» — `film_id` 1 [56:53].
*   Обработка NULL-значений: понимание того, как отсутствие данных о актере влияет на итоговую выборку [58:51].

Эти навыки работы с выборкой и условиями подготавливают почву для создания полноценного ETL-скрипта на Python [1:05:03]. Ранее в курсе уже затрагивались основы Docker, и теперь эти SQL-знания станут основой для данных, которые будут циркулировать между контейнерами источника и назначения в рамках создаваемого конвейера [1:12:34].

## 🧩 Объединение данных и сложные выборки: SQL в сердце трансформаций

[[JUMP:1:15:14]]

После того как инфраструктура данных настроена через Docker (о чем подробно говорилось в предыдущих частях), наступает критический момент: данные нужно не просто переместить, а сделать их пригодными для анализа [1:31:50]. Простой перенос таблиц «как есть» редко удовлетворяет запросам бизнеса. В этой главе мы разберем, как использовать мощь SQL для объединения разрозненных сущностей и создания сложных выборок, которые превратят сырые строки в структурированные инсайты.

### Искусство объединения: JOIN и работа с промежуточными таблицами
[[JUMP:1:31:50]]

Основная задача инженера данных при работе с реляционными базами — это восстановление связей между таблицами. В нашем проекте используются три ключевые сущности: `actors` (актеры), `films` (фильмы) и связующая таблица `film_actors` [1:39:26]. Поскольку один актер может сниматься в разных фильмах, а в одном фильме участвует множество актеров, мы имеем дело со связью «многие ко многим», которая реализуется через промежуточную таблицу-связку [1:39:40].

Для извлечения осмысленной информации используются следующие типы соединений:

*   **INNER JOIN**: Позволяет выбрать только те записи, для которых есть соответствие в обеих таблицах. Например, если мы хотим получить список актеров и названий фильмов, в которых они снимались, мы объединяем таблицу `actors` с `film_actors`, а затем с `films` [1:39:26]. Если актер не закреплен ни за одним фильмом в базе, он просто не попадет в результат.
*   **LEFT JOIN**: Критически важен, когда нам нужно сохранить все записи из «левой» (основной) таблицы, даже если для них нет пары в «правой». Это часто применяется в аналитике для поиска пропусков: например, чтобы найти фильмы, в которых еще не указан актерский состав [1:32:03].
*   **Алиасы (Aliases)**: При работе с тремя и более таблицами запросы становятся громоздкими. Использование сокращений (например, `a` для `actors`, `f` for `films`, `fa` для `film_actors`) — это не просто вопрос удобства, а стандарт индустрии, упрощающий чтение логики трансформации [1:39:40].

Как отмечает автор курса, часто аналитики или специалисты по Data Science не хотят писать сложные SQL-запросы самостоятельно [1:32:03]. Задача инженера данных — заранее подготовить эти объединения, чтобы в целевой базе (destination database) данные лежали в уже «сшитом» и понятном виде [1:31:50].

### Подзапросы и оператор UNION: гибкость в подготовке данных
[[JUMP:1:38:47]]

Когда простых JOIN-ов недостаточно для «морфинга» данных под конкретные нужды бизнеса [1:38:47], в игру вступают подзапросы и операторы объединения результатов. 

Подзапросы позволяют изолировать часть логики. Например, мы можем сначала отфильтровать список только «топовых» актеров во внутреннем запросе, а затем уже во внешнем запросе объединять этот результат с таблицей фильмов [1:31:50]. Это повышает читаемость кода и позволяет строить многоуровневые трансформации, превращая сырые данные в так называемые «витрины данных».

Оператор **UNION** незаменим в ситуациях, когда данные об однотипных сущностях приходят из разных источников. В контексте нашего ELT-процесса [1:25:08] это может выглядеть так:

1. У нас есть таблица актеров из одной системы и аналогичная таблица из другой.
2. Чтобы создать единый мастер-список для аналитиков, мы используем `UNION`, который «склеивает» результаты двух выборок по вертикали [1:31:38]. 
3. Важно помнить, что для работы `UNION` количество и типы колонок в объединяемых запросах должны строго совпадать [1:31:50].

Эти инструменты позволяют инженеру гибко управлять структурой данных, прежде чем они попадут в руки конечных пользователей [1:32:03].

### Инфраструктура для трансформаций: подготовка к dbt
[[JUMP:1:31:38]]

Для автоматизации всех описанных SQL-операций (Joins, Subqueries, Unions) автор предлагает использовать **dbt (Data Build Tool)** [1:31:38]. Это open-source инструмент, который позволяет писать SQL-код в виде моделей. Вместо того чтобы вручную запускать скрипты объединения таблиц, dbt берет на себя управление зависимостями [1:34:39].

Процесс подготовки включает несколько этапов:

*   **Инициализация проекта**: Команда `dbt init` создает структуру папок, включая `models`, где будут храниться наши SQL-файлы с джойнами [1:34:10].
*   **Настройка профилей**: В файле `profiles.yml` прописываются параметры подключения к базе данных [1:35:19]. Важным нюансом здесь является использование хоста `host.docker.internal` для того, чтобы dbt, запущенный локально, мог «достучаться» до базы внутри Docker-контейнера [1:36:21].
*   **Создание ссылок (Sources)**: Прежде чем писать сложные JOIN-запросы, необходимо создать базовые модели, которые просто выбирают данные из таблиц `actors`, `films` и `film_actors` [1:39:26]. Это делается с помощью конструкции `SELECT * FROM {{ source(...) }}` [1:39:40].

Такой подход превращает написание SQL-запросов из хаотичного процесса в стройную систему разработки, где каждая трансформация — это отдельная, тестируемая модель [1:38:47]. После успешного запуска ELT-скрипта [1:29:06] и подтверждения наличия данных в базе через `psql` [1:30:16], мы готовы к созданию полноценных аналитических моделей.

## 🛠️ Развертывание ETL-пайплайна: интеграция Python-скрипта и Docker
[[JUMP:1:40:19]]

### Проектирование логики трансформации данных
[[JUMP:1:42:14]]

После того как первичные данные были подготовлены, необходимо определить логику их преобразования внутри пайплайна. На этом этапе создаются кастомные модели, которые формулируют и модифицируют сырые данные в таблицы нужного формата [1:42:14]. Одной из ключевых задач становится объединение информации о фильмах и актерах в единую структуру, ориентированную на рейтинги [1:42:27].

Для написания чистого и поддерживаемого кода используются обобщенные табличные выражения (CTE — Common Table Expressions) [1:44:35]. Это позволяет оборачивать подзапросы в именованные блоки, которые действуют только в рамках одного файла, что значительно упрощает чтение сложных SQL-конструкций [1:44:48]. В процессе трансформации применяется оператор `CASE` (аналог `if-else` в программировании) для сегментации пользовательских рейтингов: например, оценки выше 4.5 классифицируются как «Excellent», а выше 4.0 — как «Good» [1:43:19].

Технически процесс объединения данных выглядит следующим образом:

*   Создается первая CTE для обработки рейтингов и категорий [1:42:52].
*   Создается вторая CTE для агрегации имен актеров через запятую для каждого фильма [1:45:27].
*   Используется `LEFT JOIN` для связки таблиц фильмов, актеров и промежуточных таблиц (bridge tables) по идентификатору `film_id` [1:46:06].
*   Финальная выборка объединяет обе CTE в одну итоговую таблицу [1:47:25].

Такой подход позволяет подготовить данные к аналитике еще на этапе загрузки, минимизируя нагрузку на базу при последующих запросах [1:47:50].

### Настройка Docker-окружения для ETL-пайплайна
[[JUMP:1:48:04]]

Для автоматизации работы ETL-скрипта требуется правильно настроенная контейнеризация. В файле `docker-compose.yaml` описываются сервисы, которые будут отвечать за обработку данных [1:48:17]. Основным звеном здесь выступает Python-скрипт (ранее упоминавшийся как скрипт на базе `subprocess`), который выполняет дамп и загрузку между базами Postgres, но теперь к нему добавляется сервисный слой для трансформаций [1:48:17].

Ключевым моментом в конфигурации Docker является управление зависимостями через параметр `depends_on` [1:50:58]. Трансформационная часть пайплайна должна запускаться только после того, как Python-скрипт (elt_script) успешно завершит свою работу [1:50:58]. Если запустить процесс обработки на пустой базе, система выдаст ошибку, так как ей не к чему будет обращаться [1:51:11]. 

Важные аспекты настройки контейнера:

1.  **Образы и версии:** Использование специфических образов для работы с Postgres (например, `dbt-postgres` версии 1.4.7) [1:48:31].
2.  **Маппинг томов (Volumes):** Необходимо сопоставить локальные директории проекта с путями внутри контейнера, чтобы Docker «видел» ваши SQL-файлы и профили подключения [1:49:51].
3.  **Переменные окружения:** Установка целевых таргетов (например, `dev` для разработки) позволяет гибко переключаться между тестовыми и боевыми средами [1:51:37].
4.  **Сетевое взаимодействие:** Все компоненты, включая базы данных и скрипты, должны находиться в одной сети (`elt_network`), чтобы иметь возможность беспрепятственно обмениваться данными [1:49:37].

При настройке важно избегать опечаток в именах директорий и файлов (например, использование подчеркивания вместо тире), так как Docker чувствителен к путям [1:52:48].

### Отладка и верификация данных в целевой базе Postgres
[[JUMP:1:54:48]]

После запуска контейнеров через `docker-compose up` критически важно убедиться, что данные прошли весь путь от источника до финальной таблицы без потерь [1:52:01]. Первым признаком успеха является статус `Exited Code 0` у ETL-скрипта, что означает корректное завершение процесса выгрузки и загрузки данных [1:52:18].

Для проверки результатов используется терминал и команда `docker exec` [1:54:48]. Процесс верификации включает:

*   Подключение к контейнеру целевой базы данных (destination_postgres) [1:54:55].
*   Вход в интерактивную оболочку `psql` под пользователем `postgres` [1:54:55].
*   Переключение на базу данных `destination_db` [1:55:01].
*   Просмотр списка созданных таблиц с помощью команды `\dt` [1:55:10].

Если все этапы выполнены верно, в списке появится новая таблица (например, `film_ratings`), созданная в результате работы пайплайна [1:55:10]. Выполнив простой SQL-запрос `SELECT * FROM film_ratings`, можно увидеть результат трансформации: объединенные данные о фильмах, списки актеров и присвоенные категории рейтингов [1:55:16]. Успешное отображение таких фильмов, как «Inception» с Леонардо Ди Каприо и его категорией рейтинга, подтверждает работоспособность всей логики [1:55:45].

Ранее в разговоре авторы упоминали возможность сделать эти запросы более гибкими. В дальнейшем для улучшения читаемости и повторного использования кода будут применяться более продвинутые методы, такие как макросы и Jinja-шаблоны [1:56:13], а также системы оркестрации вроде Apache Airflow для замены ручного запуска и Cron-задач [2:05:10].

## 🛠️ Трансформации и логика dbt: от SQL-моделей до динамических макросов
[[JUMP:2:05:23]]

После того как первичные данные загружены в хранилище, наступает этап их преобразования. В современной дата-инженерии для этого чаще всего используется dbt (data build tool) — инструмент, который позволяет описывать логику трансформаций на чистом SQL, сохраняя при этом все преимущества профессиональной разработки: версионирование, тестирование и документацию [2:09:32]. В отличие от традиционных ETL-инструментов, dbt реализует подход ELT, где трансформации происходят непосредственно внутри целевой базы данных, используя её вычислительные мощности [2:16:24].

### Модели dbt и концепция трансформаций «внутри» базы
[[JUMP:2:09:32]]

Основным строительным блоком в dbt является «модель». По сути, это обычный файл `.sql`, содержащий инструкцию `SELECT` [2:09:32]. Когда инженер запускает команду `dbt run`, инструмент автоматически оборачивает этот запрос в конструкцию `CREATE TABLE AS...` или `CREATE VIEW...`, создавая готовые объекты в базе данных [2:26:58]. Это избавляет разработчика от необходимости вручную писать DDL-коды (Data Definition Language) и следить за порядком создания таблиц.

В рамках проекта трансформации организуются в специальной папке (например, `custom_postgres`), которая затем монтируется в рабочее окружение [2:16:32]. Это позволяет dbt иметь полный доступ к исходным кодам моделей и конфигурационным файлам проекта [2:16:35]. Важно понимать, что dbt не перемещает данные между системами; он лишь отправляет SQL-команды в Postgres, заставляя базу данных эффективно преобразовывать уже имеющиеся в ней «сырые» таблицы в аналитические витрины [2:16:24]. 

Для работы dbt в контейнеризированной среде используется специализированный образ, например `ghcr.io/dbt-labs/dbt-postgres` версии 1.4.7 [2:26:48]. При запуске трансформаций критически важно правильно указать пути к проекту:

*   `--project-dir` — путь к папке, где находится файл `dbt_project.yml` [2:27:14];
*   `--profiles-dir` — путь к конфигурации подключений `profiles.yml`, который обычно располагается в корневой директории пользователя [2:27:10].

Такое разделение позволяет хранить логику трансформаций отдельно от учетных данных для доступа к базе [2:28:18].

### Гибкость кода: Jinja-шаблоны и макросы в dbt
[[JUMP:2:16:24]]

Одной из самых мощных функций dbt является использование движка шаблонов Jinja. Хотя dbt и работает с SQL, Jinja превращает его в подобие языка программирования, позволяя использовать переменные, циклы и условия прямо внутри запросов [2:09:32]. Это критично для создания динамических моделей, которые адаптируются к входным данным без переписывания кода вручную.

Макросы в dbt — это переиспользуемые фрагменты кода, аналогичные функциям в Python [2:20:10]. Если у инженера есть сложная логика очистки данных или специфический расчет, который повторяется в десяти моделях, он может вынести его в макрос и вызывать одной строкой [2:23:42]. Это не только сокращает объем кода, но и радикально упрощает его поддержку: ошибка исправляется в одном месте, а не в десяти [2:24:05].

Применение Jinja также позволяет реализовать механизм ссылок `{{ ref('model_name') }}`. Вместо того чтобы жестко прописывать имена таблиц в SQL (например, `FROM raw_data.users`), инженер пишет ссылку на модель [2:25:30]. dbt на лету выстраивает граф зависимостей (Lineage Graph), понимая, какие таблицы нужно создать сначала, а какие — после, чтобы данные в финальных отчетах всегда были актуальными [2:26:10]. Ранее в разговоре авторы упоминали, что такая автоматизация зависимостей — ключевое отличие dbt от простых SQL-скриптов, запускаемых вручную.

### Интеграция и конфигурация dbt в производственном цикле
[[JUMP:2:16:32]]

Для того чтобы dbt успешно взаимодействовал с базой данных в рамках автоматизированного пайплайна, необходимо обеспечить правильное мапирование томов и настройку профилей. В конфигурации системы папка с трансформациями `custom_postgres` связывается с внутренней директорией `/dbt` внутри рабочего контейнера [2:16:32]. Аналогичным образом пробрасываются и настройки подключений через `profiles.yml`, чтобы инструмент знал адрес хоста, порт и учетные данные базы данных [2:16:40].

В процессе работы dbt может быть настроен на автоматическое удаление временных контейнеров после выполнения задачи (`Auto remove = true`), что позволяет экономить системные ресурсы [2:27:27]. При этом взаимодействие с Docker-сетью происходит через специальный сокет `unix:///var/run/docker.sock`, что дает инструментам возможность «общаться» друг с другом внутри изолированной среды [2:27:39]. 

Основные этапы настройки dbt для продакшена включают:

1.  Определение сетевого режима (например, `bridge`), соответствующего драйверу общей сети проекта [2:27:55].
2.  Монтирование исходного кода моделей в целевой контейнер через `type=bind` [2:29:17].
3.  Настройку зависимостей между задачами: сначала должна завершиться загрузка данных (инструментами, описанными в предыдущих главах), и только потом запускается `dbt run` [2:30:04].

Такой подход гарантирует, что трансформации всегда работают с полным набором данных, а логика SQL-моделей остается чистой и независимой от инфраструктурного кода [2:30:32].

## 🐘 Оркестрация процессов через Apache Airflow

### Настройка DAG для управления ETL и dbt
[[JUMP:2:30:45]]

Когда основные компоненты ETL-скрипта и dbt-модели готовы, наступает этап их объединения в единый автоматизированный рабочий процесс. Для этого используется Apache Airflow, где логика управления описывается в виде DAG (Directed Acyclic Graph) [2:30:45]. В рассматриваемом примере первый оператор отвечает за запуск Python-скрипта, а второй — `DockerOperator` — создает отдельный контейнер для dbt [2:30:59]. Это позволяет изолированно запустить трансформации поверх данных, уже загруженных в целевое хранилище на первом этапе [2:31:12]. 

Важным архитектурным изменением становится модификация файла `docker-compose.yaml`. Поскольку теперь запуском задач управляет Airflow, из основного файла конфигурации Docker следует закомментировать сервисы `elt_script` и `dbt` [2:31:38]. Airflow сам будет инициировать создание нужных контейнеров в нужной последовательности [2:31:40]. Перед полноценным запуском всей системы критически важно выполнить команду `docker compose up init-airflow` [2:32:35]. Это инициализирует базу данных Postgres для метаданных Airflow и настраивает необходимые учетные записи [2:32:47]. Только после того как контейнеры базы данных и инициализации перейдут в статус выполнения, можно запускать веб-сервер и планировщик (scheduler) [2:33:00].

### Отладка и работа с интерфейсом Airflow
[[JUMP:2:33:13]]

Веб-интерфейс Airflow доступен по адресу `localhost:8080` [2:33:13]. Стандартные учетные данные для входа в рамках данного проекта — `airflow` / `airflow` [2:33:27]. Часто при первом запуске возникают ошибки импорта, такие как "broken DAG" [2:33:42]. В процессе разработки автор сталкивается с типичной опечаткой в названии модулей: вместо `operators` необходимо использовать `providers` в пути импорта для Docker-оператора [2:34:54]. Правильный путь выглядит как `airflow.providers.docker.operators.docker` [2:35:06].

После исправления синтаксических ошибок в UI появляется DAG с именем `elt_and_dbt` [2:35:50]. При ручном запуске графа (trigger dag) можно наблюдать за статусом выполнения каждой задачи в реальном времени [2:36:15]. Если задача dbt падает с ошибкой, Airflow предоставляет доступ к подробным логам, которые хранятся в его собственной базе данных Postgres [2:36:30]. Одной из специфических проблем при использовании Docker внутри Airflow является настройка путей: проект dbt должен быть правильно смонтирован, например, по пути `/opt/dbt` внутри контейнера [2:38:44]. Также следует внимательно следить за тем, чтобы в папке моделей не оставалось лишних примеров (вроде `my_second_dbt_model.sql`), которые могут вызвать конфликты при компиляции проекта [2:39:12]. Успешное выполнение обеих задач в DAG подтверждается зеленым цветом индикаторов в интерфейсе [2:40:30].

### Автоматизация среды и переход к новым операторам
[[JUMP:2:43:35]]

Для масштабирования проекта и добавления новых инструментов, таких как Airbyte (платформа для интеграции данных), необходимо расширить возможности контейнера Airflow. В Dockerfile добавляются новые провайдеры через `pip install`: помимо Docker-провайдера, устанавливаются `apache-airflow-providers-http` и `apache-airflow-providers-airbyte` [2:44:27]. HTTP-провайдер является обязательной зависимостью для работы с Airbyte [2:44:41]. Чтобы упростить процесс запуска множества контейнеров, автор создает вспомогательные bash-скрипты: `start.sh` и `stop.sh` [2:45:20]. Скрипт запуска автоматизирует инициализацию Airflow, ожидание в 5 секунд и последующий запуск Airbyte из его собственной директории [2:45:33].

В самом коде DAG также происходят изменения. Вместо использования Python-оператора для самописного ETL-скрипта (который ранее обсуждался в контексте извлечения данных), внедряется `AirbyteTriggerSyncOperator` [2:48:43]. Этот оператор позволяет инициировать синхронизацию данных через внешний инструмент, передавая лишь `connection_id` [2:49:34]. Основные параметры оператора включают:

*   `airbyte_conn_id`: имя подключения, настроенное внутри Airflow (обычно 'airbyte') [2:49:22];
*   `connection_id`: уникальный идентификатор конкретной связки источника и приемника в Airbyte [2:50:25];
*   `asynchronous`: параметр, установленный в `False`, чтобы Airflow дожидался завершения задачи перед переходом к dbt [2:49:47].

### Подготовка инфраструктуры для внешних инструментов
[[JUMP:2:51:30]]

Интеграция сторонних инструментов в оркестрацию требует понимания того, как контейнеры взаимодействуют друг с другом. Поскольку Airbyte работает в своем наборе Docker-контейнеров, Airflow должен знать, как к ним обратиться [2:51:30]. Для этого в настройках подключений Airflow используется адрес `host.docker.internal`, что позволяет контейнерам «общаться» через хост-машину [2:53:00]. При настройке источников данных (например, Source Postgres) важно правильно указывать порты, такие как `5433` для исходной базы, чтобы избежать конфликтов с целевой базой данных [2:53:04].

Хотя Airbyte имеет собственный планировщик, при работе в связке с Airflow его лучше переключить в режим `manual` [2:54:36]. Это гарантирует, что именно Airflow будет выступать «единым источником истины» для расписания всех процессов [2:54:48]. Выбор режима синхронизации (например, `Full Refresh Overwrite` [2:55:28]) также влияет на то, как dbt-модели будут обрабатывать данные на следующем шаге DAG. Таким образом, Airflow связывает разрозненные инструменты — от систем перемещения данных до систем трансформации — в единый, контролируемый поток данных.

## 🔄 Интеграция с Airbyte: Переход к современному стеку данных
[[JUMP:2:55:40]]

В финальной стадии проекта происходит качественный переход от самописных решений к профессиональным инструментам автоматизации. Если ранее в рамках курса мы создавали ETL-скрипт на Python вручную, то теперь его место занимает Airbyte — платформа с открытым исходным кодом, которая стандартизирует процесс извлечения и загрузки данных. Этот этап превращает разрозненные контейнеры в согласованную экосистему, где каждый инструмент выполняет свою узкоспециализированную задачу.

### Синхронизация Airflow и Airbyte: Настройка соединений
[[JUMP:2:55:55]]

Первым шагом к автоматизации является установление связи между оркестратором Airflow и платформой Airbyte. Для этого необходимо получить уникальный идентификатор соединения (Connection ID) непосредственно из интерфейса Airbyte и интегрировать его в код нашего DAG-файла [2:55:55]. Это критически важный момент, так как именно через этот ID Airflow будет отдавать команды на запуск конкретных потоков данных.

Однако простого обновления кода недостаточно. Поскольку компоненты системы работают в изолированных Docker-контейнерах, изменения не всегда распространяются мгновенно [2:56:08]. Инструктор подчеркивает, что для корректной работы часто требуется перезапуск части инфраструктуры. В данном случае необходимо остановить секцию ELT, сохранив Airbyte запущенным, а затем инициализировать процесс заново с помощью скрипта `elt.sh` [2:56:33]. Это гарантирует, что все переменные окружения и обновленные DAG-файлы будут подтянуты системой корректно [2:56:59].

Настройка самого соединения внутри веб-интерфейса Airflow требует точности в деталях:

*   Тип соединения устанавливается как HTTP, а в поле Host прописывается адрес `host.docker.internal` [2:59:37].
*   Порт для доступа к UI Airbyte — 8001 [2:59:49].
*   Для авторизации используются стандартные учетные данные: логин `airbyte` и пароль `password`, что является обязательным требованием безопасности в современных версиях платформы [3:00:17].

### Устранение неполадок при развертывании в Docker
[[JUMP:2:57:26]]

Интеграция сложных инструментов часто сопровождается техническими трудностями, связанными с зависимостями и правами доступа. В процессе настройки автор сталкивается с ошибкой импорта оператора `airflow.providers.airbyte.operators.airbyte` [2:57:26]. Проблема кроется в специфике сборки образов: Airflow требует выполнения установки провайдеров от имени определенного пользователя, что создает конфликт при стандартной сборке [2:57:51].

Решение заключается в специфическом алгоритме: сначала система должна войти под пользователем `airflow` для установки необходимых операторов, а затем переключиться на пользователя `root` для выполнения DAG [2:58:05]. Еще один важный нюанс — кеширование образов в Docker. Если изменения в командах установки не применяются, единственный надежный способ — полностью удалить старые образы веб-сервера и планировщика (scheduler), после чего запустить сборку с нуля [2:58:57]. Только после такой «чистой» переустановки DAG появляется в веб-интерфейсе без ошибок [2:59:10].

### Запуск финального пайплайна и верификация данных
[[JUMP:3:00:29]]

Когда все настройки завершены, происходит запуск всей цепочки. Airflow триггерит Airbyte, и в интерфейсе последнего можно наблюдать за процессом синхронизации в реальном времени [3:00:29]. Успешное завершение этого этапа подтверждается статистикой: в рассматриваемом примере из источника было извлечено 1113 записей [3:01:07]. Сразу после этого оркестратор автоматически запускает dbt (о котором рассказывалось в предыдущих главах), чтобы выполнить финальные трансформации [3:01:21].

Проверка результата в целевой базе данных (`destination_db`) показывает полную картину работы пайплайна:

1.  Появляются таблицы с префиксом `_airbyte_raw`, содержащие «сырые» данные и метаданные платформы [3:01:48].
2.  Присутствуют финальные таблицы, созданные dbt, такие как `specific_movie` [3:02:02].
3.  Данные успешно перемещены из Postgres (источник) через Airbyte в целевую БД и трансформированы для анализа [3:02:39].

В итоге проект демонстрирует эволюцию пайплайна: от сложного и хрупкого ручного скрипта в начале обучения к чистому и масштабируемому решению на базе Modern Data Stack [3:03:06]. Использование Airbyte и Airflow позволяет инженеру сосредоточиться на логике данных, а не на написании кода для обработки API или сетевых протоколов [3:03:19].