이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • reprocessing은 backfill과 어떻게 다르고, 어떤 입력을 다시 흐르게 하는 일인가?
  • checkpoint는 offset, id range, timestamp, DAG run 중 어떤 좌표로 잡아야 하는가?
  • checkpoint를 언제 갱신해야 누락과 중복을 운영 가능한 범위로 둘 수 있는가?
  • 검증 실패와 처리 실패를 checkpoint 상태에서 어떻게 구분해야 하는가?

개요

reprocessing은 이미 처리한 입력을 다시 처리하는 일이다.

backfill은 주로 과거 누락분을 채우는 작업이고, reprocessing은 잘못 처리된 입력을 다시 흘려 결과를 교정하는 작업까지 포함한다.

둘 다 checkpoint가 필요하다.

checkpoint는 어디까지 성공했는지 알려주는 운영 좌표다.

하지만 checkpoint는 성공을 선언하는 도구이기도 하므로 너무 빨리 옮기면 누락을 숨긴다.

Reprocessing 입력

reprocessing 입력은 여러 형태가 될 수 있다.

  • Kafka topic offset range
  • Redis Stream ID range
  • outbox event id range
  • 운영 DB id range
  • partition date
  • 파일명과 line range
  • Airflow DAG run id

입력 형태가 다르면 checkpoint도 달라진다.

Kafka offset을 처리하는 작업에서 DB id checkpoint를 쓰면 장애 분석이 어렵다.

DB backfill에서 Kafka offset을 checkpoint로 쓰는 것도 의미가 없다.

Checkpoint 종류

offset checkpoint: topic-partition-offset
id checkpoint: last_success_id
time checkpoint: last_success_created_at
partition checkpoint: dt=2026-06-30
run checkpoint: dag_run_id + task_id

checkpoint는 source와 target의 관계를 설명해야 한다.

last_success_id가 target row id인지 source row id인지 모호하면 재처리가 위험해진다.

문서에는 checkpoint 기준 column과 정렬 순서를 적는다.

갱신 시점

checkpoint는 처리 성공 후 갱신한다.

더 정확히는 business write와 검증 기준을 어디까지 포함할지 정해야 한다.

read batch
  -> transform
  -> write target
  -> validate batch
  -> update checkpoint

write 후 validate 전에 checkpoint를 옮기면 검증 실패 데이터를 성공으로 표시할 수 있다.

반대로 checkpoint를 너무 늦게 옮기면 재시작 시 중복 처리가 많아진다.

이 trade-off를 idempotent write로 흡수한다.

상태 분리

checkpoint table은 성공 위치만 저장하면 부족하다.

CREATE TABLE reprocess_runs (
    run_id varchar(80) PRIMARY KEY,
    source_name varchar(80) NOT NULL,
    range_start varchar(120) NOT NULL,
    range_end varchar(120) NOT NULL,
    checkpoint varchar(120),
    status varchar(20) NOT NULL,
    validation_status varchar(20) NOT NULL,
    last_error text,
    updated_at timestamp NOT NULL
);

status는 처리 상태다.

validation_status는 결과 검증 상태다.

처리 완료와 검증 완료를 분리하면 “다 돌았지만 결과가 틀림”을 표현할 수 있다.

중복과 누락

checkpoint를 처리 전에 옮기면 누락 위험이 생긴다.

checkpoint를 처리 후에 옮기면 중복 위험이 생긴다.

운영에서는 보통 중복을 선택하고 멱등 write로 흡수한다.

처리 전 checkpoint 이동 -> crash -> 입력 누락
처리 후 checkpoint 이동 전 crash -> restart -> 입력 중복

중복은 processed table과 upsert로 제어할 수 있다.

누락은 나중에 발견하기 어렵다.

재처리 범위 축소

장애가 난 뒤 전체를 다시 처리하면 위험하다.

checkpoint와 검증 query로 범위를 좁힌다.

SELECT min(order_id), max(order_id)
FROM order_summary_errors
WHERE detected_at >= :incident_start;

문제가 생긴 range만 다시 흘리면 운영 부하와 검증 범위가 줄어든다.

range가 좁아도 idempotent write와 중복 검사는 유지한다.

운영 지표

  • checkpoint position: 마지막 성공 위치다.
  • checkpoint age: 성공 위치가 얼마나 오래 멈췄는지다.
  • processed count by run: run별 처리량이다.
  • validation failed count: 처리 후 검증 실패 수다.
  • replay duplicate skip count: 재처리 중 중복 skip 수다.
  • reprocess lag: 목표 range 끝과 checkpoint 차이다.

checkpoint가 움직인다고 항상 좋은 것은 아니다.

validation failed count가 늘면서 checkpoint만 앞으로 가면 오염을 확산시키는 것이다.

개인 프로젝트 기준

작은 프로젝트에서는 checkpoint table 하나로 충분하다.

run id, source range, checkpoint, status, validation status를 남긴다.

작업을 중간에 죽이고 재시작해 본다.

같은 range를 다시 실행해도 target 결과가 같아야 한다.

검증 실패 상태를 일부러 만들고 checkpoint를 성공으로 처리하지 않는지 확인한다.

위험 신호!

  • checkpoint가 어떤 source 기준인지 문서화되어 있지 않다.
  • 처리 전에 checkpoint를 먼저 옮긴다.
  • 처리 완료와 검증 완료 상태가 구분되지 않는다.
  • 재처리 범위를 “전체”로만 실행할 수 있다.
  • checkpoint를 수동 수정하고 작업 이력을 남기지 않는다.

확인 질문

확인 질문

  • 이 작업의 checkpoint는 offset, id, timestamp, partition 중 무엇인가?
    • source 기준과 정렬 순서를 함께 답해야 한다.
  • crash가 나면 중복과 누락 중 어느 쪽이 발생하도록 설계했는가?
    • 보통 중복을 허용하고 멱등성으로 흡수한다.
  • checkpoint를 성공으로 옮기기 전에 어떤 검증이 끝나야 하는가?
    • batch-level 또는 range-level validation 기준이 필요하다.

참고 문서