핵심 인사이트 (3줄 요약)

  • 본질: Spark 체크포인팅(Checkpointing)은 RDD/DataFrame의 리니지(Lineage, 변환 계보)를 HDFS/S3 같은 안정적인 저장소에 물리적으로 스냅샷을 저장하여 리니지 체인을 단절하고, 장애 발생 시 처음부터 재연산하지 않고 체크포인트 지점에서 재시작할 수 있게 하는 메커니즘이다.
  • 가치: 수십 단계의 변환이 쌓인 장기 실행 작업이나 스트리밍에서 상태(State)를 관리할 때, 리니지가 너무 길어져 JVM 스택 오버플로나 재연산 비용이 폭증하는 문제를 체크포인팅으로 근본적으로 해결한다.
  • 판단 포인트: 체크포인팅은 cache()/persist()와 다르다. cache()는 메모리에 데이터를 보존하되 리니지는 유지하고 Executor 재시작 시 소실되지만, 체크포인팅은 리니지를 끊고 안정적 외부 스토리지에 영구 저장한다.

Ⅰ. 개요 및 필요성

1. Lineage와 Fault Tolerance

스파크는 RDD의 변환 이력(Lineage)을 DAG(Directed Acyclic Graph)로 추적하여 장애 시 재연산(Recomputation)으로 결함 허용을 달성한다. 그러나 리니지가 길어질수록 두 가지 문제가 발생한다.

  • 재연산 비용 폭발: 10단계 변환의 파티션이 하나 유실되면 10단계 전체를 재연산
  • 드라이버 메모리/스택 한계: 리니지가 수백 단계가 되면 TaskScheduler의 DAG 직렬화 크기가 커져 OOM(Out of Memory) 발생

2. 체크포인팅이 필요한 상황

  1. 반복적 ML 알고리즘: PageRank, 그래프 알고리즘, EM 알고리즘 등 수십~수백 이터레이션
  2. Structured Streaming: 상태 저장 연산(StatefulAggregation)의 상태 백업
  3. 장기 실행 파이프라인: 수 시간 이상 실행되는 ETL 파이프라인

📢 섹션 요약 비유

리니지(Lineage)는 "요리 레시피 전체를 기억하는 것"이다. 30단계 레시피를 외우다가 중간에 실수하면 처음부터 다시 시작해야 한다. 체크포인팅은 15단계 완성된 중간 결과물을 냉동고(HDFS)에 저장하는 것 — 이후 실수해도 냉동고에서 꺼내 15단계부터 이어가면 된다.


Ⅱ. 아키텍처 및 핵심 원리

1. 체크포인팅 동작 원리

체크포인팅 전:
  Input ─→ T1 ─→ T2 ─→ T3 ─→ T4 ─→ T5 ─→ T6 ─→ T7 ─→ Output
  (Lineage: 7단계 변환 기억)

  장애 발생 (T6 파티션 유실) → T1~T6 전체 재연산 필요!

체크포인팅 후:
  Input ─→ T1 ─→ T2 ─→ T3 ─→ [체크포인트: HDFS 저장]
                                   ↓
                              T4 ─→ T5 ─→ T6 ─→ T7 ─→ Output
  (T4 이후 Lineage만 기억)

  장애 발생 (T6 파티션 유실) → HDFS에서 T3 체크포인트 로드 → T4~T6만 재연산!

2. RDD 체크포인팅 사용법

# 1. 체크포인트 디렉토리 설정 (안정적 분산 스토리지 권장)
sc.setCheckpointDir("hdfs:///spark/checkpoints")

# 2. 체크포인팅 전 persist() 호출 권장 (디스크 쓰기 중복 방지)
rdd = rdd.persist()

# 3. 체크포인팅 설정
rdd.checkpoint()

# 4. 액션 실행 시 HDFS에 실제 저장됨
rdd.count()  # 이 시점에 checkpoint 파일 생성

# 5. 이후 이 RDD의 리니지는 체크포인트 파일을 가리킴
print(rdd.toDebugString())  # ReliableCheckpointRDD

3. Structured Streaming 체크포인팅

query = df.writeStream \
    .format("parquet") \
    .option("checkpointLocation", "hdfs:///streaming/checkpoint/") \
    .outputMode("append") \
    .start("/output/streaming")

스트리밍 체크포인트는 다음을 저장한다:

  • 현재까지 처리한 오프셋(Offset)
  • 집계 연산의 상태(State)
  • 완료된 마이크로배치 메타데이터

4. 체크포인팅 유형 비교

유형저장 위치리니지 단절사용 대상
Reliable CheckpointHDFS/S3 (내구성↑)완전 단절RDD, Spark Streaming
Local CheckpointExecutor 로컬 디스크부분 단절빠르지만 Executor 장애 시 유실
Streaming CheckpointHDFS/S3상태 저장Structured Streaming

📢 섹션 요약 비유

체크포인팅은 "게임 세이브 포인트"와 같다. 30분 게임 후 세이브(체크포인트)하면, 이후 죽어도 세이브 지점부터 재시작한다. HDFS에 저장하면 컴퓨터가 꺼져도 안전하다.


Ⅲ. 비교 및 연결

1. 체크포인팅 vs cache()/persist()

항목cache() / persist()checkpoint()
저장 위치Executor 메모리/디스크HDFS/S3 (외부 안정 스토리지)
리니지 처리유지 (재연산 가능)완전 단절 (재연산 불가)
Executor 재시작 시데이터 소실, 리니지로 재연산체크포인트에서 복구
사용 목적반복 참조 성능 최적화리니지 단절, 내구성 보장
비용메모리 사용HDFS 쓰기 비용

2. 연결 개념

  • Lineage: 체크포인팅이 단절하는 대상
  • Structured Streaming Watermark: 스트리밍에서 체크포인팅과 함께 상태 만료 관리
  • Fault Tolerance: 체크포인팅의 근본 목적

📢 섹션 요약 비유

cache()는 "책 내용을 머릿속에 외워두는 것"(빠르지만 잠들면 사라짐)이고, checkpoint()는 "책 내용을 필사해 금고에 보관하는 것"(느리지만 영구 보존)이다.


Ⅳ. 실무 적용 및 기술사 판단

1. 체크포인팅 적용 기준

조건체크포인팅 권장 여부
리니지 깊이 > 20~30단계✅ 강권장
ML 반복 알고리즘 (PageRank 등)✅ 매 N 이터레이션마다
Structured Streaming 상태 저장✅ 필수 (withWatermark, groupBy)
단순 배치 변환 (< 10단계)❌ 불필요 (오버헤드)

2. 실무 주의사항

# 패턴: persist() 후 checkpoint() 호출
# 이유: checkpoint()는 액션 실행 시 HDFS 쓰기 + 리니지 단절이 같이 일어남
#      persist() 없으면 액션 실행 시 재연산 후 HDFS 저장 (이중 비용)
rdd = rdd.persist(StorageLevel.MEMORY_AND_DISK)
rdd.checkpoint()
rdd.count()  # persist된 데이터를 HDFS에 쓰기 (재연산 불필요)

3. 체크리스트

  • 체크포인트 경로는 신뢰성 있는 분산 스토리지 (HDFS, S3, Azure ADLS) 사용
  • checkpoint() 호출 전 persist() 먼저 적용
  • 스트리밍 쿼리는 checkpointLocation 필수 설정
  • 체크포인트 디렉토리 용량 관리 (주기적 정리 필요)
  • LocalCheckpoint는 내구성 없으므로 프로덕션에서 사용 금지

📢 섹션 요약 비유

체크포인팅 설정은 "장거리 여행 중 주유소에서 기름을 채우는 것"이다. 중간에 차가 멈춰도(장애) 최근 주유소(체크포인트)에서 다시 출발할 수 있다. 주유 없이 무한정 달리다가 멈추면 처음 출발지로 돌아가야 한다.


Ⅴ. 기대효과 및 결론

1. 기대효과

효과설명
재연산 비용 절감체크포인트 이전 단계 재연산 제거
JVM 안정성 향상과도한 리니지로 인한 스택 오버플로 방지
스트리밍 내구성상태 저장 스트리밍 장애 복구 가능
장기 실행 작업 신뢰성수 시간 실행 작업의 중간 결과 보존

2. 결론

체크포인팅은 Spark의 fault tolerance 아키텍처의 핵심 보완 장치다. 리니지 기반 복구가 강력하지만 무한히 쌓이면 오히려 취약점이 되는 역설을 해결하며, 특히 스트리밍 상태 관리에서는 없어서는 안 될 필수 메커니즘이다.

📢 섹션 요약 비유

체크포인팅 없는 장기 Spark 작업은 "세이브 없이 100층 던전을 도전하는 것"이다. 99층에서 죽으면 1층부터 다시 시작해야 한다. 10층마다 세이브(체크포인팅)하면 최악의 경우에도 9층만 다시 하면 된다.


📌 관련 개념 맵

개념관계설명
Lineage (리니지)단절 대상체크포인팅으로 변환 계보를 끊음
cache() / persist()보완 기술메모리 캐시 + 체크포인팅 같이 사용 권장
Structured Streaming응용 영역상태 저장 스트리밍의 핵심 내구성 메커니즘
Fault Tolerance목적장애 복구 비용을 체크포인트 이후로 한정
HDFS / S3저장 대상신뢰성 있는 외부 스토리지

📈 관련 키워드 및 발전 흐름도

[Spark RDD 리니지 (Lineage) — 변환 이력 그래프 축적]
    │
    ▼
[체크포인팅 (Checkpointing) — HDFS에 RDD 물리 저장, 리니지 절단]
    │
    ▼
[스트리밍 체크포인트 — 오프셋·상태(State) 주기적 영속화]
    │
    ▼
[WAL (Write-Ahead Log) — 장애 복구 전 로그 선기록]
    │
    ▼
[장애 복구 (Fault Recovery) — 체크포인트 지점에서 재연산 최소화]

Spark의 리니지가 길어질수록 재연산 비용이 폭발하므로, 체크포인팅으로 중간 상태를 영속화해 장애 복구 시 재연산 범위를 최소화한다.

👶 어린이를 위한 3줄 비유 설명

레고 블록으로 큰 성을 만들다가 중간에 다 무너지면 처음부터 다시 만들어야 하는데, 체크포인팅은 30번째 블록 쌓았을 때 사진을 찍어두는 것(HDFS 저장)이에요. 무너지면 30번째 사진 보고 거기서부터 이어서 만들면 되니까 훨씬 빠르죠! cache()는 잘 기억해두는 것이지만, 체크포인팅은 사진을 찍어 서랍에 보관하는 거예요.