왜 Idempotency가 데이터 엔지니어링에서 그렇게 중요한가

발행: (2025년 12월 14일 오전 09:10 GMT+9)
13 min read
원문: Dev.to

Source: Dev.to

데이터 엔지니어링에서는 실패가 일상입니다: 작업이 크래시하고, 네트워크가 타임아웃되며, Airflow가 작업을 재시도하고, Kafka가 메시지를 재생하고, 백필이 수개월치 데이터를 다시 실행합니다. 이런 실패가 빈번한 환경에서 멱등성(idempotency) 은 데이터가 올바르고, 신뢰할 수 있으며, 정상적으로 유지되도록 해줍니다.

멱등성이란?

프로세스가 한 번 실행하든 여러 번 실행하든 최종 결과가 동일하면 그 프로세스는 멱등적이라고 합니다.

  • 예시: 2025‑01‑01 날짜의 데이터를 처리하는 작업
    • 한 번 실행 → 올바른 결과
    • 두 번 실행 → 동일한 올바른 결과
    • 열 번 실행 → 여전히 동일한 결과

중복도 없고, 데이터가 부풀어 오르지도 않으며, 손상도 없습니다.

분산 데이터 시스템에서 멱등성이 중요한 이유

현대 파이프라인은 분산되어 있습니다:

  • Spark 작업은 executor 손실로 실패할 수 있음
  • Airflow(또는 Dagster, Prefect) 작업은 자동으로 재시도함
  • 클라우드 스토리지는 종종 최종 일관성을 제공함
  • API는 요청 중에 타임아웃될 수 있음

멱등성이 없으면 재시도 시 다음과 같은 문제가 발생할 수 있습니다:

  • 데이터가 이중 집계됨
  • 부분적인 쓰기로 테이블이 손상됨
  • “실패를 고치는” 과정에서 새로운 버그가 생김

멱등성은 재시도를 위험이 아닌 기능으로 바꿔줍니다. 오케스트레이터는 작업을 안전하게 재시도할 수 있다고 가정합니다; 작업이 멱등적이지 않다면 재시도가 조용히 데이터 오류를 만들고, “녹색 DAG”가 나쁜 데이터를 숨기며, 디버깅이 거의 불가능해집니다.

백필(Backfills)

백필은 불가피합니다(로직 변경, 버그 수정, 늦게 도착한 데이터, 스키마 진화 등). 멱등적인 파이프라인을 사용하면 다음을 할 수 있습니다:

  • 과거 데이터를 자신 있게 다시 실행
  • 수동 정리를 피함
  • 특별한 백필 코드 경로를 없앰

멱등성이 없으면 모든 백필이 고위험 작업이 되고, 엔지니어는 오래된 데이터를 건드리는 것을 두려워하며, 기술 부채가 쌓입니다.

Exactly‑Once vs. At‑Least‑Once

  • Exactly‑once 보장은 복잡하고 비용이 많이 듭니다.
  • 분산 시스템은 보통 at‑least‑once 전달을 제공합니다.

멱등성은 중복을 우아하게 처리함으로써 at‑least‑once 전달을 안전하게 활용하게 해줍니다.

멱등 파이프라인 설계

안정적인 기본 키 & Upserts

안정적인 기본 키(예: order_id, user_id + event_time, 혹은 비즈니스 속성의 해시)를 사용하세요. 그런 다음 읽을 때 중복 제거하거나 쓰기 시 병합(upsert)합니다:

MERGE INTO users u
USING staging_users s
ON u.user_id = s.user_id
WHEN MATCHED THEN UPDATE SET ...
WHEN NOT MATCHED THEN INSERT ...

맹목적인 추가보다 INSERT OVERWRITE 혹은 MERGE를 선호합니다:

INSERT OVERWRITE TABLE sales PARTITION (date='2025-01-01')
SELECT * FROM staging_sales WHERE date='2025-01-01';

결정적 변환(Deterministic Transformations)

순수 변환은:

  • 입력에만 의존하고
  • 매번 동일한 출력을 생성합니다

다음과 같은 비결정적 함수는 피하세요:

  • CURRENT_TIMESTAMP
  • 무작위 UUID 생성
  • 핵심 변환 내부에서 외부 API 호출

스트리밍 & 증분 작업

  • 오프셋, 워터마크, 혹은 처리된 타임스탬프를 저장합니다.
  • 같은 윈도우를 재처리해도 아무 변화가 없도록 설계합니다.
  • 데이터 쓰기가 멱등적이고, 다운스트림에 명시적이며, 신중하게 제어되는지 확인합니다.

부수 효과(Side Effects)

이메일, 웹훅, API 호출과 같은 부수 효과는 데이터 변환과 분리합니다. 최종 상태가 성공적으로 기록된 후에만 트리거하고, 부수 효과 자체도 멱등하도록 설계합니다(예: 중복 제거 키 또는 요청 ID 사용).

실용적인 Do’s and Don’ts

Do

  • ✅ 모든 작업을 재시도될 것이라고 가정하고 설계
  • ✅ 맹목적인 추가 대신 overwrite 또는 merge 사용
  • ✅ 작업을 결정적이고 반복 가능하게 만들기
  • ✅ 기본 키와 중복 제거 로직 사용
  • ✅ 백필을 일등급(use‑case)으로 다루기
  • ✅ 입력, 출력, 체크포인트를 로깅

Don’t

  • ❌ “이 작업은 한 번만 실행된다”고 가정
  • ❌ 보호 장치 없이 데이터를 추가
  • ❌ 부수 효과와 변환을 혼합
  • ❌ 정확성을 위해 실행 순서에 의존
  • ❌ 핵심 로직에 비결정적 함수 사용
  • ❌ 중복 정리를 사람에게 맡김

파이프라인을 다시 실행하는 것이 두렵다면, 그 파이프라인은 멱등하지 않은 것입니다.

멱등 파이프라인 설계 체크리스트

디자인 리뷰, PR 리뷰, 사고 후 감사 시 이 체크리스트를 활용하세요. 핵심 질문에 답하십시오: “이 파이프라인을 두 번 실행하면 결과가 여전히 올바른가?”

1. 재시도 안전성

  • ⬜ 모든 작업을 수동 정리 없이 재시도할 수 있나요?
  • ⬜ 작업이 중간에 실패하고 다시 실행되면 어떻게 되나요?
  • ⬜ 오케스트레이터(Airflow / Dagster / Prefect)가 작업을 자동으로 재시도하나요?
  • ⬜ 부분 쓰기가 재시도 시 정리되거나 덮어써지나요?
  • ⬜ 명확한 실패 경계(파티션, 배치, 윈도우)가 있나요?
  • 🚩 Red flag: “이 작업은 절대 재시도하지 않는다.”

2. 결정적 입력

  • ⬜ 입력이 명시적으로 범위 지정(date, 파티션, 오프셋, 워터마크)되어 있나요?
  • ⬜ 재처리 시 입력 소스가 안정적인가요?
  • ⬜ 늦게 도착한 레코드가 결정적으로 처리되나요?
  • ⬜ 겹치는 윈도우를 두 번 읽는 것을 방지하고 있나요?
  • 🚩 Red flag: 입력이 “now”, “latest”, 혹은 암시적 상태에 의존.

3. 쓰기 전략

  • ⬜ 쓰기 전략이 overwrite, merge, 혹은 upsert인가요?
  • ⬜ 추가가 중복 제거 또는 제약 조건으로 보호되고 있나요?
  • ⬜ 출력이 결정적인 키(날짜, 시간, batch_id)로 파티셔닝되어 있나요?
  • ⬜ 단일 파티션을 안전하게 다시 쓸 수 있나요?
  • 🚩 Red flag: 보호 장치 없이 맹목적인 INSERT INTO 혹은 파일 추가.

4. 레코드 정체성

  • ⬜ 각 데이터셋에 명확한 기본 키 또는 자연 키가 있나요?
  • ⬜ 중복 제거 로직이 명시적이고 문서화되어 있나요?
  • ⬜ 키가 재시도와 백필 전반에 걸쳐 안정적인가요?
  • ⬜ 중복 제거가 읽기 시, 쓰기 시, 혹은 둘 다 적용되나요?
  • 🚩 Red flag: “중복은 발생하지 않아야 한다.” 라는 가정만 존재.

5. 결정적 변환

  • ⬜ 변환이 결정적인가요?
  • CURRENT_TIMESTAMP, 무작위 UUID, 기타 비결정적 함수가 사용되지 않았나요?
  • ⬜ 외부 API 호출이 핵심 변환에서 제외되었나요?
  • ⬜ 비즈니스 로직이 실행 순서에 독립적인가요?
  • 🚩 Red flag: 작업을 실행할 때마다 출력이 달라짐.

6. 증분 로직

  • ⬜ 오프셋, 체크포인트, 워터마크가 신뢰성 있게 저장되나요?
  • ⬜ 같은 범위를 재처리해도 안전한가요?
  • ⬜ “at‑least‑once” 전달이 올바르게 처리되나요?
  • ⬜ 파이프라인이 역사 데이터를 손상 없이 재생할 수 있나요?
  • 🚩 Red flag: “이 토픽/테이블은 재생할 수 없다.”

7. 백필 친화성

  • ⬜ 임의의 과거 범위에 대해 파이프라인을 실행할 수 있나요?
  • ⬜ 백필 로직이 일반 로직과 동일한가요?
  • ⬜ 오래된 파티션을 다시 실행하면 덮어쓰기 또는 병합이 깔끔하게 이루어지나요?
  • ⬜ 백필 중 다운스트림 소비자가 보호되나요?
  • 🚩 Red flag: 백필을 위한 별도 스크립트나 수동 SQL이 필요.

8. 격리된 부수 효과

  • ⬜ 이메일, 웹훅, API 호출이 핵심 데이터 로직과 분리되어 있나요?
  • ⬜ 부수 효과가 성공적인 완료 후에만 트리거되나요?
  • ⬜ 부수 효과 자체가 멱등적인가요(중복 키, 요청 ID 등)?
  • ⬜ 이중 알림을 방지하는 보호 장치가 있나요?
  • 🚩 Red flag: 변환 단계 안에 부수 효과가 포함.

9. 조기 감지

  • ⬜ 재실행 시 행 수가 일관적인가요?
  • ⬜ 데이터 품질 검사가 재실행에 안전한가요?
  • ⬜ 중복, null, 데이터 드리프트가 모니터링되고 있나요?
  • ⬜ 재실행 및 백필에 대한 라인에지가 명확한가요?
  • 🚩 Red flag: 데이터가 예상치 못하게 변했는지 알 방법이 없음.

10. 문서화 & 소유권

  • ⬜ 멱등성 동작이 문서화되어 있나요?
  • ⬜ 새로운 엔지니어가 파이프라인을 안전하게 재실행할 수 있나요?
  • ⬜ 복구 절차가 자동화돼 있고, 수동이 아닌가요?

멱등성은 단순한 기술적 디테일이 아니라 데이터 시스템을 더 탄력적이고, 운영하기 쉬우며, 유지 비용을 낮추고, 신뢰성을 높이는 설계 철학입니다. 재처리가 불가피하고 실패가 일상인 데이터 엔지니어링에서 멱등성은 취약한 파이프라인과 프로덕션 급 시스템을 가르는 차이점입니다.

Back to Blog

관련 글

더 보기 »

내가 이해하려는 것

배경: 저는 중간 수준의 소프트웨어 엔지니어이며, 경력을 시작할 때 주로 Ruby on Rails를 사용해 웹 애플리케이션을 구축했습니다. 시간이 지나면서 다른 …