어제 만든 Data Catalog는 "데이터가 어디 있고 어떻게 생겼는지"만 안다. 하지만 데이터 엔지니어링의 본질은 그 데이터를 변환(Transform)하는 것이다. 더러운 CSV를 깔끔한 Parquet으로, 분산된 테이블을 조인된 팩트 테이블로, 원시 로그를 집계된 지표로 바꾸는 일. 이 변환을 서버리스로 실행하는 것이 AWS Glue ETL Job이다.
Glue ETL Job은 내부적으로 Apache Spark를 돌린다. 즉 Glue는 "관리형 Spark"인 셈이다. 클러스터를 직접 띄우고 끄는 EMR과 달리, Glue는 Job을 제출하면 AWS가 Spark 환경을 자동으로 프로비저닝하고 끝나면 회수한다. 과금 단위는 DPU(Data Processing Unit)이며, 1 DPU = 4 vCPU + 16GB 메모리다. 실행 시간(초 단위)에 비례해 과금된다.
Spark를 써 봤다면 DataFrame이 익숙할 것이다. Glue는 여기에 한 겹을 더 씌운 DynamicFrame이라는 자료구조를 도입했다. 왜 또 다른 추상화가 필요했을까?
항목
Spark DataFrame
Glue DynamicFrame
스키마
사전 고정 필요
스키마 추론, 자기 기술적(self-describing)
타입 충돌
오류 또는 강제 캐스팅
Choice 타입으로 양쪽 보존
더티 데이터
처리 어려움
ETL 변환(ResolveChoice 등) 내장
Catalog 연동
별도 작업
네이티브 통합
핵심 차이는 스키마 유연성이다. 실제 데이터는 더럽다. 같은 컬럼이 어떤 행에서는 정수, 다른 행에서는 문자열일 수 있다. DataFrame은 이런 충돌에서 깨지지만, DynamicFrame은 Choice 타입으로 두 타입을 모두 보존한 뒤, 나중에 ResolveChoice 변환으로 어떻게 정리할지 결정하게 해 준다. ETL은 "예상치 못한 데이터"를 다루는 일이므로 이 유연성이 가치가 있다.
DynamicFrame은 "스키마-온-리드"를 코드 레벨에서 구현한 것이다. 전통적 ETL 도구는 매핑을 사전에 엄격히 정의해야 하지만(스키마-온-라이트적), 데이터 레이크 시대의 ETL은 "일단 받아들이고, 변환 과정에서 정제한다"는 ELT/스키마-온-리드 철학을 따른다. DynamicFrame의 Choice 타입과 ResolveChoice는 이 철학을 Spark 위에서 실현한 장치다. 필요하면 .toDF()로 DataFrame으로, .fromDF()로 다시 DynamicFrame으로 자유롭게 오갈 수 있다.
ApplyMapping은 ETL에서 가장 자주 쓰는 변환이다. 컬럼 이름을 바꾸고, 타입을 변환하고, 불필요한 컬럼을 떨어뜨린다. write_dynamic_frame의 partitionKeys로 출력 데이터를 파티션 폴더로 자동 분할하면, 다운스트림 쿼리가 프루닝의 이점을 누린다. 출력 형식은 분석에 유리한 Parquet/ORC(컬럼형 + 압축)가 정석이다.
Glue Job에는 두 가지 실행 타입이 있다. 표준 Spark Job(대용량 분산 처리)과 Python Shell Job(단일 노드에서 가벼운 파이썬 스크립트, 작은 데이터/오케스트레이션용)이다. 수 GB 이상 분산 변환이면 Spark Job, 수백 MB 이하의 가벼운 작업이나 API 호출·메타데이터 정리면 Python Shell이 훨씬 싸다. 또한 Glue 3.0/4.0은 Spark 버전이 다르므로, 라이브러리 호환성을 확인해야 한다.
매일 새 데이터가 들어오는 파이프라인에서, Job을 돌릴 때마다 전체 데이터를 다시 처리하면 비용과 시간이 폭증한다. Job 북마크(Job Bookmark)는 "이전 실행에서 어디까지 처리했는지"를 Glue가 기억해, 다음 실행에서는 새로 추가된 데이터만 처리하게 해 준다.
from awsglue.job import Jobfrom awsglue.utils import getResolvedOptionsargs = getResolvedOptions(sys.argv, ['JOB_NAME'])job = Job(glueContext)job.init(args['JOB_NAME'], args) # 북마크 컨텍스트 초기화# ... 소스 읽기 시 transformation_ctx 지정이 핵심 ...orders = glueContext.create_dynamic_frame.from_catalog( database="sales_db", table_name="orders", transformation_ctx="orders_src" # 북마크가 이 식별자로 진행 상태 추적)# ... 변환과 싱크 ...job.commit() # 여기서 북마크 상태가 갱신됨
북마크가 동작하려면 세 가지가 필요하다. (1) Job 속성에서 북마크를 Enable, (2) 소스/싱크에 transformation_ctx 지정, (3) 마지막에 job.commit() 호출. 북마크는 S3 객체의 타임스탬프나 JDBC의 기본키/타임스탬프 컬럼을 기준으로 신규 데이터를 식별한다.
⚠️ 함정: 북마크는 만능이 아니다. S3 소스는 객체의 최종 수정 시각으로 신규 여부를 가리므로, 이미 처리한 파일이 수정(덮어쓰기)되면 다음 실행에서 다시 읽혀 타깃에 중복이 생길 수 있다. 또 job.commit()을 빠뜨리거나 transformation_ctx를 지정하지 않으면 북마크가 전혀 동작하지 않는다. 과거 데이터를 다시 처리하려면 북마크를 Reset하거나 일시 Disable해야 한다. 시험에서 "Glue가 매번 전체를 재처리한다" 또는 "증분 처리"가 나오면 Job 북마크를 떠올려야 한다.
여러 Job과 크롤러를 순서대로 엮어야 할 때가 많다. Glue는 트리거(Trigger)와 워크플로우(Workflow)를 제공한다. 트리거는 스케줄(cron), 온디맨드, 이벤트(이전 Job 성공/실패) 기반으로 다음 단계를 시작한다. 워크플로우는 크롤러 → Job → 크롤러 같은 DAG를 시각적으로 구성한다. 더 복잡한 분기·재시도·외부 서비스 연동이 필요하면 Step Functions나 MWAA(Airflow)로 오케스트레이션을 끌어올린다.
🎯 시나리오: 매일 raw CSV가 S3에 적재되고, 이를 정제·타입변환해 Parquet으로 저장한 뒤 분석 가능하게 만들어야 한다. 구성은 (1) 크롤러가 raw 테이블 등록 → (2) Glue ETL Job이 from_catalog로 읽고 ApplyMapping·ResolveChoice로 정제 후 Parquet 싱크 → (3) Job 북마크 Enable로 어제 신규분만 증분 처리 → (4) 후속 크롤러가 정제 테이블 갱신 → (5) Workflow 트리거로 전체를 매일 새벽 자동 실행. Athena가 정제 테이블을 바로 조회한다.
"1 DPU = 4 vCPU + 16GB"는 단위일 뿐이고, 실제로는 워커 유형 × 워커 수 × 실행 시간이 청구액을 만든다.
워커 유형
워커당 DPU
vCPU / 메모리
쓰임
G.1X
1
4 / 16GB
대부분의 변환·조인에 쓰는 기본 선택
G.2X
2
8 / 32GB
같은 용도, 워커 하나가 더 큰 파티션을 감당
G.4X / G.8X
4 / 8
16·32 / 64·128GB
무거운 집계·조인
G.025X
0.25
2 / 4GB
저용량 스트리밍 Job 전용
R 계열
1~8
메모리 최적화 구성
메모리 부족 오류가 잦은 작업
유형을 키우는 것과 수를 늘리는 것은 다른 처방이다. 전체 데이터가 많아 느리면 워커 수를 늘린다. 반면 데이터 스큐나 큰 셔플 때문에 한 파티션이 익스큐터 메모리에 못 들어가 죽는 경우는 수를 늘려도 풀리지 않고 워커 유형을 키워야 한다.
Flex 실행 클래스: 테스트, 일회성 적재, 급하지 않은 야간 배치처럼 시작이 좀 늦어도 되는 Job을 더 낮은 단가로 돌린다. Glue 3.0 이상, G.1X·G.2X에서만 쓸 수 있다. 빠른 시작과 전용 자원이 필요한 시간 민감 작업에는 표준 클래스를 쓴다.
Auto Scaling: Glue 3.0 이상에서 --enable-auto-scaling true로 켠다. 이때 지정한 워커 수는 "최대치"가 되고, Glue가 단계별 병렬도에 맞춰 워커를 붙였다 뗀다. 일부 단계만 바쁜 Job의 과다 프로비저닝을 줄인다.
타임아웃: 최대 7일까지 설정할 수 있고, 비워 두면 버전에 따라 기본값(4.0 이하 2,880분, 5.0 이상 480분)이 들어간다. Job은 실행 시간만큼 과금되므로 멈춘 Job이 오래 돌지 않게 예상 시간에 맞춰 명시한다. 타임아웃으로 끝난 Job은 자동 재시도되지 않는다.
최대 동시 실행 수: 기본값은 1이다. 이전 실행이 끝나기 전에 다음 스케줄이 오면 오류로 막는다. 북마크를 쓰는 Job에서 같은 입력을 두 실행이 동시에 읽는 사고를 막는 안전장치다.
Python Shell Job: 0.0625 DPU(기본) 또는 1 DPU 중에서 고른다. Spark가 없는 단일 노드이며 Job 북마크를 쓸 수 없다.
Glue 버전은 Spark·Python 버전을 함께 결정한다(예: 4.0은 Spark 3.3 / Python 3.10, 5.0은 Spark 3.5 / Python 3.11). 새 버전이 계속 추가되므로 라이브러리 호환성은 Job마다 확인한다.
⚠️ 함정: 북마크는 CDC가 아니다. JDBC 소스에서 북마크는 "키 값이 지난번보다 큰 새 행"을 가져올 뿐, 이미 가져온 행이 운영 DB에서 수정·삭제된 사실은 모른다. "운영 DB의 변경(UPDATE/DELETE 포함)을 계속 반영"이 요구사항이면 Week 3의 DMS CDC가 답이고, "추가만 되는 테이블에서 신규분만"이면 북마크로 충분하다.