데이터 파이프라인에서 멱등성 Idempotency와 재처리 Retry는 배치가 중간에 실패해도 데이터를 중복 인서트하거나 손상시키지 않기 위한 핵심 설계입니다. 특히 Azure 환경에서 SAP, MSSQL 같은 원천 데이터를 적재하고 모델링하는 구조라면, 한 번 실패한 작업을 다시 돌려도 결과가 같아야 한다는 원칙을 처음부터 넣어야 운영이 편해집니다.
핵심은 단순히 재시도 횟수를 늘리는 것이 아니라, 재실행 자체가 안전하도록 저장 방식, 키 설계, 트랜잭션 경계, 상태 관리, 검증 절차를 같이 설계하는 것입니다.

멱등성이 필요한 이유
데이터 파이프라인은 네트워크 지연, 소스 장애, 타임아웃, 중간 트랜잭션 실패 같은 이유로 같은 작업이 여러 번 실행될 수 있습니다. 이때 멱등성이 없으면 같은 배치를 다시 돌렸을 때 중복 적재, 집계 중복, 일부 행만 반영된 불완전한 상태가 생깁니다.
예를 들어 주문 데이터를 적재할 때 배치가 80퍼센트 지점에서 실패했다가 다시 시작되면, 이미 들어간 80퍼센트가 다시 들어가면 안 됩니다. 반대로 아무 것도 안 들어간 것처럼 다시 처음부터 실행해도 최종 결과는 같아야 합니다.
설계 원칙
멱등성 설계는 보통 다음 순서로 잡습니다.
- 데이터의 고유 식별자를 먼저 정합니다. 예를 들면 business key, source system key, event id, document number, order id 같은 값입니다.
- 적재 단위를 날짜나 배치 실행 단위로 나눕니다. 같은 구간을 여러 번 처리해도 이전 결과를 덮어쓰거나 동일하게 유지되게 합니다.
- INSERT만 쓰지 말고 UPSERT, MERGE, overwrite partition 같은 방식으로 재실행 안전성을 확보합니다.
- 처리 상태를 별도 테이블에 기록합니다. 어떤 배치가 시작됐고, 성공했고, 실패했고, 어디까지 반영됐는지 남겨야 합니다.
- 부작용이 있는 작업은 데이터 적재와 분리합니다. 이메일 발송, 알림, 외부 API 호출은 데이터 저장과 같은 트랜잭션에 묶지 않는 편이 안전합니다.
재처리 전략
Retry는 무조건 많이 하는 것이 아니라, 실패 유형에 따라 다르게 적용해야 합니다. 일시적 네트워크 문제나 잠깐의 락 경합은 재시도로 해결될 수 있지만, 스키마 불일치나 데이터 오류는 반복 재시도해도 해결되지 않습니다.
실무에서는 다음처럼 나눕니다.
- Transient failure. 타임아웃, 일시적 연결 끊김, 잠깐의 서비스 오류.
- Permanent failure. 잘못된 컬럼 타입, 제약 조건 위반, 소스 데이터 자체 오류.
- Partial failure. 일부 테이블만 적재 성공, 일부 테이블 실패.
- Unknown failure. 응답을 못 받았지만 실제로는 커밋됐을 가능성이 있는 경우.
재시도 로직은 지수 백오프, 최대 시도 횟수, 실패 구간 분리, 체크포인트 기반 재개를 함께 써야 합니다. 즉, 처음부터 전량 재시도하기보다 실패한 구간만 다시 처리하는 구조가 안정적입니다.
SQL Server 적재 예시
아래는 SQL Server에서 멱등적 적재를 구현할 때 자주 쓰는 패턴입니다. 핵심은 staging 테이블에 먼저 넣고, target 테이블에는 키 기준으로 MERGE 또는 업데이트 후 인서트를 하는 방식입니다.
BEGIN TRAN;
MERGE dbo.SalesTarget AS T
USING dbo.SalesStage AS S
ON T.SalesId = S.SalesId
WHEN MATCHED AND (
T.Amount <> S.Amount
OR T.CustomerId <> S.CustomerId
OR T.OrderDate <> S.OrderDate
) THEN
UPDATE SET
T.Amount = S.Amount,
T.CustomerId = S.CustomerId,
T.OrderDate = S.OrderDate,
T.UpdatedAt = SYSUTCDATETIME()
WHEN NOT MATCHED BY TARGET THEN
INSERT (
SalesId,
CustomerId,
Amount,
OrderDate,
CreatedAt,
UpdatedAt
)
VALUES (
S.SalesId,
S.CustomerId,
S.Amount,
S.OrderDate,
SYSUTCDATETIME(),
SYSUTCDATETIME()
);
COMMIT TRAN;
이 방식의 장점은 같은 SalesId가 다시 들어와도 중복 인서트가 아니라 갱신으로 처리된다는 점입니다. 다만 MERGE는 운영 환경에서 예상치 못한 동작이 나올 수 있어, 많은 팀이 UPDATE 후 INSERT 또는 DELETE 후 INSERT 패턴을 더 선호하기도 합니다. 중요한 것은 도구가 아니라 같은 입력에 같은 최종 상태를 보장하는 구조입니다.
배치 실행 이력 관리
배치 재처리에서 가장 중요한 것 중 하나는 실행 이력입니다. 어떤 구간을 언제 돌렸고, 성공했는지, 몇 건이 반영됐는지를 기록해야 재실행 시 기준을 잡을 수 있습니다.
예를 들면 아래처럼 실행 로그 테이블을 둡니다.
CREATE TABLE dbo.BatchRunLog (
BatchRunId BIGINT IDENTITY(1,1) PRIMARY KEY,
PipelineName NVARCHAR(100) NOT NULL,
BusinessDate DATE NOT NULL,
Status NVARCHAR(20) NOT NULL,
SourceRowCount INT NULL,
TargetRowCount INT NULL,
StartedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),
EndedAt DATETIME2 NULL,
ErrorMessage NVARCHAR(4000) NULL
);
이 테이블이 있으면 PipelineName + BusinessDate 조합으로 이미 성공한 적이 있는지 확인할 수 있습니다. 성공한 배치는 다시 돌리지 않거나, 돌리더라도 overwrite 방식으로 동일한 결과가 나오게 만들 수 있습니다.
체크포인트 기반 재처리
대량 배치에서는 한 번에 전체를 다시 돌리는 대신 체크포인트를 쓰는 것이 효율적입니다. 예를 들어 100만 건 중 32만 건까지 처리했으면 마지막 성공 키나 마지막 offset을 저장해두고 거기서부터 이어서 처리합니다.
체크포인트 방식의 핵심은 다음과 같습니다.
- 마지막 성공 시점 또는 마지막 키를 저장합니다.
- 처리 중간에 실패하면 그 지점 이후만 다시 읽습니다.
- 읽기 순서가 항상 동일해야 합니다.
- 정렬 기준이 흔들리면 중복 또는 누락이 생길 수 있으니 고정된 key가 필요합니다.
이 방식은 CDC, 파일 ingest, API pull, 큐 기반 처리 모두에 적용할 수 있습니다. 다만 정렬 기준이 바뀌는 소스에서는 꼭 커서 키를 안정적으로 잡아야 합니다.
실패 유형별 대응
모든 실패를 같은 방식으로 처리하면 운영이 힘들어집니다. 그래서 실패를 분리해서 대응하는 구조가 좋습니다.
- 소스 일시 장애는 자동 재시도합니다.
- 데이터 품질 오류는 실패 행만 격리하고 나머지는 진행하거나, 아예 배치를 중단합니다.
- 중복 가능성이 있는 장애는 멱등 키로 방어합니다.
- 커밋 여부가 불확실하면 같은 배치를 다시 돌려도 결과가 같도록 설계합니다.
특히 SQL Server 같은 관계형 DB에는 유니크 제약 조건을 적극 활용하는 편이 좋습니다. 유니크 인덱스는 중복 인서트의 마지막 방어선이 됩니다.
CREATE UNIQUE INDEX UX_SalesTarget_SalesId
ON dbo.SalesTarget (SalesId);
이렇게 해두면 코드가 실수해도 같은 키가 두 번 들어가는 상황을 막을 수 있습니다. 물론 인덱스만 믿지 말고, 적재 로직 자체도 멱등하게 만들어야 합니다.
운영 관점 체크포인트
실제 운영에서는 아래 항목을 같이 봐야 안정적입니다.
- 재시도 전용 구간과 영구 실패 구간을 분리합니다.
- 배치별 input snapshot을 고정합니다.
- target 적재 전 staging 검증을 넣습니다.
- row count, checksum, hash total 같은 검증 지표를 남깁니다.
- 중복 방지를 위해 business key를 표준화합니다.
- 알림은 중복 발송되지 않도록 별도 idempotency key를 둡니다.
이런 구조를 갖추면 장애가 나도 운영자가 수동으로 데이터를 지우고 다시 넣는 일이 크게 줄어듭니다. 결과적으로 배치 안정성, 복구 속도, 감사 추적성이 함께 좋아집니다.
실무 적용 순서
실제로 설계할 때는 아래 순서로 잡으면 됩니다.
- 소스의 고유 키를 정합니다.
- 적재 단위를 배치 날짜 또는 커밋 단위로 정합니다.
- staging 테이블을 둡니다.
- target은 upsert 또는 overwrite 구조로 만듭니다.
- batch run log를 둡니다.
- 재시도 정책을 transient와 permanent로 나눕니다.
- 유니크 제약과 검증 쿼리를 추가합니다.
이 순서대로 하면 중간 실패 후 재실행해도 중복 인서트나 데이터 손상이 거의 없는 구조를 만들 수 있습니다.
멱등성은 단순한 개발 습관이 아니라 데이터 파이프라인의 복구 능력을 결정하는 설계 원칙입니다. Retry는 보조 장치이고, 진짜 핵심은 몇 번 다시 돌려도 결과가 같아지는 구조를 먼저 만드는 데 있습니다.
SQL Server를 쓰는 환경이라면 staging, MERGE 또는 upsert, 유니크 인덱스, 실행 로그, 체크포인트를 함께 두는 방식이 가장 실무적입니다. 이렇게 설계하면 장애가 나도 배치를 처음부터 다시 돌릴 수 있고, 운영자는 데이터 정합성을 크게 걱정하지 않아도 됩니다.
놓치면 아쉬운 추천 글, 함께 읽어보세요!
- 추천 글을 불러오는 중입니다...