자동화 slecs

결제 거래내역 대조 배치 주기를 2시간 간격으로 단축

목차

결제 도메인에서 배치 주기 하나를 바꾸는 건 생각보다 손이 많이 간다. charge-batch 영역에 거래내역 대조 보정 배치를 추가하고, 실행 주기를 매일 06:30 단 1회에서 2시간마다 :30분으로 단축했음. 바뀐 파일은 내부 클래스 2개, 뷰/스타일 2개, 설정/문서 1개. 숫자만 보면 소규모 같지만, 결제 정합성 검증 로직이 얽혀 있어서 실제 작업 범위는 좀 달랐다.

왜 2시간으로 당겼나

기존 1일 1회 배치는 "어제 데이터는 오늘 아침에 맞춰진다"는 구조였다. 하루치 거래가 몇 시간씩 불일치 상태로 방치되는 건, 운영 입장에서 봤을 때 이미 뭔가 놓치고 있다는 신호다. 정합성 문제는 발견 시점이 늦을수록 원인 추적이 어려워진다 - 로그는 남아 있어도 "언제부터 틀렸는지"를 역산하는 게 고되다.

배치 주기를 줄이면 불일치 감지 창이 좁아진다. 극단적으로는 이벤트 드리븐으로 실시간 보정을 할 수도 있는데, 결제 게이트웨이 응답이 지연되거나 재시도가 발생하는 경우를 고려하면 짧은 주기의 폴링 배치가 더 안정적인 선택이었음. 이벤트로 처리하면 메시지 유실이나 순서 역전 시 별도 보정 로직이 필요해지는데, 그게 오히려 복잡도를 올린다. 메시지 큐를 쓴다면 dead letter 처리, 재처리 정책, 중복 소비 방지까지 따라붙고 - 현 시점에서 그 복잡도를 가져가는 게 맞지 않았다.

2시간 주기는 그 절충점이었음. 실시간에 가깝게 감지하면서도 배치 오버헤드를 과도하게 늘리지 않는 선. 하루 1회에서 12회로 늘어나는 거라 쿼리 성능도 같이 봤는데, 인덱스 범위 조건이 제대로 걸려 있어서 실행 계획상 문제없었다.

구현하면서 건드린 것들

배치 클래스 2개에 실행 로직을 추가하고, cron 표현식으로 주기를 걸었다.

# 기존
0 30 6 * * ?

# 변경 후
0 30 0/2 * * ?

자정 00:30부터 시작해 02:30, 04:30... 22:30까지 하루 12회 실행. 표현식 자체는 간단하지만, 실행 빈도가 12배 늘어난다는 게 다른 문제를 끌고 온다.

멱등성 보장이 이번 작업에서 가장 신경 쓴 부분이었음. 주기가 짧아지면 중복 실행 가능성도 그만큼 올라간다 - 배포 타이밍이 겹치거나, 수동 실행과 스케줄 실행이 동시에 뜨거나, 다중 인스턴스 환경에서 같은 데이터를 두 번 건드리면 보정 로직이 오히려 정합성을 깨뜨린다. 처리 상태 플래그를 두고 "이미 처리된 건 건너뛰기" 패턴으로 막았다. 대략 이런 흐름이다.

// 의사코드 수준 - 실제 구현 패턴
if (isAlreadyProcessed(txId)) {
    log.info("skip: txId={} already reconciled", txId);
    return;
}
process(txId);
markAsProcessed(txId);

단순한 패턴이지만 이걸 빠뜨리면 배치가 같은 건을 두 번 보정하거나, 이미 완료된 건의 상태를 되돌려버리는 사고가 난다. 결제 건에서 상태가 의도치 않게 바뀌면 그게 실제 오류인지 배치 버그인지 구분하는 것부터 일이 시작된다.

배치 완료 후 결과 로그도 구조화해서 남겼다. 언제 실행됐는지, 조회 대상 건이 몇 개였는지, 실제 보정한 건, 건너뛴 건, 오류 건. 운영 중에 "배치가 제대로 돌고 있나?"를 확인할 때 코드 안 열고 로그만 봐도 알 수 있어야 한다고 생각함. 오류 건이 연속으로 쌓이면 알람으로 이어지도록 하는 게 이상적이지만, 우선 로그 구조를 잡아두는 것만으로도 모니터링 진입 장벽이 낮아진다.

수동 실행 API도 붙였다. 긴급 상황이나 개발 단계 테스트에서 "다음 :30분까지 기다려야 하나?"가 아니라 즉시 트리거할 수 있는 경로. 운영 편의 기능처럼 보이지만, 이게 없으면 배치 관련 이슈 대응 시간이 의도치 않게 늘어난다.

설계하면서 따져본 것들

항목 결정 내용
실행 주기 방식 폴링 배치 선택 - 이벤트 드리븐 대비 구현 복잡도 낮고 예측 가능
멱등성 처리 상태 플래그 기반 중복 실행 방어
예외 처리 NULL, 빈 결과, 권한 오류 각각 분기 처리, 전체 실패 전파 방지
성능 쿼리 실행 계획 사전 확인, 인덱스 범위 조건 적용 확인
가시성 배치 실행 결과 구조화 로그, 수동 실행 API 추가

쿼리 레벨부터 설계한 이유가 여기 있다. 화면/API 먼저 만들고 나중에 쿼리를 끼워 맞추면, 집계 조건이 화면의 필터 조건과 미묘하게 달라지는 문제가 생긴다. "왜 화면 숫자랑 리포트 숫자가 다르지?"라는 질문이 나오는 전형적인 패턴. 이번엔 뷰/스타일도 같이 수정됐는데, 수치를 화면에 노출할 때 포맷이 달라지거나 상태값이 다른 컨텍스트에서 다르게 보이는 걸 미리 맞춰둬야 "이 화면이랑 저 화면이랑 왜 다르지?" 이슈가 나중에 안 생긴다.

구현 후엔 직접 화면에서 동작을 확인했음. 배치 수동 실행 → 화면 갱신 → 관련 수치 cross-check. 기존 데이터가 깨지지 않았는지, 경계값 케이스에서 이상 동작이 없는지도 같이 봤다.


금융/결제 도메인에서 "대충 맞는 것 같다"는 없다. 숫자가 1원이라도 다르면 신뢰부터 흔들린다. 이번 배치 주기 단축도 단순히 cron 표현식 바꾸는 게 아니라 멱등성, 예외 처리, 로그 구조까지 같이 정비해야 했던 이유가 거기 있다.

배치 주기를 줄이면 운영 가시성이 올라가는 대신, 예상치 못한 실행 빈도 증가로 숨어 있던 버그가 표면에 드러나기도 한다. 하루 1번 실행할 때는 안 보이던 게 12번 실행하면 보인다. 그 의미에서 주기 단축은 시스템에 대한 스트레스 테스트이기도 하다. 문제가 빨리 나오는 게 나쁜 게 아님 - 운영 중에 터지는 것보다 개발/검증 단계에서 터지는 게 훨씬 낫다.

사내 서비스를 만들다 보면 기능 하나가 버튼 하나로 끝나지 않는다는 걸 계속 체감함. SQL 집계, 상태 처리, 예외 방어, 화면 렌더링, 권한 체크가 모두 엮여 있어서 어느 하나만 빠뜨려도 특정 사용자에게 이상한 화면이 나타나거나 숫자가 맞지 않는다. 이번에도 배치 클래스뿐 아니라 뷰와 설정 파일까지 손이 간 게 그 때문이다.

커밋은 논리적으로 독립된 단위로 쪼개는 습관을 유지하고 있다. 배치 로직, 멱등성, 로그, API - 각각 따로 커밋하면 나중에 문제가 생겼을 때 어느 변경에서 깨졌는지 찾는 시간이 확 줄어든다. 커밋 메시지도 "무엇을 바꿨다"보다 "왜 바꿨다"를 담으려 노력하는 편이다. 코드는 diff로 보이지만 의도는 커밋 메시지로만 남는다.

댓글 0

첫 댓글 달아줘.