우리 파이프라인이 같은 날 데이터를 47번 처리한 시간

발행: (2025년 12월 18일 오전 12:29 GMT+9)
16 min read
원문: Dev.to

Source: Dev.to

월요일 아침에 Airflow 로그에서 이상한 점을 발견했습니다. 우리 일일 데이터 파이프라인이 하루에 한 번이 아니라 주말 내내 여러 번 실행되었습니다.

몇 번의 추가 실행이 아니라 47 executions, 모두 같은 날짜의 데이터인 December 3rd를 처리했습니다.

각 실행은 성공으로 표시되었습니다. 오류도 없었고, 알림도 없었습니다. 같은 날짜의 데이터가 계속해서 처리된 것이었습니다.

다음은 발생한 일과 제가 더 일찍 알았으면 좋았던 재시도 로직에 대해 배운 점입니다.

어떻게 찾았는가

월요일 아침, 나는 일상 점검의 일환으로 주말 파이프라인 실행을 검토하고 있었다. Airflow 대시보드에 이상한 패턴이 보였다 – 메인 변환 DAG가 예상보다 훨씬 많이 실행되고 있었다.

자세히 살펴보니, 해당 DAG가 토요일 아침부터 월요일까지 총 47번 실행되었는데, 우리는 하루에 한 번 오전 2시에만 스케줄링하고 있었다.

내가 주목한 점: 모든 실행이 12월 3일 데이터만을 처리하고 있었다. 12월 4일, 5일, 6일이 아니라 – 오직 12월 3일 데이터만이 반복해서 처리되고 있었다.

모든 실행은 성공(녹색 상태)으로 표시되었고, 실패한 작업은 없었다. 로그에는 정상적인 처리 과정이 기록되어 있었다 – 데이터를 읽고, 변환하고, 웨어하우스에 쓰고, 완료 표시.

조사

  • 누군가 수동으로 재실행을 트리거했나요?
    아니요. 감사 로그에 모든 실행이 자동으로 스케줄러에 의해 트리거된 것으로 나타났습니다.

  • 소스 데이터가 변경되었나요?
    아니요. S3 타임스탬프를 확인한 결과 12월 3일 데이터는 처음 생성된 이후로 수정된 적이 없습니다.

  • 스케줄러 설정에 문제가 있었나요?
    스케줄은 정상적으로 보였습니다: 매일 새벽 2시.

그런 다음 실행 기록에서 무언가를 발견했습니다. 패턴이 토요일에 시작되었습니다. 파이프라인이 새벽 2시(정상) 에 실행된 뒤, 새벽 4시, 6시, 8시… 주말 내내 2시간 간격으로 다시 실행되었습니다.

그때 깨달았습니다: 이것들은 예약된 실행이 아니라 재시도였습니다.

배경

지난 금요일에 우리는 새로운 분석 기능을 배포했습니다 – 고객 세그먼트별 평균 거래 가치를 계산하는 기능입니다. 마케팅 팀은 일반 고객과 별도로 프리미엄 고객 행동을 추적하고 싶어했습니다.

코드는 철저히 테스트되었습니다. 지난 주의 샘플 데이터를 사용해 실행했으며, 모든 테스트를 통과했습니다. 우리는 금요일 오후에 배포했습니다.

우리가 테스트하지 않은 부분: 주말 데이터 패턴.

Source:

근본 원인

우리 파이프라인은 Airflow의 execution_date를 사용해 처리할 데이터 파티션을 결정했습니다:

execution_date = context['execution_date']
data_date = execution_date.strftime('%Y-%m-%d')
s3_path = f"s3://bucket/data/date={data_date}/"

파이프라인 단계

  1. S3에서 데이터 읽기
  2. 레코드 변환 및 검증
  3. 일일 지표 계산
  4. 웨어하우스에 기록

3단계에서 주말에 문제가 발생했습니다.

우리의 새로운 지표는 “고객 세그먼트별 평균 거래 가치”를 계산했습니다:

# Calculate average for our premium customer segment
target_customers = df[df['customer_segment'] == 'premium']
total_value = target_customers['amount'].sum()
customer_count = target_customers['customer_id'].nunique()
avg_value = total_value / customer_count

평일에 테스트했을 때는 정상적으로 작동했습니다:

날짜프리미엄 고객 수결과
12월 3일 (수)8,500성공
12월 4일 (목)7,200성공
12월 5일 (금)6,800성공
12월 6일 (토)0실패

우리 프리미엄 세그먼트는 전적으로 B2B 고객, 즉 비즈니스 계정 및 기업 고객으로 구성되었습니다. 기업이 휴무인 주말에는 거래가 발생하지 않습니다.

토요일에 프리미엄 고객이 0명이었고, 이로 인해 0으로 나누는 오류가 발생했습니다:

customer_count = target_customers['customer_id'].nunique()  # Returns 0
avg_value = total_value / 0  # Division by zero error

작업이 실패했고 Airflow는 재시도를 예약했습니다. 우리가 작성한 재시도 로직은 다음과 같습니다:

if task_instance.try_number > 1:
    # If this is a retry, process the last successful date
    # to avoid reprocessing potentially corrupted data
    last_successful = get_last_successful_date()
    data_date = last_successful
else:
    data_date = execution_date.strftime('%Y-%m-%d')

의도는 좋았습니다: 작업이 중간에 실패하면 잠재적으로 손상된 데이터를 다시 처리하지 않고, 마지막으로 정상적인 날짜로 돌아가도록 하는 것이었습니다.

실제로 일어난 일:

  1. 12월 6일 처리 중 (0으로 나누기) 실패.
  2. 재시도가 트리거되고 try_number = 2가 되면서 코드가 last_successful = 12월 3일을 가져옴.
  3. 재시도가 12월 3일 데이터를 처리 (프리미엄 거래가 있었음).
  4. 계산이 성공하고 Airflow는 12월 6일 실행을 완료된 것으로 표시.

같은 일이 12월 7일(일)에도 발생했으며, 월요일 아침에 중단할 때까지 주말 내내 계속되었습니다.

영향

즉각적인 문제는 중복 데이터였습니다. 우리는 12월 3일의 트랜잭션을 47번 우리 데이터 웨어하우스에 로드했습니다.

  • 중복 제거 로직이 대부분을 잡아냈습니다 – 트랜잭션 ID를 기본 키로 사용했기 때문에 데이터베이스가 동일한 레코드를 단순히 덮어썼습니다.
  • 그러나 모든 하위 보고서가 중복을 제거한 것은 아닙니다. 일부 집계 테이블은 각 로드를 새로운 데이터로 계산했습니다. 월요일 아침 몇 시간 동안 대시보드에는 12월 3일이 정상 거래량의 47× 로 표시되었습니다.

더 큰 문제: 이 버그는 주말(또는 특정 구간에 데이터가 없는 경우) 때문에 작업이 실패할 때 과거 데이터를 조용히 손상시킬 수 있는 재시도 전략의 결함을 드러냈습니다.

교훈

  • 실제 주말 데이터를 테스트하세요 – 특히 새로운 로직이 특정 날짜에 데이터가 없을 수 있는 경우에 의존한다면.
  • 재시도 로직은 멱등성(idempotent)이어야 하며, 중복을 일으킬 가능성이 확실히 없을 때만 과거 날짜를 다시 처리해야 합니다.
  • 0으로 나누는 오류 방지(또는 빈 데이터셋에서 실패할 수 있는 모든 연산)를 명시적인 체크로 방어하세요.
  • 비정상적인 재시도 패턴에 대한 알림을 추가하세요(예: 짧은 시간 내에 많은 재시도)하여 조기에 감지할 수 있도록 합니다.

execution_date를 동일하게 재처리하도록(또는 데이터가 없을 경우 실행 자체를 건너뛰도록) 재시도 로직을 단순화하고 0으로 나누는 방어 코드를 수정함으로써 추가적인 중복 로드를 방지하고 파이프라인에 대한 신뢰를 회복했습니다.

The Problem

우리는 12월 6일 또는 12월 7일에 대한 데이터가 없었습니다. 파이프라인은 해당 날짜들을 성공적으로 처리한 것으로 판단했으며(실제로는 12월 3일을 처리했기 때문에), 그래서 12월 8일로 넘어갔습니다.

우리는 주말 데이터 이틀을 놓쳤으며, 비즈니스 사용자가 주말 매출 보고서가 비어 있는 이유를 물어볼 때까지 이를 깨닫지 못했습니다.

수정 내용

두 가지를 고쳤습니다:

1️⃣ 즉시 해결 버그 – 계산에서 고객 수가 0인 경우 처리

target_customers = df[df['customer_segment'] == 'premium']
customer_count = target_customers['customer_id'].nunique()

if customer_count > 0:
    avg_value = target_customers['amount'].sum() / customer_count
else:
    # 이 세그먼트에 고객이 없으므로 실패 대신 NULL로 설정
    avg_value = None

2️⃣ 재시도 로직 – 완전히 제거

# 재시도 횟수와 관계없이 항상 실행 날짜를 처리
data_date = execution_date.strftime('%Y-%m-%d')

핵심 인사이트: 재시도는 같은 데이터를 다시 처리해야 하며, 이전 데이터를 되돌아가서 사용해서는 안 됩니다. 실제 데이터 문제가 있으면 재시도로는 해결되지 않으며, 일시적인 문제라면 같은 작업을 재시도하는 것이 효과적입니다.

주말‑특화 업데이트

# 주말 데이터 참고: 프리미엄 세그먼트(B2B)는 주말 활동이 0입니다
# 이는 예상된 동작이며, 주말 지표는 NULL로 기록합니다

배운 점

  • 현실적인 데이터 패턴으로 테스트하세요. 우리는 편리해서 평일 데이터만 테스트했습니다. 주말, 공휴일, 월말 데이터 등 모든 엣지 케이스도 테스트해야 합니다.
  • 재시도 로직은 신중히 설계해야 합니다. “마지막 성공 날짜”를 안전한 대체값으로 가정한 것은 잘못된 판단이었습니다. 재시도는 다른 데이터가 아니라 동일한 데이터를 다시 처리해야 합니다.
  • 분할 by zero(0으로 나누기)는 분석에서 흔히 발생합니다. 평균이나 비율을 계산할 때는 0인 경우를 명시적으로 처리하여 오류가 발생하지 않도록 해야 합니다.
  • 실패만이 아니라 성공적인 실행도 모니터링하세요. 우리의 모든 알림은 실패에만 초점을 맞췄습니다. 이 실행들은 성공했기 때문에 알림이 없었고, 문제는 로그를 수동으로 검토했을 때만 발견되었습니다.
  • 실행 날짜와 데이터 날짜는 구분해야 합니다. Airflow의 실행 날짜는 작업이 실행되는 시점이며, 처리하는 데이터 날짜는 다를 수 있습니다(특히 재시도 시). 코드에서 두 날짜를 별도로 관리하세요.

사후 상황

수정 후, 파이프라인은 주말 데이터를 정상적으로 처리했습니다:

  • 토요일: 12월 13일 처리 – Premium metrics = NULL (예상). 성공.
  • 일요일: 12월 14일 처리 – Premium metrics = NULL (예상). 성공.
  • 재시도 없음. 중복 처리 없음.

누락된 12월 6일7일 데이터를 수동으로 백필하고, 주말 시나리오에 대한 테스트 케이스를 테스트 스위트에 추가했습니다.

  • 총 디버깅 시간: ~3 시간
  • 주말 데이터 누락 수정에 소요된 시간: ~2 시간

교훈: 특히 주말과 같이 예측 가능한 경우와 같은 엣지 케이스를 항상 테스트하세요.

토론

주말에 문제가 발생한 코드를 금요일에 배포한 적이 있나요? 아니면 재시도 로직이 상황을 더 악화시킨 적이 있나요? 가변적인 데이터 패턴을 가진 메트릭에 대한 데이터 품질 검증을 다른 사람들은 어떻게 처리하는지 듣고 싶습니다.

Connect with me on LinkedIn or check out my portfolio.

읽어 주셔서 감사합니다! 실무 데이터 엔지니어링 스토리와 프로덕션 시스템에서 얻은 교훈을 더 보려면 팔로우해주세요.

Back to Blog

관련 글

더 보기 »

창고 활용에 대한 종합 가이드

소개 창고는 근본적으로 3‑D 박스일 뿐입니다. Utilisation은 실제로 그 박스를 얼마나 사용하고 있는지를 측정하는 지표입니다. While logistics c...