결제 거래 실시간 동기화 API 설계와 멱등성 확보
목차
배경
결제 플랫폼 모니터링 도구에서 거래 이력을 외부 시스템과 맞춰야 하는 요구가 있었음. 기존엔 새벽 배치로 한 번씩 긁어왔는데, 파트너 쪽에서 실시간에 가까운 동기화를 요청. 배치 주기를 줄이는 건 한계가 있어서 동기화용 API를 따로 뽑기로 함.
설계 포인트
처음엔 "최근 N분치 가져오기" 식으로 만들려다가, 멱등성을 못 잡으면 중복 적재가 터질 게 뻔해서 방향을 틀었음.
- 외부 거래 ID + 발생 시각 조합으로 upsert
- 페이지네이션은 커서 기반 (offset은 누락/중복 위험)
- 동기화 윈도우는 호출자가 지정하되 최대 24시간으로 캡
SELECT id, ext_tx_id, occurred_at, status
FROM transaction_history
WHERE occurred_at >= :from
AND occurred_at < :to
AND id > :cursor
ORDER BY id ASC
LIMIT 500;
cursor 컬럼은 단조 증가 보장되는 PK로 잡음. occurred_at만으로 정렬하면 같은 초에 들어온 행 순서가 깨져서 다음 호출이 어디서 끊겼는지 모호해짐.
멱등성 처리
| 케이스 | 기존 동작 | 변경 후 |
|---|---|---|
| 같은 거래 두 번 인입 | 중복 row | upsert로 1건 유지 |
| 상태만 바뀐 거래 | 무시 | 상태·금액 갱신 |
| 취소 거래 동기화 | 누락 | cancelled로 반영 |
삽질 메모
- 처음엔 트랜잭션 잡고 한 페이지씩 처리했는데, 락 점유 시간이 길어져서 다른 잡들이 밀림. 페이지 단위로 트랜잭션 끊는 걸로 바꾸니 정리됨.
- 응답에
next_cursor+has_more둘 다 내려줘야 호출자 코드가 깔끔해짐.has_more뺐다가 호출자가 매번 한 번 더 찔러보는 바람에 트래픽 두 배 됐었음. - 시간 비교를 호출자 로컬 타임존으로 받았다가 KST/UTC 섞여서 데이터가 비는 사고가 한 번 있었음. 인입 즉시 UTC로 정규화하고 응답도 UTC로 통일.
성능
- 500건/페이지 기준 평균 응답 180ms 근처
- (occurred_at, id) 복합 인덱스 새로 깖
- 풀스캔 없이 커서로 진입하니 동기화 윈도우가 길어져도 응답 시간은 거의 평탄
회고
배치를 줄여서 API로 빼는 작업은 "그냥 SELECT 한 번 더 만드는 것" 같지만, 실제로는 멱등성·커서·타임존·트랜잭션 경계 네 가지가 동시에 걸려 있었음. 한 번 배포하면 호출자 쪽 구현이 박혀서 시그니처를 못 바꾸기 때문에, 응답 페이로드에 살짝 과한 메타데이터(has_more, synced_at, window_to)를 미리 넣어둔 게 결과적으로 잘한 선택이었음.
다음
댓글 0
첫 댓글 달아줘.