DAG 이해 및 유즈케이스 연구
1. DAG의 이해
- DAG (Directed Acyclic Graph, 방향성 비순환 그래프)
- 아파치 에어플로우(Apache Airflow)에서 워크플로우를 설계하는 가장 핵심적인 논리적 단위
- 복잡한 데이터 파이프라인의 작업 순서와 의존 관계를 프로그래밍 코드로 추상화한 지도(Map)
1.1 DAG를 구성하는 3대 속성
- Graph (그래프):
- 노드(Node)와 간선(Edge)으로 이루어진 구조
- 노드: 구체적인 작업(Task)을 의미
- 간선: 작업 간의 실행 순서(Dependency)를 의미
- 노드(Node)와 간선(Edge)으로 이루어진 구조
- Directed (방향성):
- 모든 간선에는 화살표가 존재함
- Task A가 끝나야 Task B가 실행된다는 명확한 인과관계와 데이터 흐름의 방향이 고정되어 있음
- Acyclic (비순환):
- 그래프 내에 루프(Loop)나 순환 경로가 절대 존재할 수 없음
- 예를 들어
Task A 🡲 Task B 🡲 Task C 🡲 Task A와 같은 순환 구조가 만들어지면- 워크플로우가 무한 루프에 빠져 영원히 종료되지 않기 때문
- 예를 들어
- Airflow 엔진은 DAG 파싱 단계에서 이를 에러로 차단
- 그래프 내에 루프(Loop)나 순환 경로가 절대 존재할 수 없음
1.2 데이터 엔지니어링에서 DAG가 필수적인 이유
- 결함 격리 (Fault Isolation):
- 특정 단계(예: API 전송 실패)에서 작업이 멈추었을 때,
- 후속 작업들만 안전하게 대기 상태로 묶고
- 앞선 성공 단계는 재실행하지 않도록 통제
- 멱등성(Idempotency) 보장:
- 동일한 논리적 실행 날짜(Logical Date)에 DAG를 몇 번을 다시 돌려도
- 항상 같은 결과 데이터셋이 나오도록 파이프라인의 안전망을 구축할 수 있음
- 분산 병렬 처리 (Parallelism):
- 선후 관계가 없는 독립적인 노드들(예: 서로 다른 3개 마이크로 서비스의 로그 수집)을
- 스케줄러가 판단하여
- 다수의 Worker 노드에 동시에 뿌려 병렬 처리를 극대화
2. DAG 유즈케이스 연구 (Use Cases)
2.1 Case A: 선형 및 분기형 데이터 동기화 파이프라인 (ETL/ELT)
- 가장 보편적인 유즈케이스
대용량 트랜잭션 데이터를 분석 저장소로 안전하게 이관하는 구조
- 흐름:
- 새벽 2시에 Production DB에서 어제 자 정산 데이터를 백업 처리 (
BashOperator) - 동시에 마케팅 API 서버로부터 광고 효율 로그를 다운로드 (
PythonOperator) - 두 원시 데이터가 모두 스토리지(MinIO/S3)에 적재 완료되었는지 검증 (
MinioSensor) - 정제 및 병합 쿼리를 실행하여 데이터 웨어하우스(빅쿼리/Postgres)에 적재 (
PostgresOperator) - 성공/실패 여부를 사내 메신저로 전송 (
SlackWebhookOperator)
- 새벽 2시에 Production DB에서 어제 자 정산 데이터를 백업 처리 (
2.2 Case B: 주기적 인프라 상태 점검 및 자동 스케일링 체인
데이터 분석 외에 시스템 운영 인프라를 관제하고 최적화하는 유즈케이스
흐름:
- 매주 일요일 가상 머신(VM) 및 쿠버네티스(K8s) 클러스터의 미사용 자원 스캔
- 임계치 이하의 노드가 발견되면 리소스 축소(Downscaling) 스크립트 트리거
- 백업 볼륨 복구 테스트 진행 및 디스크 정제 작업 수행
2.3 Case C: 로컬 AI 모델 재학습 및 하이브리드 RAG 지식 임베딩 자동화
- 지속적으로 발생하는 사내 기술 매뉴얼과 매일 쌓이는 고객 상담 이력을
로컬 AI 인프라에 동적으로 반영하기 위한 파이프라인
- 흐름:
- 현장 엔지니어가 작성한 최신 장비 가이드 문서(
*.txt)를 MinIO 특정 버킷에서 감지 - 텍스트 문서를 로드하여 토큰 제한에 맞게 의미론적 청킹(Chunking) 수행
- 로컬 Ollama 임베딩 모델을 호출하여 지식 데이터를 벡터화
- Qdrant와 같은 VectorDB의 지정된 컬렉션에 하이브리드 인덱스로 신규 업서트(Upsert) 반영
- 현장 엔지니어가 작성한 최신 장비 가이드 문서(
3. 실습 예제:
- 이원화 저장소 백업 및 텍스트 데이터 정제 파이프라인
- 로컬 파일 시스템에서 원시 로그 데이터를 읽어와 MinIO 객체 스토리지에 백업한 뒤,
- 의미론적으로 데이터를 가공 및 변환(Transform)하여
- RAG나 분석 시스템이 사용할 수 있도록 정제하는
- 가상의 DAG 아키텍처를 파이썬 코드로 구현
시나리오 구성도
[Task 1: check_file] 🡲 [Task 2: backup_to_minio] 🡲 [Task 3: transform_data]- 선행 환경 점검
- airflow-scheduler, airflow-webserver, minio 컨테이너의 State가 모두 Up 상태여야 함
docker compose ps - DAG 소스코드 작성
#//file: "dags/minio_processing_pipeline.py"
from datetime import datetime, timedelta
import os
import io
from airflow import DAG
from airflow.operators.bash import BashOperator
from airflow.operators.python import PythonOperator
from minio import Minio
# ============================================================
# [설정 구역] 인프라 연결 정보 정의
# ============================================================
MINIO_URL = "localhost:9000"
MINIO_ACCESS_KEY = "minioadmin"
MINIO_SECRET_KEY = "minioadminpassword"
BUCKET_NAME = "factory-raw-logs"
LOCAL_FILE_DIR = "/opt/airflow/data"
FILE_NAME = "factory_sensor_logs.txt"
LOCAL_FILE_PATH = os.path.join(LOCAL_FILE_DIR, FILE_NAME)
# ============================================================
# [비즈니스 로직 함수 정의]
# ============================================================
def fn_backup_to_minio():
"""로컬의 원시 센서 데이터를 MinIO 스토리지 버킷에 안전하게 백업합니다."""
client = Minio(MINIO_URL, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=False)
# 버킷이 없을 경우 자동 생성
if not client.bucket_exists(BUCKET_NAME):
client.make_bucket(BUCKET_NAME)
print(f"[MinIO] '{BUCKET_NAME}' 버킷이 새로 생성되었습니다.")
# 테스트를 위한 더미 데이터 생성 (물리 파일이 없을 경우 방어 코드)
if not os.path.exists(LOCAL_FILE_PATH):
os.makedirs(LOCAL_FILE_DIR, exist_ok=True)
with open(LOCAL_FILE_PATH, "w", encoding="utf-8") as f:
f.write("TIMESTAMP:2026-07-07 15:00:00 | SENSOR:FM-2026 | VAL:45.2 | STATUS:NORMAL\n"
"TIMESTAMP:2026-07-07 15:05:00 | SENSOR:FM-2026 | VAL:99.9 | STATUS:WARNING_OVERFLOW\n")
print(f"[Local] 테스트용 더미 로그 파일 생성 완료: {LOCAL_FILE_PATH}")
# 파일 업로드 실행
with open(LOCAL_FILE_PATH, "rb") as file_data:
data_bytes = file_data.read()
client.put_object(
bucket_name=BUCKET_NAME,
object_name=FILE_NAME,
data=io.BytesIO(data_bytes),
length=len(data_bytes),
content_type="text/plain"
)
print(f"[성공] 원시 파일이 MinIO [{BUCKET_NAME}/{FILE_NAME}]에 백업 동기화되었습니다.")
def fn_transform_and_clean_data():
"""MinIO에서 백업된 데이터를 다운로드하여 WARNING 상태인 라인만 추출해 정제합니다."""
client = Minio(MINIO_URL, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=False)
# 스토리지로부터 데이터 가져오기
try:
response = client.get_object(BUCKET_NAME, FILE_NAME)
raw_content = response.read().decode('utf-8')
finally:
response.close()
response.release_conn()
# 데이터 변환 변형 (Transform Logics)
lines = raw_content.strip().split("\n")
cleaned_records = []
for line in lines:
if "WARNING" in line: # 위험 감지 데이터만 필터링하는 규칙
parts = line.split(" | ")
refined_line = f"[위험 알림] 시간: {parts[0].split(':')[1]}, 센서명: {parts[1].split(':')[1]}, 측정값: {parts[2].split(':')[1]}"
cleaned_records.append(refined_line)
# 변환된 최종 데이터 출력 (운영 환경에서는 이 결과를 다른 DB나 하위 파일로 이관합니다.)
print("=" * 60)
print("[정제 완료된 하이브리드 컨텍스트 데이터 목록]")
print("=" * 60)
if cleaned_records:
for record in cleaned_records:
print(record)
else:
print("정제 대상(WARNING) 데이터가 존재하지 않습니다.")
print("=" * 60)
# ============================================================
# [DAG 정의 및 오케스트레이션 설계]
# ============================================================
default_args = {
'owner': 'seokhwan_yang',
'depends_on_past': False,
'start_date': datetime(2026, 7, 1),
'email_on_failure': False,
'email_on_retry': False,
'retries': 1,
'retry_delay': timedelta(minutes=3),
}
with DAG(
dag_id='factory_log_hybrid_processing_v1',
default_args=default_args,
description='MinIO 백업 및 데이터 텍스트 정제를 위한 기본 하이브리드 파이프라인 DAG',
schedule_interval='@daily', # 매일 자정에 정기 실행
catchup=False, # 과거 누적 배치 미실행 (과부하 방지)
tags=['production', 'factory', 'minio'],
) as dag:
# Task 1: Bash 명령어를 통한 파일 시스템 존재 상태 선행 점검
task_check_file = BashOperator(
task_id='check_local_file_exists',
bash_command='echo "워크플로우 기동 시작" && date',
)
# Task 2: Python 커스텀 함수를 활용한 MinIO 스토리지 적재 백업 작업
task_backup_minio = PythonOperator(
task_id='backup_raw_data_to_minio',
python_callable=fn_backup_to_minio,
)
# Task 3: 적재 완료된 파일의 비즈니스 규칙 기반 필터링 및 텍스트 트랜스폼 작업
task_transform_clean = PythonOperator(
task_id='transform_and_extract_warnings',
python_callable=fn_transform_and_clean_data,
)
# ============================================================
# [의존성 선언 구역] 상하 흐름 명시적 정의 (DAG 뼈대 빌드)
# ============================================================
task_check_file >> task_backup_minio >> task_transform_clean
실습 코드 구동 분석 및 디버깅 가이드
- Step 1: 환경 구성 확인
- Airflow 컨테이너 볼륨 내에 위 코드를 배치한 후,
- Airflow Web UI (
http://localhost:8080)에 접속하면 factory_log_hybrid_processing_v1이라는 이름의 DAG가 그래프 뷰에 표시됨
- Step 2: 실행 메커니즘 관측
- DAG를 수동 트리거(Trigger DAG)하면 스케줄러가 간선(
>>) 순서에 맞춰task_check_file을 가장 먼저 큐에 배치 - 첫 번째 태스크가 정상 종료(Success)되면 그 결과를 인지하고
즉시 다음 노드인backup_raw_data_to_minio태스크 인스턴스를 활성화하여 물리 자원을 배정 - 파이썬 함수 내부에 구현된
Minio클라이언트 프로토콜에 의해 파일이 버킷에 동기화 완료되면,
마지막으로transform_and_extract_warnings가 기동되어 로그를 파싱하고 정제 결과를 콘솔에 출력
- DAG를 수동 트리거(Trigger DAG)하면 스케줄러가 간선(
- Step 3: 실행 결과 로그(Log) 시각화 예시
- 마지막
task_transform_clean인스턴스의 텍스트 로그를 클릭하여 열어보면 다음과 같은 출력 구조가 표시되는 것을 확인할 수 있음 - 이를 통해 DAG 파이프라인이 정상적으로 원인-결과 흐름을 조율했음이 증명됨
- 마지막
*** Found local logs: [2026-07-07 17:15:22,345] {python.py:177} INFO - Done. Integration tasks succeeded. ============================================================ [정제 완료된 하이브리드 컨텍스트 데이터 목록] ============================================================ [위험 알림] 시간: 2026-07-07 15:05:00, 센서명: FM-2026, 측정값: 99.9 ============================================================- Step 1: 환경 구성 확인