장애가 끝났다고 해서 운영이 끝나는 것은 아닙니다. API가 다시 200을 반환해도, 장애 중 누락된 포인트 적립, 중복 차감된 쿠폰, 잘못 노출된 권한, 부분 적용된 관리자 일괄 수정이 남아 있으면 사용자는 계속 영향을 받습니다. 이때 많은 팀이 급하게 SQL을 짜서 데이터를 맞춥니다. 문제는 그 SQL이 어떤 대상에 적용됐고, 누가 승인했고, 중간에 실패하면 어디서 다시 시작해야 하는지 남지 않는다는 점입니다.

Correction Job은 이런 보정 작업을 개인의 임시 SQL에서 운영 가능한 workflow로 끌어올리는 방식입니다. 앞단의 Reconciliation 파이프라인이 불일치를 찾는 역할이라면, Correction Job은 그 불일치를 어떤 조건에서 실제로 수정할지를 다룹니다. 재실행 안전성은 Batch Idempotency/Reprocessing과 연결되고, 사후 설명 책임은 Tamper-Evident Audit Log와 연결됩니다.

이 글에서 얻는 것

  • 장애 후 데이터 보정을 수동 SQL이 아니라 승인 가능한 운영 작업으로 설계하는 기준을 얻습니다.
  • dry-run, 대상 산정, batch 실행, pause/abort, 종료 검증을 어떤 순서로 배치할지 이해합니다.
  • 금액, 권한, 개인정보, 재고처럼 오보정 비용이 큰 도메인에서 자동 보정과 승인 보정을 나누는 숫자 기준을 잡을 수 있습니다.
  • 보정 이력을 감사 로그와 effect ledger로 남겨, 나중에 “무엇을 왜 바꿨는지” 설명할 수 있습니다.

핵심 개념/이슈

1) Correction Job의 핵심은 UPDATE가 아니라 대상 산정이다

데이터 보정 사고의 대부분은 수정 쿼리 자체보다 대상 산정에서 발생합니다. status = 'FAILED'인 주문을 모두 PAID로 바꾸는 쿼리는 쉬워 보이지만, 그 안에는 실제 실패 주문, 결제 승인 이벤트가 늦게 온 주문, 이미 환불된 주문, 테스트 주문이 섞일 수 있습니다. 따라서 Correction Job은 항상 “무엇을 바꿀지"와 “어떻게 바꿀지"를 분리해야 합니다.

기본 구조는 아래처럼 둡니다.

  1. candidate query: 보정 후보를 찾되 실제 수정은 하지 않음
  2. dry-run report: 건수, 금액 합계, tenant, 위험 유형, 샘플 before/after를 생성
  3. approval gate: 위험도에 따라 자동 적용, 1인 승인, 2인 승인, 보류로 분기
  4. apply job: 작은 batch로 수정하고 effect ledger를 남김
  5. verification: 원본 기준 재검증, 잔존 mismatch, 사용자 영향 closure 확인

숫자 기준은 처음에는 보수적으로 시작합니다. 자동 보정은 estimated_rows <= 10,000, high_risk_rows = 0, expected_amount_delta = 0, dry_run_error = 0, DB CPU p95 < 60%일 때만 허용합니다. 금액이나 권한이 바뀌는 작업은 1건이라도 승인 대상으로 두는 편이 안전합니다.

2) dry-run report는 승인자를 위한 증거 패킷이다

“승인해주세요"라는 메시지만으로는 운영 통제가 되지 않습니다. 승인자가 확인할 수 있는 증거가 있어야 합니다. 좋은 dry-run report에는 최소 다음 항목이 들어갑니다.

항목이유예시 기준
대상 건수영향 범위 판단1만 건 초과 시 배치 window 필요
금액/포인트 합계손실 상한 판단100만 원 초과 시 2인 승인
tenant/user 분포특정 고객 집중 여부한 tenant 30% 초과 시 별도 확인
before/after sample의도한 변경인지 검토위험 유형별 20건 샘플
제외 건수와 사유누락/오탐 확인excluded reason별 비중
중단 조건실행 중 피해 제한error rate 0.5% 초과, lock wait 2초 초과

이 report는 파일로만 남기기보다 job record에 붙이는 편이 좋습니다. 나중에 “왜 이 건을 자동으로 고쳤나"를 묻는 사람이 있으면, 그 시점의 후보 쿼리, dry-run 결과, 승인자, 적용 batch를 한 번에 볼 수 있어야 합니다.

3) 보정 실행은 멱등성과 효과 원장이 있어야 한다

Correction Job은 반드시 재실행될 수 있다고 가정해야 합니다. 중간에 DB lock이 길어져 멈추거나, 배포가 겹치거나, 특정 row에서 validation error가 날 수 있습니다. 재실행 시 같은 row를 다시 바꿔도 안전하려면 effect ledger가 필요합니다.

예시는 아래와 같습니다.

CREATE TABLE correction_effects (
  job_id           varchar(80)  NOT NULL,
  target_type      varchar(80)  NOT NULL,
  target_id        varchar(120) NOT NULL,
  action           varchar(80)  NOT NULL,
  before_hash      varchar(128) NOT NULL,
  after_hash       varchar(128) NOT NULL,
  reason_code      varchar(80)  NOT NULL,
  approved_by      varchar(120),
  execution_batch  integer      NOT NULL,
  applied_at       timestamp    NOT NULL,
  PRIMARY KEY (job_id, target_type, target_id, action)
);

핵심은 job_id + target_id + action을 멱등 키로 쓰는 것입니다. 같은 job이 재시작돼도 이미 처리한 row는 건너뛰고, before hash가 달라졌다면 stale candidate로 보고 중단합니다. 이렇게 해야 보정 작업이 온라인 쓰기와 충돌했을 때 조용히 덮어쓰지 않습니다.

4) 고위험 보정은 rollback보다 compensation을 먼저 생각한다

데이터 보정에서 “rollback 가능"이라는 말은 종종 과장됩니다. 결제나 포인트는 이미 사용자에게 알림이 갔고, 외부 PG나 회계 시스템에 전파됐을 수 있습니다. 이 경우 물리적으로 예전 row로 되돌리는 것이 오히려 더 위험합니다. 대신 반대 방향 원장 이벤트를 추가하는 compensation이 더 안전할 때가 많습니다.

예를 들어 포인트가 1,000점 중복 적립됐다면 기존 원장을 삭제하지 않고 CORRECTION_DEBIT 이벤트를 추가합니다. 쿠폰이 잘못 복구됐다면 쿠폰 상태를 직접 덮어쓰기보다 correction reason과 함께 취소 이벤트를 남깁니다. 권한이 잘못 부여됐다면 revoke 이벤트와 감사 로그를 남기고, 노출 가능 기간을 별도 인시던트 기록으로 닫습니다.

이 사고방식은 Poison Message Quarantine/Safe Replay와도 비슷합니다. 안전한 재처리와 보정은 “성공 로그"가 아니라 재실행 근거와 효과 기록으로 닫아야 합니다.

실무 적용

1) Correction Job 상태 머신을 명시한다

상태가 없으면 운영자는 job이 안전한지 판단할 수 없습니다. 최소 상태는 아래처럼 둡니다.

DRAFT
  -> DRY_RUN_READY
  -> APPROVAL_REQUIRED
  -> APPROVED
  -> APPLYING
  -> PAUSED
  -> VERIFYING
  -> CLOSED
  -> ABORTED

DRAFT에서는 후보 쿼리를 저장하고, DRY_RUN_READY에서는 실제 결과를 만들며, APPROVED 이후에만 쓰기 작업이 가능합니다. PAUSED는 실패가 아니라 정상 제어 상태입니다. 운영 DB CPU가 올라가거나 lock wait가 길어지면 자동으로 멈추고 나중에 재개할 수 있어야 합니다.

2) batch와 throttle 기준을 보수적으로 시작한다

초기 기본값은 아래 정도가 현실적입니다.

  • batch size: 500~2,000 rows
  • batch 간 sleep: 100~500ms
  • 단일 transaction 시간: 2초 이하
  • lock wait p95: 500ms 이하, 2초 초과 3회면 pause
  • DB CPU p95: 65% 초과 5분 지속 시 pause
  • apply error rate: 0.5% 초과 시 abort
  • residual mismatch: apply 후 0.1% 초과면 close 금지

대량 보정은 빨리 끝내는 것이 목표가 아닙니다. 온라인 트래픽을 건드리지 않고, 잘못된 대상을 조기에 발견하고, 중간에 멈춰도 재개 가능한 것이 목표입니다. 대량 입력 작업의 실패 정책은 Bulk Import Job과 같이 보면 좋습니다.

3) 승인 기준은 도메인별로 다르게 둔다

모든 job을 사람이 승인하면 느려지고, 모든 job을 자동화하면 위험합니다. 도메인별 기준을 나눕니다.

도메인자동 가능 조건승인 필요 조건
읽기 모델/검색 인덱스원본 재생성, 사용자 직접 영향 없음권한/삭제 전파 포함
포인트/쿠폰금액성 가치 0원, 파생값 재계산사용자 잔액 변동, 만료 복구
결제/환불dry-run only 권장실제 금액 상태 변경은 2인 승인
권한/RBAC권한 축소 일부 자동 가능권한 부여, 관리자 권한, tenant 이동
개인정보자동 삭제 전파는 가능복구, 재노출, 외부 전송 관련 변경

특히 권한과 개인정보는 처리 건수보다 변경 방향이 중요합니다. 권한 축소 1만 건보다 관리자 권한 부여 1건이 더 위험할 수 있습니다.

4) 종료 기준을 숫자로 닫는다

Correction Job은 “다 돌았다"가 아니라 “검증 기준을 만족했다"로 닫아야 합니다.

  • dry-run 후보 대비 적용 완료율: 99.9% 이상
  • residual mismatch: 일반 데이터 0.1% 이하, 금액/권한/개인정보 0건
  • effect ledger 누락: 0건
  • apply 후 reconciliation 재검사 통과
  • 사용자 고지나 CS 대응이 필요한 건은 ticket 연결 완료
  • 후속 재발 방지 action owner 지정

장애 대응과 연결된 보정이라면 Incident Command와 Severity의 closure 기준에 넣는 편이 좋습니다. API 복구, 데이터 보정, 사용자 영향 확인, 재발 방지 항목이 분리되어야 합니다.

트레이드오프/주의점

첫째, Correction Job 체계를 만들면 초기 속도는 느려집니다. 후보 쿼리만 짜면 끝날 일을 dry-run, 승인, effect ledger, verification으로 나누기 때문입니다. 하지만 장애 후 같은 실수를 반복하지 않으려면 이 비용이 필요합니다. 특히 월 1회 이상 보정이 반복되는 도메인은 수동 SQL보다 job화하는 편이 결국 더 빠릅니다.

둘째, 자동 보정률을 KPI로 삼으면 위험합니다. 좋은 지표는 자동 처리 비율이 아니라 오보정률, 승인 대기 시간, residual mismatch, 재실행 성공률입니다. 자동으로 많이 고치는 팀보다, 위험한 건을 정확히 멈추는 팀이 더 안정적입니다.

셋째, 감사 로그에 원문 개인정보를 남기면 보정 체계가 새로운 보안 리스크가 됩니다. before/after 전체 값을 저장하기보다 hash, masked sample, reason code, owner, approval record를 남기고, 원문 조회는 break-glass 절차로 제한합니다.

넷째, 보정 job과 온라인 쓰기가 같은 row를 건드릴 수 있습니다. stale candidate 검증 없이 덮어쓰면 최신 사용자 행동을 되돌릴 수 있습니다. apply 직전 updated_at, version, before hash를 다시 확인하고 달라졌다면 skip 또는 재분류해야 합니다.

의사결정 우선순위는 정답 기준 고정 > 대상 산정 정확도 > 오보정 방지 > 재실행 안전성 > 처리 속도입니다. 빠른 보정은 이 순서를 지킬 때만 가치가 있습니다.

체크리스트 또는 연습

체크리스트

  • 보정 대상 산정 쿼리와 실제 수정 쿼리를 분리했다.
  • dry-run report에 건수, 금액 합계, 샘플, 제외 사유, 중단 조건이 포함된다.
  • job 상태 머신과 승인 상태가 저장된다.
  • effect ledger에 job_id, target_id, before/after hash, reason, approver가 남는다.
  • 재시작 시 이미 적용된 row를 건너뛰는 멱등 키가 있다.
  • lock wait, DB CPU, error rate, residual mismatch 기준으로 pause/abort가 동작한다.
  • 종료 전 reconciliation 재검사와 사용자 영향 closure를 확인한다.

연습

  1. 최근 운영에서 수동 SQL로 고친 사례 하나를 골라 candidate query, dry-run report, approval 기준, apply batch, verification 기준으로 다시 작성해 보세요.
  2. 포인트 중복 적립 5,000건을 보정한다고 가정하고 자동 보정 가능 조건과 2인 승인 조건을 숫자로 나눠 보세요.
  3. 권한이 잘못 부여된 사용자 30명을 수정하는 job을 설계하고, before/after 원문을 저장하지 않으면서도 감사 가능한 effect ledger 필드를 정해 보세요.

관련 글