Apache Airflow 2026๋ ๊ฐ์ด๋: ํ์ดํ๋ผ์ธ ์ค์ผ์คํธ๋ ์ด์ , DAG ๋ฐ ๋ฉด์ ์ง๋ฌธ ์ ๋ฆฌ
Apache Airflow 3.2์ Task SDK๋ฅผ ํ์ฉํ DAG ๊ตฌ์ถ, ์์ ํํฐ์ , ๋ค์ดํฐ๋ธ ๋น๋๊ธฐ ํ์คํฌ, ๋ฐ์ดํฐ ์์ง๋์ด ๋ฉด์ ์์ ์์ฃผ ์ถ์ ๋๋ ์ง๋ฌธ์ ์ข ํฉ์ ์ผ๋ก ๋ค๋ฃจ๋ ํํ ๋ฆฌ์ผ์ ๋๋ค.

Apache Airflow 3.2๋ 3.0 ์ํคํ ์ฒ ์ ๋ฉด ๊ฐํธ ์ดํ ๊ฐ์ฅ ์ค์ํ ์งํ๋ฅผ ์ด๋ฃฌ ๋ฆด๋ฆฌ์ค์ ๋๋ค. PyPI ์๊ฐ ๋ค์ด๋ก๋ ์๊ฐ 1,000๋ง ๊ฑด์ ๋์ด์ฐ์ผ๋ฉฐ, Airbnb๋ถํฐ Spotify๊น์ง ๋ค์ํ ๊ธฐ์ ์์ ์ฑํํ๊ณ ์์ด ๋ฐ์ดํฐ ํ์ดํ๋ผ์ธ ๊ตฌ์ถ, ์ค์ผ์ค๋ง, ๋ชจ๋ํฐ๋ง ๋ถ์ผ์์ ํต์ฌ ๋๊ตฌ๋ก์์ ์์์ ํ๊ณ ํ ํ๊ณ ์์ต๋๋ค. ์ด ํํ ๋ฆฌ์ผ์์๋ ์๋ก์ด Task SDK๋ฅผ ํ์ฉํ DAG ์์ฑ, ํ์ดํ๋ผ์ธ ์ค์ผ์คํธ๋ ์ด์ ํจํด, ๊ทธ๋ฆฌ๊ณ ๋ฐ์ดํฐ ์์ง๋์ด ์ง๋ฌด ๋ฉด์ ์์ ์์ฃผ ์ถ์ ๋๋ ์ง๋ฌธ์ ์ดํด๋ด ๋๋ค.
Airflow 3.2๋ 2026๋
4์์ ๋ฆด๋ฆฌ์ค๋์์ผ๋ฉฐ, ์ธ๋ถํ๋ ๋ฐ์ดํฐ ์ธ์ ์ค์ผ์ค๋ง์ ์ํ ์์
ํํฐ์
, PythonOperator์ ๋ค์ดํฐ๋ธ ๋น๋๊ธฐ ์ง์, ๋ฉํฐํ ๋ฐฐํฌ ๊ธฐ๋ฅ์ด ๋์
๋์์ต๋๋ค. ๋ชจ๋ DAG ์ํฌํธ๋ Airflow 3.0์์ ๋์
๋ ์์ ์ ์ธ airflow.sdk ๋ค์์คํ์ด์ค๋ฅผ ์ฌ์ฉํฉ๋๋ค.
Airflow Task SDK๋ฅผ ํ์ฉํ DAG ์์ฑ
Airflow 3.0์์ ๋์
๋ Task SDK๋ DAG ์ ์๋ฅผ Airflow ๋ด๋ถ ๊ตฌํ์ผ๋ก๋ถํฐ ๋ถ๋ฆฌํ๋ ๋
๋ฆฝ ํจํค์ง์
๋๋ค. ํต์ฌ ๋ชฉํ๋ Airflow ์
๊ทธ๋ ์ด๋ ์์๋ ์ฝ๋ ๋ณ๊ฒฝ ์์ด ์๋ํ๋ ์ด์ ๊ฐ๋ฅํ๊ณ ์์ ์ ์ธ DAG๋ฅผ ์์ฑํ๋ ๊ฒ์
๋๋ค. DAG, dag, task, BaseOperator, Connection, Variable ๋ฑ ๋ชจ๋ ํต์ฌ ๊ฐ์ฒด๊ฐ airflow.sdk ํ์์ ๋ฐฐ์น๋์ด ์์ต๋๋ค.
๋ ๊ฑฐ์ ์ํฌํธ ๊ฒฝ๋ก(airflow.decorators.task, airflow.models.dag.DAG)๋ 3.2์์๋ ๋์ํ์ง๋ง ์ง์ ์ค๋จ์ผ๋ก ํ์๋์ด ์์ผ๋ฉฐ, ํฅํ ๋ฆด๋ฆฌ์ค์์ ์ ๊ฑฐ๋ ์์ ์
๋๋ค.
# etl_daily_revenue.py
import pendulum
from airflow.sdk import dag, task
@dag(
schedule="@daily",
start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
catchup=False,
tags=["finance", "etl"],
)
def etl_daily_revenue():
@task()
def extract_transactions() -> list[dict]:
# Pulls raw transactions from the payments API
import requests
response = requests.get("https://api.internal/payments/daily")
return response.json()["transactions"]
@task(multiple_outputs=True)
def transform(transactions: list[dict]) -> dict:
# Aggregates by currency and computes totals
totals = {}
for tx in transactions:
currency = tx["currency"]
totals[currency] = totals.get(currency, 0) + tx["amount"]
return {"totals": totals, "count": len(transactions)}
@task()
def load(totals: dict, count: int):
# Inserts aggregated row into the warehouse
from warehouse import insert_revenue_summary
insert_revenue_summary(totals=totals, transaction_count=count)
raw = extract_transactions()
result = transform(raw)
load(result["totals"], result["count"])
etl_daily_revenue()@dag ๋ฐ์ฝ๋ ์ดํฐ๋ ๊ธฐ์กด์ with DAG(...) ์ปจํ
์คํธ ๋งค๋์ ๋ฅผ ๋์ฒดํฉ๋๋ค. @task ๋ฐ์ฝ๋ ์ดํฐ๋ ์ผ๋ฐ Python ํจ์๋ฅผ Airflow ํ์คํฌ๋ก ๋ณํํ๋ฉฐ, XCom ์ง๋ ฌํ๋ ์๋์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค. transform(raw)๋ฅผ ํธ์ถํ๋ฉด ์์กด ๊ด๊ณ๊ฐ ์ ์ธ๋๊ณ , Airflow๋ ํจ์ ํธ์ถ ๊ทธ๋ํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก DAG ์ฃ์ง๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
๊ฐ๋ณ ์ํฌ๋ก๋๋ฅผ ์ํ ๋์ ํ์คํฌ ๋งคํ
์ฒ๋ฆฌํ ํญ๋ชฉ ์๊ฐ ์คํ๋ง๋ค ๋ฌ๋ผ์ง๋ ๊ฒฝ์ฐ, ์ ์ DAG๋ก๋ ๋์ํ ์ ์์ต๋๋ค. Airflow 2.4์์ ์์ ํ๋๊ณ 3.x์์ ๋์ฑ ๊ฐ์ ๋ ๋์ ํ์คํฌ ๋งคํ์ .expand()๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐํ์์ ํ์คํฌ๋ฅผ ํ์ฅํจ์ผ๋ก์จ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค.
# etl_multi_region.py
import pendulum
from airflow.sdk import dag, task
@dag(
schedule="@hourly",
start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
catchup=False,
tags=["regional", "etl"],
)
def etl_multi_region():
@task()
def get_regions() -> list[str]:
# Returns the active regions from config
return ["us-east-1", "eu-west-1", "ap-southeast-1"]
@task()
def process_region(region: str) -> dict:
# Processes events for a single region
from data_processor import aggregate_events
return aggregate_events(region)
@task()
def merge_results(results: list[dict]):
# Combines all regional outputs into a single report
from reporter import publish_global_report
publish_global_report(results)
regions = get_regions()
# .expand() creates one mapped task instance per region
processed = process_region.expand(region=regions)
merge_results(processed)
etl_multi_region()์คํ ์์ ์ Airflow๋ ๋ฆฌ์ ๋ณ๋ก process_region์ ๋ณ๋ ฌ ์ธ์คํด์ค๋ฅผ 3๊ฐ ์์ฑํฉ๋๋ค. ๋ค์ ๋ ๋ค ๋ฒ์งธ ๋ฆฌ์ ์ด ์ถ๊ฐ๋๋๋ผ๋ DAG ์ฝ๋๋ฅผ ์์ ํ ํ์๊ฐ ์์ต๋๋ค. merge_results ํ์คํฌ๋ ๋ชจ๋ ๋งคํ๋ ์ธ์คํด์ค๊ฐ ์๋ฃ๋ ๋๊น์ง ๋๊ธฐํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก Airflow๋ ๋งคํ๋น 1024๊ฐ์ ์ธ์คํด์ค๋ก ์ ํํฉ๋๋ค. ๋ ํฐ ๋ฐ์ดํฐ์
์ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ DAG ์ค์ ์์ max_map_length๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ์ฌ ์ด ๊ฐ์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
์์ ํํฐ์ : Airflow 3.2์ ๋ฐ์ดํฐ ์ธ์ ์ค์ผ์ค๋ง
Airflow 3.2 ์ด์ ์๋ ๋ฐ์ดํฐ ์ธ์ ์ค์ผ์ค๋ง์ด ์์ ์์ค์์ ์๋ํ์ต๋๋ค. ํ๋ก๋์ DAG๊ฐ ์์ ์ ์ ๋ฐ์ดํธํ๋ฉด, ์ด๋ค ๋ฐ์ดํฐ ์ฌ๋ผ์ด์ค๊ฐ ๋ณ๊ฒฝ๋์๋์ง์ ๊ด๊ณ์์ด ๋ชจ๋ ์ปจ์๋จธ DAG๊ฐ ํธ๋ฆฌ๊ฑฐ๋์์ต๋๋ค. ์์ ํํฐ์ ์ ํํฐ์ ์์ค์ ์ธ๋ถ์ฑ์ ์ง์ํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค.
์ธ ๊ฐ์ ์์ DAG๊ฐ ์๋ก ๋ค๋ฅธ ์คํฌ์ธ ๋ฆฌ๊ทธ์ ์๊ฐ๋ณ ์ ์ ํต๊ณ๋ฅผ ์์ฑํ๋ ์๋๋ฆฌ์ค๋ฅผ ์๊ฐํด ๋ด ์๋ค. ํ์ ๋ถ์ DAG๋ ์ธ ๋ฆฌ๊ทธ ๋ชจ๋ ๋์ผํ ์๊ฐ๋์ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํ ๊ฒฝ์ฐ์๋ง ํธ๋ฆฌ๊ฑฐ๋์ด์ผ ํฉ๋๋ค.
# downstream_analytics.py
import pendulum
from airflow.sdk import dag, task
from airflow.timetables.assets import CronPartitionTimetable
# Define partitioned assets with hourly granularity
nba_stats = Asset("s3://datalake/nba/hourly/", partitions=CronPartitionTimetable("0 * * * *"))
epl_stats = Asset("s3://datalake/epl/hourly/", partitions=CronPartitionTimetable("0 * * * *"))
nfl_stats = Asset("s3://datalake/nfl/hourly/", partitions=CronPartitionTimetable("0 * * * *"))
@dag(
# Triggers only when all three assets have matching partition
schedule=(nba_stats & epl_stats & nfl_stats),
start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
catchup=False,
)
def unified_sports_analytics():
@task()
def aggregate_all_leagues(**context):
partition = context["partition"]
# Process only the specific hourly slice across all leagues
from analytics import build_cross_league_report
build_cross_league_report(partition=partition)
aggregate_all_leagues()
unified_sports_analytics()ํํฐ์ ๊ธฐ๋ฐ ์ค์ผ์ค๋ง์ ๋ถํ์ํ ํ์ดํ๋ผ์ธ ์คํ์ ์ ๊ฑฐํฉ๋๋ค. ํ์ DAG๋ ํ์ํ ํํฐ์ ์ด ๋ชจ๋ ์์ ์์ค์์ ์ค๋น๋ ๊ฒฝ์ฐ์๋ง ์คํ๋๋ฉฐ, ๋ถ๋ถ์ ์ธ ๋ฐ์ดํฐ๋ก๋ ์คํ๋์ง ์์ต๋๋ค.
Data Engineering ๋ฉด์ ์ค๋น๊ฐ ๋์ จ๋์?
์ธํฐ๋ํฐ๋ธ ์๋ฎฌ๋ ์ดํฐ, flashcards, ๊ธฐ์ ํ ์คํธ๋ก ์ฐ์ตํ์ธ์.
I/O ์ง์ฝ์ ์ํฌ๋ก๋๋ฅผ ์ํ ๋ค์ดํฐ๋ธ ๋น๋๊ธฐ ํ์คํฌ
Airflow 3.2์์๋ PythonOperator์ ๋ค์ดํฐ๋ธ ๋น๋๊ธฐ ์ง์์ด ์ถ๊ฐ๋์์ต๋๋ค. ์ด์ ์๋ ๋๋์ API ํธ์ถ์ด๋ ๋ฐฐ์น ํ์ผ ๋ค์ด๋ก๋ ๊ฐ์ ๋ณ๋ ฌ I/O ์์
์ ์ํํ๋ ค๋ฉด ์ปค์คํ
deferrable ์คํผ๋ ์ดํฐ๋ฅผ ์์ฑํด์ผ ํ์ต๋๋ค. ์ด์ ๋ async ํจ์๋ฅผ ์ง์ ์ ๋ฌํ ์ ์์ต๋๋ค.
# async_file_download.py
import asyncio
import aiohttp
from airflow.sdk import dag, task
import pendulum
@dag(
schedule="@daily",
start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
catchup=False,
tags=["async", "download"],
)
def async_file_download():
@task()
async def download_files():
urls = [f"https://data.source/files/{i}.csv" for i in range(500)]
async with aiohttp.ClientSession() as session:
# Downloads 500 files concurrently instead of sequentially
tasks = [fetch_and_save(session, url) for url in urls]
results = await asyncio.gather(*tasks)
return {"downloaded": len(results)}
download_files()
async def fetch_and_save(session: aiohttp.ClientSession, url: str):
async with session.get(url) as resp:
content = await resp.read()
filename = url.split("/")[-1]
with open(f"/data/downloads/{filename}", "wb") as f:
f.write(content)
return filename
async_file_download()๋น๋๊ธฐ ๋ฐฉ์์ ๋จ์ผ ์์ปค ์ฌ๋กฏ์์ 500๊ฐ์ ํ์ผ์ ๋์์ ๋ค์ด๋ก๋ํฉ๋๋ค. ๋๊ธฐ ๋ฒ์ ์ 500ํ ์์ฐจ HTTP ์์ฒญ๊ณผ ๋น๊ตํ๋ฉด, I/O ๋ฐ์ด๋ ์ํฌ๋ก๋์์ ์์ญ ๋ฐฐ์ ์๋ ํฅ์์ ์คํํ ์ ์์ผ๋ฉฐ ์ถ๊ฐ ์์ปค๋ฅผ ํ๋ก๋น์ ๋ํ ํ์๊ฐ ์์ต๋๋ค.
Airflow ์ํคํ ์ฒ: ๊ตฌ์ฑ ์์์ ์คํ ํ๋ฆ
Airflow์ ์ํคํ ์ฒ๋ฅผ ์ดํดํ๋ ๊ฒ์ ํ๋ก๋์ ์ด์๊ณผ ๋ฉด์ ๋๋น ๋ชจ๋์ ํ์์ ์ ๋๋ค. ๋ชจ๋ DAG ์คํ์์ ๋ค์ฏ ๊ฐ์ง ๊ตฌ์ฑ ์์๊ฐ ์ํธ์์ฉํฉ๋๋ค.
- ์ค์ผ์ค๋ฌ โ DAG ํ์ผ์ ํ์ฑํ๊ณ , ์์กด ๊ด๊ณ๋ฅผ ํด์ํ๋ฉฐ, ์คํ์ ์ํด ํ์คํฌ๋ฅผ ํ์ ๋ฃ์ต๋๋ค. Airflow 3.x์์ ์ค์ผ์ค๋ฌ๋ ๋ฉํ๋ฐ์ดํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํด ๋ฌด์ํ๋ก ์๋ํฉ๋๋ค.
- ์ต์คํํฐ โ ํ์คํฌ๊ฐ ์คํ๋๋ ์์น๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
LocalExecutor๋ ๋จ์ผ ๋จธ์ ํ๊ฒฝ์ ์ฒ๋ฆฌํฉ๋๋ค.CeleryExecutor๋ ์์ปค ๋ ธ๋ ๊ฐ์ ๋ถ์ฐํฉ๋๋ค.KubernetesExecutor๋ ํ์คํฌ๋ง๋ค ํ๋๋ฅผ ์์ฑํฉ๋๋ค. - ์์ปค โ ์ค์ ํ์คํฌ ์ฝ๋๋ฅผ ์คํํฉ๋๋ค. Airflow 3.0์ Task Execution API๋ฅผ ํตํด ์์ปค๋ ์์ ๋ ๊ณ์ฝ ๊ธฐ๋ฐ์ผ๋ก ํต์ ํ๋ฉฐ, ์ปจํ ์ด๋, ์ฃ์ง ํ๊ฒฝ, ์ธ๋ถ ๋ฐํ์์์์ ์คํ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ๋ฉํ๋ฐ์ดํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค โ PostgreSQL(๊ถ์ฅ) ๋๋ MySQL์ ์ฌ์ฉํฉ๋๋ค. DAG ์ ์, ํ์คํฌ ์ํ, XCom ๊ฐ, ์ฐ๊ฒฐ ์ ๋ณด, ๊ฐ์ฌ ๋ก๊ทธ๋ฅผ ์ ์ฅํฉ๋๋ค.
- ์น ์๋ฒ โ DAG ์คํ ๋ชจ๋ํฐ๋ง, ๋ก๊ทธ ํ์ธ, ์๋ ์คํ ํธ๋ฆฌ๊ฑฐ, ์ฐ๊ฒฐ ๊ด๋ฆฌ๋ฅผ ์ํ Airflow UI์ ๋๋ค.
ํ๋ก๋์
ํ๊ฒฝ์์๋ SequentialExecutor ์ฌ์ฉ์ ํผํด์ผ ํฉ๋๋ค. ํ ๋ฒ์ ํ๋์ ํ์คํฌ๋ง ์คํํ ์ ์์ผ๋ฉฐ ๊ฐ๋ฐ ์ฉ๋๋ก๋ง ์กด์ฌํฉ๋๋ค. Kubernetes ๋ค์ดํฐ๋ธ ํ๊ฒฝ์์๋ KubernetesExecutor๊ฐ ๊ฐ์ฅ ๊ฐ๋ ฅํ ๊ฒฉ๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ฐ ํ์คํฌ๊ฐ ๋
๋ฆฝ์ ์ธ ๋ฆฌ์์ค์ ์์กด์ฑ์ ๊ฐ์ง ๊ฐ๋ณ ํ๋์์ ์คํ๋๊ธฐ ๋๋ฌธ์
๋๋ค.
2026๋ Airflow vs. Prefect vs. Dagster ๋น๊ต
Airflow์๋ ๋ ๊ฐ์ง ์ฃผ์ ๊ฒฝ์ ๋๊ตฌ๊ฐ ์์ต๋๋ค. ์ฌ๋ฐ๋ฅธ ์ ํ์ ํ ๊ท๋ชจ, ๊ธฐ์กด ์ธํ๋ผ, ๊ตฌ์ถํ๋ ํ์ดํ๋ผ์ธ์ ์ ํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค.
| ๊ธฐ๋ฅ | Airflow 3.2 | Prefect 3.x | Dagster 1.9 |
|---|---|---|---|
| DAG ์ ์ | Python ๋ฐ์ฝ๋ ์ดํฐ(airflow.sdk) | Python ๋ฐ์ฝ๋ ์ดํฐ(@flow, @task) | Python ๋ฐ์ฝ๋ ์ดํฐ(@asset, @op) |
| ์ค์ผ์ค๋ง | Cron, ์์
์ธ์, ํํฐ์
์ธ์ | Cron, ์ด๋ฒคํธ ๊ธฐ๋ฐ | Cron, ์ผ์ ๊ธฐ๋ฐ, ์์
์ธ์ |
| ์คํ ๋ชจ๋ธ | ์ค์ ์ค์ผ์ค๋ฌ + ๋ถ์ฐ ์์ปค | ํ์ด๋ธ๋ฆฌ๋(์๋ฒ + ์ํฌ ํ) | ์ค์ dagster-daemon |
| ๋์ ํ์คํฌ | .expand() ๋งคํ๋ ํ์คํฌ | ๋ค์ดํฐ๋ธ Python ๋ฃจํ | ๋์ ํํฐ์
|
| ๋น๋๊ธฐ ์ง์ | 3.2์์ ๋ค์ดํฐ๋ธ ์ง์ | 2.0๋ถํฐ ๋ค์ดํฐ๋ธ ์ง์ | ๋น๋๊ธฐ I/O ์คํผ๋ ์ด์
|
| ๋ฉํฐํ ๊ฒฉ๋ฆฌ | ๋ด์ฅ(3.2 ์คํ์ ๊ธฐ๋ฅ) | ์ํฌ์คํ์ด์ค ๊ธฐ๋ฐ(Cloud) | ๋ธ๋์น ๋ฐฐํฌ |
| ์ปค๋ฎค๋ํฐ ๊ท๋ชจ | ์ต๋(GitHub 35,000+ ์คํ) | ์ฑ์ฅ ์ค(18,000+ ์คํ) | ์ฑ์ฅ ์ค(12,000+ ์คํ) |
| ์ ํฉํ ํ๊ฒฝ | ๋๊ท๋ชจ ๋ณต์กํ ๋ฉํฐํ ํ์ดํ๋ผ์ธ | ๋น ๋ฅธ ๊ฐ๋ฐ ์ฌ์ดํด์ ์ํ๋ ์๊ท๋ชจ ํ | ๋ฐ์ดํฐ ์์
์ค์ฌ ์กฐ์ง |
Airflow์ ๊ฐ์ ์ ์์ฝ์์คํ ์ ์์ต๋๋ค. ์ฃผ์ ํด๋ผ์ฐ๋ ์๋น์ค, ๋ฐ์ดํฐ๋ฒ ์ด์ค, API๋ฅผ ํฌ๊ดํ๋ 80๊ฐ ์ด์์ ํ๋ก๋ฐ์ด๋ ํจํค์ง๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. Prefect๋ ๋ณด์ผ๋ฌํ๋ ์ดํธ๊ฐ ์ ์ ์ฐ์ํ ๊ฐ๋ฐ์ ๊ฒฝํ์ ๊ฐ์ ์ด ์์ต๋๋ค. Dagster์ ์์ ์ค์ฌ ๋ชจ๋ธ์ ํ์คํฌ ์ํ์ค๊ฐ ์๋ ๋ฐ์ดํฐ ํ๋ก๋ํธ ๊ด์ ์์ ์ฌ๊ณ ํ๋ ํ์ ์ ํฉํฉ๋๋ค.
๋ฐ์ดํฐ ์์ง๋์ด ๋ฉด์ ์์ ์ถ์ ๋๋ Apache Airflow ์ง๋ฌธ
๋ค์ ์ง๋ฌธ๋ค์ Airflow๋ฅผ ํ๋ก๋์ ํ๊ฒฝ์์ ์ด์ํ๋ ๊ธฐ์ ์ ๋ฐ์ดํฐ ์์ง๋์ด ๋ฉด์ ์์ ์ค์ ๋ก ์ถ์ ๋๋ ๋ด์ฉ์ ๋ฐ์ํฉ๋๋ค.
DAG๋ ๋ฌด์์ด๋ฉฐ, Airflow์์ ์ด๋ป๊ฒ ํ์ฉ๋๋๊ฐ
DAG(Directed Acyclic Graph, ๋ฐฉํฅ ๋น์ํ ๊ทธ๋ํ)๋ ์ํ ์ฐธ์กฐ๊ฐ ์๋ ๊ฒ์ด ๋ณด์ฅ๋ ํ์คํฌ์ ์์กด ๊ด๊ณ์ ์งํฉ์ผ๋ก ์ํฌํ๋ก๋ฅผ ์ ์ํฉ๋๋ค. Airflow๋ dags/ ํด๋์ Python ํ์ผ์ ํ์ฑํ์ฌ ์์กด ๊ด๊ณ ๊ทธ๋ํ๋ฅผ ๊ตฌ์ถํ๊ณ , ์ค์ผ์ค๋ฌ๊ฐ ์คํ ์์๋ฅผ ๊ฒฐ์ ํฉ๋๋ค. ๊ฐ DAG ์คํ์ ๋
ผ๋ฆฌ์ ๋ ์ง์ ์ฐ๊ฒฐ๋ DagRun ๊ฐ์ฒด๋ฅผ ์์ฑํฉ๋๋ค. "๋น์ํ" ์ ์ฝ ์กฐ๊ฑด์ผ๋ก ์ธํด ์ค์ผ์ค๋ฌ๋ ํญ์ ์ ํจํ ์คํ ์์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.
XCom์ ์๋ ๋ฐฉ์๊ณผ ์ฌ์ฉ์ ํผํด์ผ ํ๋ ๊ฒฝ์ฐ๋ ๋ฌด์์ธ๊ฐ
XCom(Cross-Communication)์ ํ์คํฌ ๊ฐ์ ์๋์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ๋ฉ์ปค๋์ฆ์ ๋๋ค. ํ์คํฌ๊ฐ ๋ฐํ๊ฐ์ XCom์ ํธ์ํ๊ณ , ํ์ ํ์คํฌ๊ฐ ์ด๋ฅผ ํํฉ๋๋ค. Task SDK์์๋ ๋ฐ์ฝ๋ ์ดํ ๋ ํ์คํฌ ๊ฐ ํจ์ ๋ฐํ๊ฐ ์ ๋ฌ ์ ์๋์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค. XCom์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฉํ๋ฐ์ดํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ฏ๋ก, ๋์ฉ๋ ๋ฐ์ดํฐ(์ KB ์ด์)์ ์ ์ก์๋ ์ ํฉํ์ง ์์ต๋๋ค. ํฐ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ ๋๋ ์ธ๋ถ ์คํ ๋ฆฌ์ง(S3, GCS)๋ฅผ ์ฌ์ฉํ๊ณ , XCom์๋ ์ฐธ์กฐ ๊ฒฝ๋ก๋ง ์ ๋ฌํด์ผ ํฉ๋๋ค.
schedule, start_date, catchup์ ์ฐจ์ด์ ์ ์ค๋ช
ํ๋ผ
schedule ํ๋ผ๋ฏธํฐ(Airflow 3.x์์ schedule_interval์์ ์ด๋ฆ ๋ณ๊ฒฝ)๋ DAG์ ์คํ ๋น๋๋ฅผ ์ ์ํฉ๋๋ค. cron ๋ฌธ์์ด, timedelta, ํ์ํ
์ด๋ธ ๊ฐ์ฒด, ๋๋ ์์
ํธ๋ฆฌ๊ฑฐ๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค. start_date๋ DAG ์คํ์ ์์ฑํ ์ ์๋ ๊ฐ์ฅ ์ด๋ฅธ ๋
ผ๋ฆฌ์ ๋ ์ง๋ฅผ ์ค์ ํฉ๋๋ค. catchup=True(๊ธฐ๋ณธ๊ฐ)๋ start_date๋ถํฐ ํ์ฌ๊น์ง ๋๋ฝ๋ ๋ชจ๋ ๊ฐ๊ฒฉ์ ๋ํด DAG ์คํ์ ์์ฑํฉ๋๋ค. catchup=False๋ฅผ ์ค์ ํ๋ฉด ๊ณผ๊ฑฐ ๊ฐ๊ฒฉ์ ๊ฑด๋๋ฐ๊ณ ํ์ฌ ์์ ๋ถํฐ๋ง ์ค์ผ์ค๋งํฉ๋๋ค. ํ๋ก๋์
์ ์ผ๋ฐ์ ์ธ ํจํด์ ์ด์ DAG์ catchup=False๋ฅผ, ์ด๋ ฅ ๋ฐฑํ์ catchup=True๋ฅผ ์ค์ ํ๋ ๊ฒ์
๋๋ค.
KubernetesExecutor์ CeleryExecutor์ ์ฐจ์ด์ ์ ๋ฌด์์ธ๊ฐ
CeleryExecutor๋ ๋ฉ์์ง ๋ธ๋ก์ปค(Redis ๋๋ RabbitMQ)๋ฅผ ํตํด ์ฐ๊ฒฐ๋ ์ฅ์๊ฐ ์คํ ์์ปค ํ๋ก์ธ์ค ํ์ ์ ์งํฉ๋๋ค. ํ์คํฌ๊ฐ ํ์ ๋ค์ด๊ฐ๊ณ ์ฌ์ฉ ๊ฐ๋ฅํ ์์ปค์์ ์คํ๋ฉ๋๋ค. KubernetesExecutor๋ ํ์คํฌ๋ง๋ค Docker ์ด๋ฏธ์ง์ ๋ฆฌ์์ค ์๊ตฌ์ฌํญ์ ๊ธฐ๋ฐ์ผ๋ก ์๋ก์ด Kubernetes ํ๋๋ฅผ ์์ฑํฉ๋๋ค. Celery๋ ๋ฎ์ ์ง์ฐ ์๊ฐ(ํ๋ ์์ ์ค๋ฒํค๋ ์์)์ ์ ๊ณตํ๋ฉฐ ๋์ง์ ์ํฌ๋ก๋์ ์ ํฉํฉ๋๋ค. KubernetesExecutor๋ ๋ ๊ฐ๋ ฅํ ๊ฒฉ๋ฆฌ์ ํ์คํฌ๋ณ ๋ฆฌ์์ค ์ ์ด๋ฅผ ์ ๊ณตํ๋ฉฐ, ์ ์ ์์ปค ํ ๊ด๋ฆฌ๊ฐ ๋ถํ์ํ์ฌ ์ด์ง์ ์ํฌ๋ก๋์ ์ด์์ ์
๋๋ค.
ํ์คํฌ ์คํจ ์ฒ๋ฆฌ์๋ ์ด๋ค ์ ๋ต์ด ์๋๊ฐ
Airflow๋ ์ฌ๋ฌ ๊ฐ์ง ์คํจ ์ฒ๋ฆฌ ๋ฉ์ปค๋์ฆ์ ์ ๊ณตํฉ๋๋ค. retries์ retry_delay๋ก ์ง์ ๋ฐฑ์คํ๋ฅผ ์ ์ฉํ ์๋ ์ฌ์๋๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค. on_failure_callback์ ํ์คํฌ ์คํจ ์ ์ปค์คํ
๋ก์ง(Slack ์๋ฆผ, PagerDuty ์ธ์๋ํธ)์ ํธ๋ฆฌ๊ฑฐํฉ๋๋ค. trigger_rule๋ก ํ์ ํ์คํฌ์ ์๋ต ๋ฐฉ์์ ์ ์ดํฉ๋๋ค. all_success(๊ธฐ๋ณธ๊ฐ), one_success, all_failed, none_failed_min_one_success๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ผ์์ ์ธ ์ธํ๋ผ ์ฅ์ ์ ๋ํด์๋ retry_exponential_backoff=True ํ๋ผ๋ฏธํฐ๋ก ์ฌ์๋ ๊ฐ ๋๊ธฐ ์๊ฐ์ ์ ์ง์ ์ผ๋ก ๋๋ฆฝ๋๋ค. SLA(3.x์์๋ Deadline Alerts)๋ ์คํ ์๊ฐ์ ๋ชจ๋ํฐ๋งํ๊ณ , ํ์คํฌ๊ฐ ์์ ์คํ ์๊ฐ์ ์ด๊ณผํ๋ฉด ์ฝ๋ฐฑ์ ๋ฐ๋ํฉ๋๋ค.
Airflow ํ๋ก๋์ ๋ฐฐํฌ ๋ชจ๋ฒ ์ฌ๋ก
Airflow๋ฅผ ๋๊ท๋ชจ๋ก ์์ ์ ์ผ๋ก ์ด์ํ๋ ค๋ฉด ์ฌ๋ฐ๋ฅธ DAG ์์ฑ์ ๋์ด์ ์ฌ๋ฌ ์ด์ ํจํด์ ์ฃผ์๋ฅผ ๊ธฐ์ธ์ฌ์ผ ํฉ๋๋ค.
๋ฉฑ๋ฑ์ฑ์ ๊ฐ์ถ ํ์คํฌ. ๋ชจ๋ ํ์คํฌ๋ ๋์ผํ ์
๋ ฅ์ผ๋ก ์ฌ๋ฌ ๋ฒ ์คํํด๋ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค. ๋จ์ INSERT ๋์ INSERT ... ON CONFLICT๋ MERGE๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ถ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ๋
ผ๋ฆฌ์ ๋ ์ง ๊ธฐ์ค์ผ๋ก ํํฐ์
๋ํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ฐ์ดํฐ ์ค๋ณต ์์ด ์์ ํ ์ฌ์๋์ ๋ฐฑํ์ด ๊ฐ๋ฅํด์ง๋๋ค.
์๊ณ ์ง์ค๋ DAG. ์์ญ ๊ฐ์ ํ์คํฌ๋ฅผ ๊ฐ์ง ๋ชจ๋๋ฆฌ์ DAG ๊ตฌ์ถ์ ์ง์ํด์ผ ํฉ๋๋ค. ๋ณต์กํ ํ์ดํ๋ผ์ธ์ ์์ (์ด์ datasets)์ผ๋ก ์ฐ๊ฒฐ๋ ์ฌ๋ฌ DAG๋ก ๋ถํ ํฉ๋๋ค. ์์ DAG๋ ํ์ฑ์ด ๋น ๋ฅด๊ณ ๋๋ฒ๊น ์ด ์ฌ์ฐ๋ฉฐ, ๋ถ๋ถ์ ์ธ ํ์ดํ๋ผ์ธ ์ฌ์์์ด ๊ฐ๋ฅํฉ๋๋ค.
์ฐ๊ฒฐ ๊ด๋ฆฌ. ๋ชจ๋ ์ธ์ฆ ์ ๋ณด๋ฅผ Airflow์ ์ฐ๊ฒฐ ๊ด๋ฆฌ์ ๋๋ ์ธ๋ถ ์ํฌ๋ฆฟ ๋ฐฑ์๋(AWS Secrets Manager, HashiCorp Vault)์ ์ ์ฅํฉ๋๋ค. DAG ํ์ผ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ํ๋์ฝ๋ฉํด์๋ ์ ๋ฉ๋๋ค. Airflow 3.x๋ ๋ฉํ๋ฐ์ดํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๊ฒฐ ํ๋๋ฅผ ์ ์ฅ ์ ์ํธํํฉ๋๋ค.
๋ชจ๋ํฐ๋ง ๋ฐ ์๋ฆผ. Airflow ๋ฉํธ๋ฆญ์ Prometheus ๋๋ StatsD๋ก ๋ด๋ณด๋
๋๋ค. scheduler_heartbeat, dag_processing.total_parse_time, executor.queued_tasks๋ฅผ ์ถ์ ํฉ๋๋ค. Airflow 3.2์์๋ OpenTelemetry ํธ๋ ์ด์ค๊ฐ ์ถ๊ฐ๋์ด ํ์ดํ๋ผ์ธ์ ์๋ํฌ์๋ ๊ด์ธก ๊ฐ๋ฅ์ฑ์ด ํ๋ณด๋์์ต๋๋ค.
ํ์ดํ๋ผ์ธ ์ค์ผ์คํธ๋ ์ด์ ์ ํฌํจํ ๋ฐ์ดํฐ ์์ง๋์ด ๋ฉด์ ์ ์ค๋นํ๊ณ ๊ณ์ ๋ค๋ฉด, SharpSkill์ Airflow ๋ฉด์ ๋๋น ๋ชจ๋์ ํ์ฉํ ์ ์์ต๋๋ค. ํ๋ก๋์ ํ๊ฒฝ์ ์ค์ ์๋๋ฆฌ์ค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ์ฐ์ต ๋ฌธ์ ๊ฐ ์ค๋น๋์ด ์์ต๋๋ค.
์ฐ์ต์ ์์ํ์ธ์!
๋ฉด์ ์๋ฎฌ๋ ์ดํฐ์ ๊ธฐ์ ํ ์คํธ๋ก ์ง์์ ํ ์คํธํ์ธ์.
๊ฒฐ๋ก
- Airflow 3.2๋ DAG ์์ฑ์ ์ํ ์์ API๋ก Task SDK(
airflow.sdk)๋ฅผ ๋์ ํ์ต๋๋ค. ํฅํ ํธํ์ฑ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ํฌํธ ๋ง์ด๊ทธ๋ ์ด์ ์ ๊ถ์ฅํฉ๋๋ค - ์์ ํํฐ์ ์ ํํฐ์ ์์ค์ ๋ฐ์ดํฐ ์ธ์ ์ค์ผ์ค๋ง์ ์ง์ํ์ฌ, ๋ถ๋ถ์ ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ๋ก ์ธํ ๋ถํ์ํ ํ์ดํ๋ผ์ธ ํธ๋ฆฌ๊ฑฐ๋ฅผ ์ ๊ฑฐํฉ๋๋ค
- PythonOperator์ ๋ค์ดํฐ๋ธ ๋น๋๊ธฐ ์ง์์ผ๋ก ์ปค์คํ deferrable ์คํผ๋ ์ดํฐ ์์ด I/O ์ง์ฝ์ ์ํฌ๋ก๋๋ฅผ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค
.expand()๋ฅผ ํ์ฉํ ๋์ ํ์คํฌ ๋งคํ์ DAG ์ฝ๋ ๋ณ๊ฒฝ ์์ด ๊ฐ๋ณ์ ์ธ ์ํฌ๋ก๋ ํฌ๊ธฐ์ ๋ฐํ์์์ ๋์ํฉ๋๋ค- ๋ฉด์ ์ค๋น ์ DAG ๋ฉ์ปค๋์ฆ, ์ต์คํํฐ ํธ๋ ์ด๋์คํ(Celery vs. Kubernetes), XCom ์ ํ์ฌํญ, ๋ฉฑ๋ฑ์ฑ ํจํด์ ๋ฐ๋์ ์์งํด์ผ ํฉ๋๋ค
- ํ๋ก๋์ ๋ฐฐํฌ์์๋ ์๊ณ ๋ฉฑ๋ฑํ DAG, ์ธ๋ถ ์ํฌ๋ฆฟ ๊ด๋ฆฌ, ๊ด์ธก ๊ฐ๋ฅ์ฑ ํ๋ซํผ์ผ๋ก์ ๋ฉํธ๋ฆญ ๋ด๋ณด๋ด๊ธฐ๊ฐ ํจ๊ณผ์ ์ ๋๋ค
์ฐ์ต์ ์์ํ์ธ์!
๋ฉด์ ์๋ฎฌ๋ ์ดํฐ์ ๊ธฐ์ ํ ์คํธ๋ก ์ง์์ ํ ์คํธํ์ธ์.
๊ณต์
๊ด๋ จ ๊ธฐ์ฌ

dbt 2026 ์๋ฒฝ ๊ฐ์ด๋: ๋ฐ์ดํฐ ๋ณํ, ํ ์คํธ ์ ๋ต, ๋ฉด์ ์ง๋ฌธ ์ด์ ๋ฆฌ
dbt๋ฅผ ํ์ฉํ ๋ฐ์ดํฐ ๋ณํ์ ํต์ฌ ๊ฐ๋ ๋ถํฐ ์ค๋ฌด๊น์ง, ๋ ์ด์ด๋ ๋ชจ๋ธ๋ง, ์ธํฌ๋ฆฌ๋ฉํ ์ ๋ต, ํ ์คํธ ๋ฐฉ๋ฒ๋ก , ๊ทธ๋ฆฌ๊ณ 2026๋ ๋ฐ์ดํฐ ์์ง๋์ด๋ง ๋ฉด์ ์์ ์์ฃผ ์ถ์ ๋๋ ์ง๋ฌธ์ ์ฝ๋ ์์ ์ ํจ๊ป ์์ธํ ๋ค๋ฃน๋๋ค.

2026๋ Apache Spark 4 ์๋ฒฝ ๊ฐ์ด๋: ์ ๊ท ๊ธฐ๋ฅ, Structured Streaming, ๋ฉด์ ์ง๋ฌธ
Apache Spark 4์ ํต์ฌ ์ ๊ท ๊ธฐ๋ฅ์ธ ANSI SQL ๋ชจ๋, VARIANT ๋ฐ์ดํฐ ํ์ , ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ ๋ชจ๋, Spark Connect๋ฅผ ์ฌ์ธต ๋ถ์ํฉ๋๋ค. ๋ฐ์ดํฐ ์์ง๋์ด๋ง ๋ฉด์ ์ ์ํ ํ์ ์ง๋ฌธ๊ณผ ๋ต๋ณ๋ ํจ๊ป ์ ๊ณตํฉ๋๋ค.

๋ฐ์ดํฐ ์์ง๋์ด๋ฅผ ์ํ Apache Kafka: ์คํธ๋ฆฌ๋ฐ, ํํฐ์ , ๋ฉด์ ์ง๋ฌธ
๋ฐ์ดํฐ ์์ง๋์ด๋ฅผ ์ํ Apache Kafka ์ฌ์ธต ๋ถ์. Kafka 4.x์ KRaft๋ฅผ ํ์ฉํ ์คํธ๋ฆฌ๋ฐ ์ํคํ ์ฒ, ํํฐ์ ์ ๋ต, ์ปจ์๋จธ ๊ทธ๋ฃน, ๊ธฐ์ ๋ฉด์ ๋น์ถ ์ง๋ฌธ์ ์ค์ ์ฝ๋ ์์ ์ ํจ๊ป ์ค๋ช ํฉ๋๋ค.