재실행 안전성

Idempotent Pipeline

개념

같은 구간을 두 번 세 번 다시 돌려도 결과가 처음 한 번 돌렸을 때와 같아지는 성질입니다.

등장 배경

월요일 아침에 지현이 대시보드를 열었더니 지난 금요일 매출이 평소의 두 배였습니다. 영업이 갑자기 잘된 것이 아니라, 새벽 세 시 배치가 중간에 멈춰서 누군가 아침에 한 번 더 돌렸고 그 결과가 원래 있던 값 위에 더해진 것이었습니다. 만든 쪽은 복구했다고 생각했고, 본 쪽은 금요일에 무슨 일이 있었는지를 반나절 찾았습니다.

이런 일이 생기는 이유는 파이프라인이 실패하기 때문이 아닙니다. 파이프라인은 원래 실패합니다. 원천 시스템이 점검에 들어가고, 파일이 늦게 도착합니다. 문제는 실패한 뒤에 있습니다. 다시 돌려도 되는 작업과 다시 돌리면 안 되는 작업이 섞여 있는데, 그 구분이 코드 안에만 있어서 밖에서는 알 수가 없었습니다.

그래서 당직자는 실패 알림을 받아도 손을 대지 못했습니다. 그냥 다시 돌려도 되는지, 어딘가를 먼저 지워야 하는지 아는 사람은 그 작업을 만든 본인뿐이었습니다.

재실행 안전성은 그 질문을 아예 없애자는 쪽으로 방향을 잡은 결과입니다. 작업마다 다시 돌려도 되는지 확인하는 대신, 모든 작업을 다시 돌려도 되게 만들어 둡니다. 그러면 실패했을 때 할 일은 하나로 줄어듭니다. 그냥 다시 돌립니다.

작동 방식

결과를 기존 값에 더하지 않고 그 구간을 통째로 덮어쓰는 방식으로 만듭니다. 다시 돌린 결과가 앞선 결과를 대체하면 몇 번을 돌리든 남는 값은 하나입니다.

실제로 지켜야 하는 것은 넷입니다.

  1. 구간을 날짜 같은 단위로 명시적으로 쪼갭니다. 결과 테이블에 처리 대상 날짜를 칸으로 두고 그 칸을 기준으로 데이터를 나눠 둡니다. 5월 1일치를 다시 쓰려면 5월 1일 행이 어디까지인지 먼저 가려낼 수 있어야 합니다.
  2. 쓰기 전에 그 구간의 기존 결과를 지웁니다. 5월 1일 행을 전부 지우고 새로 계산한 행을 넣습니다. 행 하나를 고유하게 가리키는 열인 기본 키가 잡혀 있으면 지우는 단계 없이 같은 키의 행을 덮어쓰는 방식도 씁니다.
  3. 입력을 시점이 아니라 구간으로 읽습니다. 최근 24시간처럼 실행한 순간을 기준으로 가져오면 이틀 뒤에 돌렸을 때 전혀 다른 범위가 들어옵니다. 5월 1일 0시부터 24시까지로 못 박아 두어야 언제 돌려도 같은 입력이 들어옵니다.
  4. 처리 기록을 남깁니다. 어느 구간을 언제 몇 번째로 돌렸는지 적어 두면, 숫자가 이상할 때 계산을 의심할지 실행 횟수를 의심할지 바로 갈립니다.

거꾸로 이 성질을 깨는 코드는 모양이 정해져 있습니다. 누적 합계 칸에 이번 결과를 더하는 코드, 실행할 때마다 새 번호를 붙여 행을 쌓는 코드, 실행 시각이나 무작위 값을 계산에 쓰는 코드입니다. 앞의 두 가지는 정해진 주기에 모아서 한꺼번에 처리하는 배치 처리에서 특히 자주 나옵니다. 하루에 한 번만 도는 것을 전제로 짜 두면 두 번 도는 날이 사고가 됩니다.

작업 순서와 의존을 관리하는 워크플로 오케스트레이터를 쓰면 어느 구간이 실패했는지를 도구가 알고 있어 그 구간만 골라 다시 돌릴 수 있습니다. 다만 도구가 이 성질을 만들어 주지는 않습니다. 덮어쓰기로 짜 두지 않은 작업은 도구가 다시 돌려도 똑같이 두 배가 됩니다.

쓰이는 곳

가장 많이 쓰이는 자리는 백필입니다. 과거 구간을 다시 처리해 채워 넣는 작업인데, 지난 1년치를 새 기준으로 다시 계산하는 일이 여기 해당합니다. 재실행 안전성이 없으면 백필은 복구가 아니라 사고가 됩니다. 1년치를 돌린 순간 모든 날의 숫자가 두 배로 부풀기 때문입니다.

그다음은 지표 정의가 바뀔 때입니다. 활성 사용자에서 내부 계정을 빼기로 했다면 과거 숫자도 같은 기준으로 다시 계산해야 비교가 됩니다. 데이터를 믿고 쓸 수 있는 상태로 유지하는 데이터 품질 작업의 상당 부분이 이렇게 기존 값을 지우고 새로 쓰는 일입니다.

들어오는 즉시 흘려보내며 처리하는 스트림 처리에서도 같은 성질이 필요합니다. 한 건이 도중에 끊기면 대부분의 도구가 그 건을 다시 흘려보내는데, 받는 쪽이 같은 건을 두 번 더하면 실시간 숫자가 조용히 어긋납니다.

요청 하나를 놓고 보는 같은 성질이 멱등성입니다. 서버가 같은 요청을 두 번 받아도 한 번만 처리하게 만드는 쪽이고, 재실행 안전성은 그 생각을 작업 하나가 아니라 구간 전체로 넓힌 것입니다.

실무 용례

"이 작업 다시 돌려도 되나요? 재실행 안전한가요?"

실패한 구간을 그냥 한 번 더 실행해도 숫자가 어긋나지 않는지 묻는 말입니다.

"새벽 배치가 중간에 죽었는데 재실행 안전하게 짜 놔서 그냥 다시 돌렸습니다."

손으로 지우고 넣는 단계 없이 복구가 끝났다는 보고입니다.

"이건 아직 재실행 안전하지 않아요. 돌리기 전에 그 구간 먼저 지워 주세요."

안전장치가 코드에 없으니 사람이 대신 그 일을 하라는 요청입니다.

페이지덱에서는 새 집계 작업을 만들 때 지현이 태오에게 늘 같은 것을 확인합니다. 5월 1일치를 세 번 돌리면 어떻게 되느냐입니다. 답이 "같은 값이 나옵니다" 가 아니면 작업은 아직 완성이 아닙니다. 계산이 맞는지는 그다음 문제입니다.

바뀐 것은 파이프라인이 덜 실패하게 된 것이 아니라, 실패했을 때 만든 사람을 찾지 않아도 되게 된 것입니다.

흔한 오해

실패하지 않게 잘 만들면 필요 없다고 보는 경우가 가장 많습니다. 다시 돌리는 일이 사고 대응처럼 보이니 사고를 안 내면 그만이라는 생각이 따라옵니다. 하지만 재실행은 사고가 아니라 일상입니다. 지표 정의가 바뀌고, 원천 데이터가 하루 늦게 도착하고, 컬럼을 하나 추가해 과거치를 다시 채우는 일이 매달 일어납니다. 이 성질 없이 버티면 구간마다 손으로 지우는 절차가 문서로 쌓이고, 그 문서를 아는 사람이 휴가를 가면 아무도 숫자를 고치지 못합니다.

재실행 안전하면 아무 때나 돌려도 된다고 읽는 것도 흔합니다. 안전하다는 말이 언제 돌려도 옳은 답이 나온다는 뜻처럼 들리기 때문입니다. 보장되는 것은 그게 아니라 같은 입력이면 같은 결과입니다. 원천 데이터가 아직 절반만 도착한 구간을 다시 돌리면 절반짜리 결과가 멀쩡한 결과를 덮어씁니다. 복구하려고 돌린 작업이 오히려 숫자를 지우는 셈인데, 원인이 실행 시점에 있어 코드를 들여다봐도 이유가 보이지 않습니다.

같이 보면 좋은 페이지

  • 데이터 파이프라인

    이 성질을 갖춰야 하는 흐름 전체를 먼저 봅니다

  • 백필

    이 성질이 실제로 쓰이는 가장 흔한 자리입니다

마지막 업데이트 2026-09-16