배치 처리 - AWS Glue & EMR
대용량 데이터 변환과 ETL 파이프라인 구축
목차
- 배치 처리 개요
- AWS Glue ETL 심층 분석
- Amazon EMR
- 워크플로우 오케스트레이션
- 정리 및 다음 세션 예고
1. 배치 처리 개요
"ETL은 데이터 파이프라인의 심장이다. 원시 데이터를 비즈니스 가치로 변환하는 핵심 과정이다."
— Data Engineering Fundamentals
1.1 ETL vs ELT
┌─────────────────────────────────────────────────────────────────────┐
│ ETL vs ELT 비교 │
│ │
│ ETL (Extract-Transform-Load) │
│ ┌─────────┐ ┌─────────────┐ ┌─────────┐ │
│ │ Source │───▶│ Transform │───▶│ Target │ │
│ │ DB │ │ (ETL Tool) │ │ DW │ │
│ └─────────┘ └─────────────┘ └─────────┘ │
│ │ │
│ 별도 서버에서 │
│ 변환 수행 │
│ │
│ ELT (Extract-Load-Transform) │
│ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │
│ │ Source │───▶│ Load │───▶│ Transform │ │
│ │ DB │ │ (S3/DW) │ │ (in DW) │ │
│ └─────────┘ └─────────┘ └─────────────┘ │
│ │ │
│ 타겟 시스템의 │
│ 컴퓨팅 파워 활용 │
└─────────────────────────────────────────────────────────────────────┘ETL 선택
- • 복잡한 변환 로직
- • 데이터 품질 검증 필요
- • 레거시 시스템 연동
- • 민감 데이터 마스킹
ELT 선택 (권장)
- • 클라우드 네이티브 환경
- • 대용량 데이터 처리
- • 유연한 스키마 변경
- • dbt 등 모던 도구 활용
1.2 AWS 배치 처리 서비스
| 서비스 | 특징 | 사용 사례 | 비용 모델 |
|---|---|---|---|
| AWS Glue | 서버리스 Spark, 관리형 | 일반적인 ETL, 카탈로그 통합 | DPU-시간 |
| EMR | 완전한 Hadoop 에코시스템 | 복잡한 처리, ML, 대규모 | 인스턴스 시간 |
| EMR Serverless | 서버리스 Spark/Hive | 간헐적 대용량 처리 | vCPU-시간 |
| Lambda | 서버리스, 이벤트 기반 | 소규모 변환, 트리거 | 요청 + 실행시간 |
| Step Functions | 워크플로우 오케스트레이션 | 복잡한 파이프라인 조율 | 상태 전환 |
1.3 배치 처리 아키텍처 패턴
┌─────────────────────────────────────────────────────────────────────┐
│ 일반적인 배치 처리 파이프라인 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Raw │───▶│ Glue │───▶│ Curated │───▶│ Glue │ │
│ │ S3 │ │ Job 1 │ │ S3 │ │ Job 2 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Cleanse │ │ Aggregate │ │
│ │ Dedupe │ │ Join │ │
│ │ Validate │ │ Enrich │ │
│ └─────────────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Analytics │ │
│ │ S3 │ │
│ └──────┬──────┘ │
│ │ │
│ ┌────────────────────┼────────────────┐│
│ ▼ ▼ ▼││
│ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ Athena │ │Redshift │ │QuickSight│
│ └─────────┘ └─────────┘ └─────────┘
└─────────────────────────────────────────────────────────────────────┘
오케스트레이션: Step Functions / Airflow / Glue Workflow1.4 서비스 선택 가이드
AWS Glue 선택
서버리스 선호, Glue Catalog 통합, 일반적인 ETL 작업, 빠른 시작
EMR 선택
복잡한 Spark/Hive 작업, 커스텀 라이브러리, 장시간 실행, 비용 최적화 필요
EMR Serverless 선택
간헐적 대용량 처리, 클러스터 관리 부담 제거, 자동 스케일링
2. AWS Glue ETL 심층 분석
AWS Glue는 완전 관리형 ETL 서비스로, Apache Spark 기반의 서버리스 데이터 처리를 제공합니다. Data Catalog과 긴밀하게 통합되어 메타데이터 관리가 용이합니다.
2.1 Glue Job 유형
Spark
- • 대용량 분산 처리
- • PySpark / Scala
- • DynamicFrame API
- • 2~100 DPU
Spark Streaming
- • 실시간 스트리밍
- • Kinesis/Kafka 소스
- • 마이크로배치
- • 2~100 DPU
Python Shell
- • 소규모 작업
- • 순수 Python
- • pandas, boto3
- • 0.0625~1 DPU
2.2 Glue Spark Job 예시
import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from awsglue.context import GlueContext
from awsglue.job import Job
from pyspark.context import SparkContext
from pyspark.sql.functions import col, when, lit, current_timestamp
# Job 초기화
args = getResolvedOptions(sys.argv, ['JOB_NAME', 'source_database', 'target_path'])
sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args['JOB_NAME'], args)
# 1. 소스 데이터 읽기 (Glue Catalog)
orders_dyf = glueContext.create_dynamic_frame.from_catalog(
database=args['source_database'],
table_name="orders_raw",
transformation_ctx="orders_source"
)
customers_dyf = glueContext.create_dynamic_frame.from_catalog(
database=args['source_database'],
table_name="customers_raw",
transformation_ctx="customers_source"
)
# 2. DynamicFrame → DataFrame 변환
orders_df = orders_dyf.toDF()
customers_df = customers_dyf.toDF()
# 3. 데이터 정제
orders_cleaned = orders_df \
.filter(col("amount") > 0) \
.filter(col("status").isin(["completed", "shipped", "pending"])) \
.withColumn("amount_category",
when(col("amount") < 100, "small")
.when(col("amount") < 1000, "medium")
.otherwise("large")
) \
.withColumn("processed_at", current_timestamp())
# 4. 조인
enriched_orders = orders_cleaned.join(
customers_df.select("customer_id", "customer_name", "region"),
on="customer_id",
how="left"
)
# 5. 결과 저장 (Parquet, 파티셔닝)
enriched_orders.write \
.mode("overwrite") \
.partitionBy("region", "order_date") \
.parquet(args['target_path'])
# 6. Glue Catalog 업데이트 (선택적)
sink = glueContext.getSink(
connection_type="s3",
path=args['target_path'],
enableUpdateCatalog=True,
updateBehavior="UPDATE_IN_DATABASE",
partitionKeys=["region", "order_date"]
)
sink.setFormat("parquet")
sink.setCatalogInfo(
catalogDatabase="curated_db",
catalogTableName="orders_enriched"
)
sink.writeFrame(DynamicFrame.fromDF(enriched_orders, glueContext, "output"))
job.commit()2.3 Job Bookmark (증분 처리)
┌─────────────────────────────────────────────────────────────────────┐
│ Job Bookmark 증분 처리 │
│ │
│ 첫 번째 실행 (Day 1) │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ S3 Source: file1.json, file2.json, file3.json ││
│ │ ↓ ││
│ │ Glue Job: 모든 파일 처리 ││
│ │ ↓ ││
│ │ Bookmark 저장: "마지막 처리 = file3.json" ││
│ └─────────────────────────────────────────────────────────────────┘│
│ │
│ 두 번째 실행 (Day 2) │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ S3 Source: file1.json, file2.json, file3.json, file4.json ││
│ │ ↑ 새 파일 ││
│ │ Bookmark 확인: "file3.json 이후만 처리" ││
│ │ ↓ ││
│ │ Glue Job: file4.json만 처리 ← 증분! ││
│ │ ↓ ││
│ │ Bookmark 업데이트: "마지막 처리 = file4.json" ││
│ └─────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────┘// Job Bookmark 활성화
# Terraform
resource "aws_glue_job" "etl" {
name = "orders-etl"
role_arn = aws_iam_role.glue.arn
command {
script_location = "s3://scripts/etl.py"
python_version = "3"
}
default_arguments = {
"--job-bookmark-option" = "job-bookmark-enable" # 핵심!
"--enable-metrics" = "true"
"--enable-continuous-cloudwatch-log" = "true"
}
}
# PySpark에서 transformation_ctx 필수
orders_dyf = glueContext.create_dynamic_frame.from_catalog(
database="raw_db",
table_name="orders",
transformation_ctx="orders_source" # Bookmark 추적용
)Job Bookmark 주의사항
- • transformation_ctx 파라미터 필수
- • S3 파일명 기반 추적 (수정된 파일은 재처리 안 됨)
- • JDBC 소스는 timestamp/sequence 컬럼 필요
- • 북마크 리셋:
aws glue reset-job-bookmark
2.4 Glue 비용 최적화
DPU 설정
- • 기본 10 DPU, 필요에 따라 조정
- • 소규모 작업: 2~5 DPU
- • Auto Scaling 활성화 권장
- • 1 DPU = 4 vCPU, 16GB RAM
실행 시간 단축
- • Parquet 입력 (컬럼 프루닝)
- • 파티션 필터 pushdown
- • 브로드캐스트 조인 활용
- • 캐싱 전략 적용
💰 비용 계산 예시
10 DPU × 30분 실행 × $0.44/DPU-시간 = $2.20/실행
일 1회 실행 시 월간: $66
3. Amazon EMR
Amazon EMR은 Apache Spark, Hive, Presto 등 빅데이터 프레임워크를 관리형으로 제공하는 서비스입니다. 대규모 데이터 처리와 복잡한 분석에 적합합니다.
3.1 EMR 배포 옵션
| 옵션 | 특징 | 사용 사례 | 비용 |
|---|---|---|---|
| EMR on EC2 | 전통적인 클러스터 | 장시간 실행, 커스텀 설정 | EC2 + EMR 요금 |
| EMR on EKS | Kubernetes 통합 | 컨테이너 환경, 멀티테넌트 | EKS + EMR 요금 |
| EMR Serverless | 완전 서버리스 | 간헐적 작업, 자동 스케일링 | vCPU-시간 |
| EMR Studio | 노트북 환경 | 대화형 분석, 개발 | 클러스터 비용 |
3.2 EMR on EC2 클러스터 설계
┌─────────────────────────────────────────────────────────────────────┐
│ EMR Cluster Architecture │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ Master Node (1개) ││
│ │ • YARN ResourceManager ││
│ │ • HDFS NameNode ││
│ │ • Spark Driver (cluster mode) ││
│ │ • Hive Metastore ││
│ │ 인스턴스: m5.xlarge ~ m5.4xlarge ││
│ └─────────────────────────────────────────────────────────────────┘│
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Core Node 1 │ │ Core Node 2 │ │ Core Node N │ │
│ │ • YARN NM │ │ • YARN NM │ │ • YARN NM │ │
│ │ • HDFS DN │ │ • HDFS DN │ │ • HDFS DN │ │
│ │ • Spark Exec │ │ • Spark Exec │ │ • Spark Exec │ │
│ │ r5.2xlarge │ │ r5.2xlarge │ │ r5.2xlarge │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Task Node 1 │ │ Task Node N │ ← Auto Scaling │
│ │ • YARN NM │ │ • YARN NM │ (Spot Instance) │
│ │ • Spark Exec │ │ • Spark Exec │ │
│ │ • No HDFS │ │ • No HDFS │ │
│ │ r5.4xlarge │ │ r5.4xlarge │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘Master Node
- • 클러스터 조율
- • On-Demand 권장
- • 고가용성: Multi-Master
Core Node
- • HDFS 데이터 저장
- • On-Demand 권장
- • 축소 시 데이터 손실 주의
Task Node
- • 컴퓨팅 전용
- • Spot Instance 적합
- • 자유로운 스케일링
3.3 EMR Serverless
EMR Serverless는 클러스터 관리 없이 Spark, Hive 작업을 실행합니다. 자동으로 리소스를 프로비저닝하고 스케일링합니다.
// Terraform - EMR Serverless Application
resource "aws_emrserverless_application" "spark" {
name = "data-processing"
release_label = "emr-6.15.0"
type = "SPARK"
initial_capacity {
initial_capacity_type = "DRIVER"
initial_capacity_config {
worker_count = 2
worker_configuration {
cpu = "4vCPU"
memory = "16GB"
}
}
}
initial_capacity {
initial_capacity_type = "EXECUTOR"
initial_capacity_config {
worker_count = 10
worker_configuration {
cpu = "4vCPU"
memory = "16GB"
}
}
}
maximum_capacity {
cpu = "400vCPU"
memory = "1600GB"
}
auto_start_configuration {
enabled = true
}
auto_stop_configuration {
enabled = true
idle_timeout_minutes = 15
}
}
# Job 실행
resource "aws_emrserverless_job_run" "etl" {
application_id = aws_emrserverless_application.spark.id
execution_role_arn = aws_iam_role.emr_serverless.arn
name = "daily-etl"
job_driver {
spark_submit {
entry_point = "s3://scripts/etl.py"
entry_point_arguments = [
"--source", "s3://raw-data/",
"--target", "s3://curated-data/"
]
spark_submit_parameters = join(" ", [
"--conf spark.executor.cores=4",
"--conf spark.executor.memory=16g",
"--conf spark.dynamicAllocation.enabled=true"
])
}
}
}3.4 Glue vs EMR 선택
| 항목 | AWS Glue | EMR | EMR Serverless |
|---|---|---|---|
| 관리 수준 | 완전 관리형 | 클러스터 관리 | 완전 관리형 |
| 시작 시간 | ~1분 | 5~15분 | ~1분 |
| 커스터마이징 | 제한적 | 완전 자유 | 중간 |
| 비용 (1시간 기준) | $4.40 (10 DPU) | $2~5 (클러스터) | $2~4 (vCPU) |
| Catalog 통합 | 네이티브 | 설정 필요 | 설정 필요 |
💡 선택 가이드
- • Glue: 빠른 시작, Catalog 통합, 일반적인 ETL
- • EMR on EC2: 장시간 실행, 커스텀 라이브러리, 비용 최적화
- • EMR Serverless: 간헐적 대용량, 클러스터 관리 부담 제거
4. 워크플로우 오케스트레이션
복잡한 데이터 파이프라인은 여러 작업의 순서, 의존성, 에러 처리를 관리해야 합니다. AWS Step Functions와 Glue Workflow가 대표적인 오케스트레이션 도구입니다.
4.1 Step Functions
┌─────────────────────────────────────────────────────────────────────┐
│ Step Functions ETL Pipeline │
│ │
│ ┌─────────┐ │
│ │ Start │ │
│ └────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Glue Crawler │ │
│ │ (Raw Data) │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Glue Job 1 │ │
│ │ (Cleansing) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Glue Job 2a │ │ Glue Job 2b │ │ Glue Job 2c │ Parallel │
│ │ (Orders) │ │ (Customers) │ │ (Products) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └───────────────┼───────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Glue Job 3 │ │
│ │ (Aggregation) │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Glue Crawler │ │
│ │ (Update Catalog)│ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ End │ │
│ └──────────┘ │
└─────────────────────────────────────────────────────────────────────┘// Step Functions State Machine (ASL)
{
"Comment": "ETL Pipeline",
"StartAt": "RunCrawler",
"States": {
"RunCrawler": {
"Type": "Task",
"Resource": "arn:aws:states:::glue:startCrawler.sync",
"Parameters": {
"Name": "raw-data-crawler"
},
"Next": "CleansingJob"
},
"CleansingJob": {
"Type": "Task",
"Resource": "arn:aws:states:::glue:startJobRun.sync",
"Parameters": {
"JobName": "cleansing-job",
"Arguments": {
"--source_database": "raw_db",
"--target_path": "s3://curated/"
}
},
"Next": "ParallelProcessing"
},
"ParallelProcessing": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "ProcessOrders",
"States": {
"ProcessOrders": {
"Type": "Task",
"Resource": "arn:aws:states:::glue:startJobRun.sync",
"Parameters": { "JobName": "orders-job" },
"End": true
}
}
},
{
"StartAt": "ProcessCustomers",
"States": {
"ProcessCustomers": {
"Type": "Task",
"Resource": "arn:aws:states:::glue:startJobRun.sync",
"Parameters": { "JobName": "customers-job" },
"End": true
}
}
}
],
"Next": "AggregationJob",
"Catch": [{
"ErrorEquals": ["States.ALL"],
"Next": "NotifyFailure"
}]
},
"AggregationJob": {
"Type": "Task",
"Resource": "arn:aws:states:::glue:startJobRun.sync",
"Parameters": { "JobName": "aggregation-job" },
"Next": "UpdateCatalog"
},
"UpdateCatalog": {
"Type": "Task",
"Resource": "arn:aws:states:::glue:startCrawler.sync",
"Parameters": { "Name": "curated-crawler" },
"End": true
},
"NotifyFailure": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn": "arn:aws:sns:...:pipeline-alerts",
"Message": "ETL Pipeline Failed"
},
"End": true
}
}
}4.2 Glue Workflow
Glue Workflow는 Glue 작업(Job, Crawler)을 조율하는 네이티브 오케스트레이션 도구입니다. 트리거 기반으로 작업 순서를 정의합니다.
// Terraform - Glue Workflow
resource "aws_glue_workflow" "etl_pipeline" {
name = "daily-etl-pipeline"
}
# 시작 트리거 (스케줄)
resource "aws_glue_trigger" "start" {
name = "start-trigger"
type = "SCHEDULED"
schedule = "cron(0 1 * * ? *)" # 매일 새벽 1시
workflow_name = aws_glue_workflow.etl_pipeline.name
actions {
crawler_name = aws_glue_crawler.raw.name
}
}
# Crawler 완료 후 Job 실행
resource "aws_glue_trigger" "after_crawler" {
name = "after-crawler"
type = "CONDITIONAL"
workflow_name = aws_glue_workflow.etl_pipeline.name
predicate {
conditions {
crawler_name = aws_glue_crawler.raw.name
crawl_state = "SUCCEEDED"
}
}
actions {
job_name = aws_glue_job.cleansing.name
arguments = {
"--source_database" = "raw_db"
}
}
}
# Job 완료 후 다음 Job 실행
resource "aws_glue_trigger" "after_cleansing" {
name = "after-cleansing"
type = "CONDITIONAL"
workflow_name = aws_glue_workflow.etl_pipeline.name
predicate {
conditions {
job_name = aws_glue_job.cleansing.name
state = "SUCCEEDED"
}
}
actions {
job_name = aws_glue_job.aggregation.name
}
}4.3 오케스트레이션 도구 비교
| 특성 | Step Functions | Glue Workflow | MWAA (Airflow) |
|---|---|---|---|
| 관리 수준 | 서버리스 | 서버리스 | 관리형 |
| 통합 범위 | 200+ AWS 서비스 | Glue 전용 | 무제한 |
| 정의 방식 | JSON (ASL) | 트리거 기반 | Python DAG |
| 복잡한 로직 | Choice, Map, Parallel | 제한적 | 완전 자유 |
| 비용 | 상태 전환당 | 무료 (Job 비용만) | 환경 시간당 |
💡 선택 가이드
- • Step Functions: 다양한 AWS 서비스 조합, 복잡한 분기/병렬
- • Glue Workflow: Glue 작업만 사용, 간단한 파이프라인
- • MWAA: 기존 Airflow 경험, 복잡한 의존성, 외부 시스템 연동
5. 정리 및 다음 세션 예고
5.1 핵심 요약
ETL vs ELT
클라우드 환경에서는 ELT 패턴이 권장됨. 타겟 시스템의 컴퓨팅 파워를 활용하여 유연하고 확장 가능한 변환 수행.
AWS Glue
서버리스 Spark 기반 ETL. Data Catalog 네이티브 통합. Job Bookmark로 증분 처리. 빠른 시작과 간편한 운영.
Amazon EMR
완전한 Hadoop 에코시스템. 복잡한 처리와 커스터마이징에 적합. EMR Serverless로 클러스터 관리 부담 제거 가능.
워크플로우 오케스트레이션
Step Functions는 다양한 AWS 서비스 통합에 적합. Glue Workflow는 Glue 전용 간단한 파이프라인에 적합.
5.2 서비스 선택 의사결정 트리
배치 처리 서비스 선택
Q1: 서버리스를 원하는가?
├── Yes → Q2: Glue Catalog 통합이 중요한가?
│ ├── Yes → AWS Glue
│ └── No → Q3: 대용량 처리인가?
│ ├── Yes → EMR Serverless
│ └── No → Lambda + Step Functions
│
└── No → Q4: 장시간 실행 클러스터가 필요한가?
├── Yes → EMR on EC2
└── No → EMR Serverless
오케스트레이션 선택
Q1: Glue 작업만 사용하는가?
├── Yes → Glue Workflow
└── No → Q2: 복잡한 분기/병렬 로직이 있는가?
├── Yes → Step Functions
└── No → Q3: 기존 Airflow 경험이 있는가?
├── Yes → MWAA
└── No → Step Functions5.3 다음 세션 예고
Session 5: 실시간 처리 - Kinesis Analytics & Flink
스트리밍 데이터의 실시간 분석과 처리 방법을 학습합니다.
- 스트림 처리 개념 (윈도우, 워터마크)
- Kinesis Data Analytics for Apache Flink
- Amazon Managed Service for Apache Flink
- 실시간 대시보드 및 알림 구축
- Lambda Architecture 구현
5.4 실습 과제
과제 1: Glue ETL Job 작성
JSON 원본 데이터를 읽어 정제하고 Parquet으로 변환하는 Glue Job을 작성하세요. Job Bookmark를 활성화하여 증분 처리를 구현하세요.
과제 2: Step Functions 파이프라인
Crawler → Glue Job → Crawler 순서로 실행되는 Step Functions 워크플로우를 구축하세요. 에러 발생 시 SNS 알림을 보내도록 설정하세요.
과제 3: 비용 비교 분석
동일한 ETL 작업을 Glue, EMR on EC2, EMR Serverless로 실행하고 비용과 실행 시간을 비교 분석하세요.