Конвеєр
послідовність кроків, що веде дані від джерела до вжитку
Що це таке
Конвеєр, повніше конвеєр даних, а в професійному мовленні часто пайплайн (від
англійського pipeline), — це набір завдань, які виконуються в чітко заданому
порядку, причому кожне бере те, що підготувало попереднє, аж поки сирі записи із
систем, де вони виникають, не опиняться очищеними, зведеними й збереженими там,
звідки їх читають звіти, дашборди чи інші програми. Назва прийшла з оболонки
Unix, де символ | передає вивід однієї команди на вхід наступної. Більшість
конвеєрів працює без нагляду: за розкладом або щойно надходить новий файл.
Рідко який конвеєр є прямою лінією. Завданню, що завантажує денні продажі, можуть спершу знадобитися і курси валют, і список клієнтів, тоді як ці два завдання цілком можуть виконуватися паралельно. Завдання разом зі стрілками «має завершитися раніше» між ними утворюють орієнтований ациклічний граф: кожна стрілка вказує в один бік, і жодне завдання не може опинитися в очікуванні самого себе. Apache Airflow, платформа з відкритим кодом для планування й моніторингу таких робочих процесів, називає кожен такий граф Dag — саме так, а не DAG, бо значення цього терміна в Airflow давно вийшло за межі суто математичної структури.
Пакетний (batch) конвеєр прокидається за розкладом чи за подією, обробляє обмежений зріз даних, наприклад показання лічильників за один день, і зупиняється. Потоковий (streaming) конвеєр працює безперервно й передає кожну подію далі, щойно вона надходить. Airflow описує себе як платформу для оркестрування пакетних робочих процесів, призначену насамперед для роботи з чітким початком і кінцем, що виконується за розкладом.
Найпростіший конвеєр — це рядок у crontab, що о другій ночі запускає скрипт.
Цього досить, поки скрипти незалежні. Щойно кілька з них мають іти по черзі, а
один падає, комусь доводиться з’ясовувати, що встигло виконатися і що запускати
знову. Оркестратор бере цю облікову роботу на себе: він тримає граф, запускає
кожне завдання, щойно успішно завершилися ті, від яких воно залежить, повторює
завдання, що впали, веде журнал для кожного завдання й кожного запуску і фіксує,
які періоди даних охопив кожен запуск. В Airflow кожен запуск за розкладом
охоплює інтервал даних (data interval). Щоденний запуск за 1 січня починається
одразу після півночі 2 січня, коли день уже минув, і його завдання можуть
прочитати межі на кшталт data_interval_start, щоб точно знати, який зріз даних
їхній.
Чому це важливо
Конвеєр перетворює процедуру на код. Порядок завдань, перевірки між ними, те, що відбувається, коли одне з них падає, і як часто все це запускається, переходять із пам’яті однієї людини у файли, які можна переглядати, зберігати в системі контролю версій і тестувати, як будь-яке інше програмне забезпечення. Airflow описує робочі процеси повністю на Python — підхід, який проєкт називає workflows as code, — і серед переваг називає контроль версій і тестування.
Збій до того ж отримує адресу. Коли весь процес — це один довгий скрипт, падіння на сороковій хвилині означає почати з нульової. Якщо ж розбити його на завдання із записаним станом, невдалий запуск показує, яке завдання зупинилося і чому, і перезапускати треба лише його та ті, що йдуть після нього. Airflow дає змогу операторові перезапустити тільки завдання, що впали, або виконати backfill для попереднього періоду, і саме так виправлення, зроблене в середу, доходить до даних за понеділок.
Повторення стає безпечним лише тоді, коли кожне завдання для цього написано. Посібник Airflow з найкращих практик вимагає, щоб кожне завдання поводилося як транзакція бази даних, яка ніколи не залишає часткових результатів, і давало однаковий результат за кожного повторного запуску. Таке завдання планувальник може повторити о третій ночі, людина може перезапустити його після виправлення, а за потреби його можна прогнати знову за минулий місяць, і ніхто не мусить спершу перевіряти цільову таблицю.
Ціна — ще одне програмне забезпечення, яке треба утримувати. Оркестратор — це окремий сервіс із власною базою даних, оновленнями й правами доступу, а кожен конвеєр у ньому — код, що потребує рев’ю й тестів. Для двох скриптів з одним розкладом чесною відповіддю можуть бути cron і файл журналу. Аргументи на користь оркестратора зростають разом із кількістю завдань, що залежать одне від одного, кількістю людей, яким треба бачити, що виконалося, і ціною ранкового звіту, побудованого на завантаженні, що зупинилося на півдорозі.
Як це виглядає на практиці
Двадцять GIS-конвеєрів даних і ETL для даних застосунку на одній платформі запускали й відстежували вручну, і весь час існував ризик, що якийсь із них непомітно зупиниться, а дані в подальших кроках виявляться хибними раніше, ніж хтось це помітить. Усі вони перейшли на автоматизоване постачання. Розклад, журналювання й обробку збоїв звели до одного стандарту, тож усі конвеєри поводилися однаково, а той, що зламався, ставав видимим, і їх включили в уже наявну автоматизацію інфраструктури та звітність. Відтоді всі двадцять запускалися самі й поводилися передбачувано, даючи актуальні дані, на які можна покластися, без ручного супроводу.
Конвеєр може також збирати щось ціле однією командою. Генератор CV і портфоліо на основі бази даних бере кожен документ з однієї бази даних SQLite на 23 таблиці, а саму базу збирає конвеєр із 19 кроків, що проходить шлях від порожнього файлу до повної бази даних. Кроки мають сталий порядок, і кожен можна безпечно запустити ще раз, тож конвеєр можна проганяти й на вже наявній базі, і жоден рядок при цьому не з’являється двічі. Далі 41 модуль Python загальним обсягом 10 580 рядків створює з цього єдиного джерела шість вихідних форматів, а залежностей під час виконання в нього лише дві.
Усередині окремого завдання швидкість важить не менше за порядок. Конвеєр погодинного агрегування споживання електроенергії бере дані JSONL, що надходять щогодини, кожні 15 хвилин або в нерегулярні моменти, вирівнює їх за строгими погодинними інтервалами, підсумовує виробництво за міксом, додає імпорт і віднімає експорт, а на виході дає один рядок на годину на зону. Його реалізували трьома способами — на Python, на JQ і на SQL у PostgreSQL — і кожен варіант перевірили на даних за один день і за 30 днів. Найшвидшим виявився Python: 34 мс для одного дня і 180 мс для 30 днів, а його код можна було розширити на більше зон або вбудувати в ETL-конвеєр.
Де виникають проблеми
Повторний запуск дає іншу відповідь. Щоденне завдання дописує свої підсумки
через INSERT, падає на півдорозі, планувальник повторює його, і рядки,
записані до збою, тепер лежать двічі. Завдання, що вибирає все від поточного
моменту мінус доба, під час повтору читає інший зріз, ніж за першої спроби, а
завдання, що читає найновішу версію вхідних даних, підхоплює все, що змінилося в
проміжку. Посібник Airflow з найкращих практик називає всі три випадки: замінити
INSERT на upsert, читати й писати конкретну партицію, прив’язану до
data_interval_start запуску, і тримати now() подалі від будь-якого
критичного обчислення. Для таблиці погодинних підсумків у PostgreSQL один
простий спосіб — замінювати весь інтервал однією транзакцією.
BEGIN;
DELETE FROM hourly_load
WHERE hour_start >= :'start' AND hour_start < :'end';
INSERT INTO hourly_load (zone, hour_start, mwh)
SELECT zone, date_trunc('hour', read_at), sum(mwh)
FROM reading
WHERE read_at >= :'start' AND read_at < :'end'
GROUP BY zone, date_trunc('hour', read_at);
COMMIT;
Якщо запускати його як
psql -v ON_ERROR_STOP=1 -v start=... -v end=... -f hourly_load.sql, а початок
і кінець інтервалу даних запуску передає оркестратор, завдання видаляє й
переписує рядки рівно цього інтервалу, тож другий запуск залишає таблицю такою
самою, як перший. Форма :'start' — це власний синтаксис psql для підстановки
змінної, взятої в лапки як SQL-літерал.
Завдання завершується зеленим, а дані хибні. Оркестратор бачить, чи процес
завдання завершився чисто, і на цьому його знання закінчуються. psql — показовий
приклад: за замовчуванням він продовжує роботу після помилки у скрипті й
повертає оболонці 0, тож крок, що виконує psql -f, звітує про успіх, навіть
коли одна з інструкцій усередині впала. Зі встановленою змінною ON_ERROR_STOP
psql зупиняється на першій помилці й завершується з кодом 3, і крок падає там,
де має. Але й чисте завершення доводить лише, що програма відпрацювала, тому
посібник Airflow радить самоперевірки: завдання далі в графі, яке підтверджує,
що партицію записано і дані виглядають правильно, перш ніж їх хтось прочитає.
Для запуску, що зависає, а не падає, є Deadline Alerts, які з’явилися в Airflow
3.1 на заміну функції SLA, вилученої у 3.0. Вони запускають callback, наприклад
лист електронною поштою, коли запуск перевищує часовий поріг.
Повторне ввімкнення конвеєра спричиняє лавину запусків. З catchup=True
планувальник створює запуск для кожного інтервалу даних, пропущеного від дати
початку, а документація Airflow додає, що catchup спрацьовує й тоді, коли Dag на
якийсь час вимкнули, а потім увімкнули знову. Щоденний конвеєр, що місяць
простояв на паузі, може повернутися з тридцятьма запусками в черзі до одного й
того самого джерела. Значення за замовчуванням, catchup_by_default=False,
змушує планувальник створювати запуск лише для останнього інтервалу, а
документація каже, що Dag, чиї завдання читають поточний час замість свого
інтервалу, має тримати catchup вимкненим. Історію краще заповнювати свідомо,
командою airflow backfill create, яка приймає діапазон дат і обмеження на
кількість паралельних запусків через --max-active-runs.
Важкий код на початку файлу з Dag гальмує планувальник. Airflow розбирає
кожен файл із Dag знову і знову, щонайменше з інтервалом
min_file_process_interval секунд між розборами, тож код на верхньому рівні
файлу, поза будь-яким завданням, виконується значно частіше за сам конвеєр.
Запит до бази даних, мережевий виклик чи повільний імпорт там коштують часу під
час кожного розбору, і посібник з найкращих практик наводить приклад, у якому
один такий виклик додає до розбору Dag ще 1000 секунд. Залишайте на верхньому
рівні лише оголошення завдань і їхнього порядку, а дорогі імпорти й виклики
переносьте всередину функцій завдань, яким вони потрібні.
Що почитати далі
- Apache Airflow — огляд від самого проєкту: робочі процеси як код на Python, оркестрування пакетних процесів, backfill і перезапуск лише тих завдань, що впали.
- Best Practices — завдання як транзакції, однаковий результат за кожного повторного запуску, код верхнього рівня й самоперевірки.
- Dags — як Dag оголошує свій розклад, завдання, залежності й типові аргументи на кшталт retries і де місце Deadline Alerts.
- Dag Runs — інтервали даних, catchup, backfill і очищення завдань, щоб вони виконалися знову.
- Tasks — залежності між завданнями, тайм-аути, політики повторних спроб і перехід від SLA до Deadline Alerts.
- psql — змінні та їх
взяття в лапки,
ON_ERROR_STOPі коди завершення, які читає оркестратор.