- Published on
새벽 갱신 배치가 느리거나 자정을 넘길 때, 구독 결제는 어떻게 되나
- Authors

- Name
- 김민석
- 새벽에 돈이 나가는 배치는 사고가 나도 아침에야 안다
- 순차 처리라 5천 건에 29분, 1만 건이면 락이 먼저 풀린다
- PG가 답을 안 하면 구독마다 30초씩 걸려 60건에 30분
- 자정을 넘긴 배치와 다음 날 배치가 겹치면 152건이 두 번 결제됐다
- 앞 글에서 남긴 승인 경로 용량은 동시 300건까지 전부 성공한다
- 구독 5천 건, 무응답 60건, 겹침 200건으로 잡은 이유
- 남은 것
- 정리
새벽에 돈이 나가는 배치는 사고가 나도 아침에야 안다
정기 결제 사고 사례 중에는 같은 회원에게 한 달 치가 두 번 빠져나간 이중 납부가 있다. 사람이 결제 버튼을 누르는 게 아니라 새벽에 배치가 빌링키로 결제하기 때문에, 잘못 나가도 고객이 아침에 문자를 보고서야 안다.
구독 갱신 배치는 매일 01:00에 결제 예정일이 지난 구독을 찾아 빌링키로 결제하고 구독을 4주 연장한다. 코드는 구독을 하나씩 순서대로 처리하는 루프다. 같은 배치가 두 서버에서 동시에 돌지 않도록 ShedLock 락을 잡고, 락은 50분이 지나면 자동으로 풀린다.
세 가지를 숫자로 확인했다. 구독이 5천 건이면 배치가 얼마나 걸리는지, PG가 답을 안 하면 배치가 어떻게 되는지, 배치가 자정을 넘겨 다음 날 배치와 겹치면 같은 구독이 두 번 결제되는지다. 앞 글에서 남겨 둔 승인 경로 용량도 같은 기간에 쟀다.
PG 응답을 늦추거나 끊을 수 있는 테스트용 PG를 붙이고, 결제 예정일이 지난 구독을 원하는 수만큼 만들어 배치를 돌렸다. 고치기 전에 먼저 재고 고친 다음 같은 조건으로 다시 잰다.
순차 처리라 5천 건에 29분, 1만 건이면 락이 먼저 풀린다
구독 5천 건을 갱신하는 데 28분 59초가 걸렸다. 건수에 정비례라 1만 건이면 58분으로, 락이 풀리는 50분을 넘긴다.
DB 커넥션 풀 10개인 서버에 결제 예정일이 지난 구독 1천 건과 5천 건을 만들고, PG 응답을 300ms로 두고 배치를 돌렸다.
구독 5천 건에 28분 59초, 서버는 놀고 있었다
| 항목 | 1천 건 | 5천 건 |
|---|---|---|
| 소요 | 351,826ms (5분 52초) | 1,738,943ms (28분 59초) |
| 구독 한 건당 | 351ms | 347ms |
| 갱신 성공 | 1000 / 1000 | 5000 / 5000 |
| 두 번 결제된 구독 | 0 | 0 |
| 사고 기록 | 0 | 0 |
| 커넥션 사용 최대 | 1 | 1 |
| CPU | 5% | 5% |
한 건에 350ms가 든다. PG 응답 300ms에 서버와 DB 몫 50ms다. 순서대로 하나씩 처리하니 1천 건이든 5천 건이든 한 건당 시간이 같고, 총 소요는 건수에 정비례한다. 초당 2.9건, 분당 173건이다. 1만 건이면 58분, 약 8,650건에서 50분에 닿는다.
돈은 정확했다. 원장 항등식은 PG 승인 건수 = 갱신 결제 + 취소 + 사고 기록이고, 5000 = 5000 + 0 + 0으로 맞았다. 문제는 시간뿐이다. 배치가 도는 동안 커넥션 풀 사용은 0과 1 사이, CPU는 5%를 넘지 않았다. 배치 스레드 하나가 PG 응답을 기다리는 동안 서버는 아무것도 안 한다.

- 결과별 분당 건수가
173에서 평평하다. 회차 소요 패널에5분 52초계단 하나, 건당 처리 시간은0.35초다.

5천 건도 같은 높이173으로29분동안 이어진다. 회차 소요 계단이29분이다.
락 50분은 배치가 그 안에 끝난다는 전제로 잡은 값이다
락 만료 lockAtMostFor 50분은 배치가 그 안에 끝난다는 전제로 잡은 값인데, 순차 처리로 1만 건이면 58분이라 전제가 깨진다. 살아 있는 배치가 락 밖에서 결제를 이어 가는 구간이 생기고, 그 사이 배포로 같은 서버에 프로세스가 하나 더 뜨면 같은 배치를 다시 잡을 수 있다.
그리고 배치를 도는 스케줄러 스레드가 1개다. 갱신 배치가 02:00을 넘기면 그 시각에 돌아야 할 만료 처리 배치 같은 다른 새벽 배치는 그 스레드가 비기를 기다린다. 순차 1만 건은 01:58에 끝나 아슬아슬하고, PG가 조금만 느려도 넘긴다. 스레드가 하나라는 건 코드 사실이고, 실제로 얼마나 기다리는지는 다른 배치를 같이 돌려 재야 하는 숫자다.
락 연장과 작업자 10개는 고르지 않았다
- 락 시간을 실측의
3배로 늘리는 방법은 순차 처리가 그대로라 건수가 늘면 다시 넘겨서 막힌다. 락 만료는 배치가 죽은 채 락을 쥐고 있을 때만 뜻이 있고 배치는 하루 한 번이라, 시간을 늘려서 얻는 것도 없다. - 구독을 나눠 결제할 작업자 스레드를 커넥션 풀만큼
10개두는 방법은 알림 처리 스레드4개가 쓸 커넥션이 남지 않아서 막힌다. 앞 글에서 본 커넥션 대기가 배치에서 다시 난다. - 작업자
5개를 골랐다. 커넥션 풀10개의 절반이고, 알림4개와 여유1개를 남긴다.
작업자 5개가 나눠 결제하자 5천 건이 6분 3초에 끝났다
갱신 전용 스레드 풀을 두고 작업자 5개가 구독을 나눠 결제하게 했다. 배치 스레드는 구독을 작업자에게 넘기고 마지막 작업이 끝날 때까지 락을 쥔 채 기다리니, 락은 배치가 끝날 때까지 유지된다. 스케줄러 스레드도 1개에서 4개로 늘려 갱신이 도는 동안 다른 새벽 배치가 줄을 서지 않게 했다.
작업은 큐에 쌓아 두지 않고 진행 중인 것이 5개를 넘지 않게 하나씩 넘긴다. 한 구독에서 예외가 나도 나머지는 그대로 돌고, 서버가 종료 신호를 받으면 새 작업은 넘기지 않고 진행 중인 것만 마친다.
| 항목 | 순차 처리 | 작업자 5개 |
|---|---|---|
| 1천 건 소요 | 5분 52초 | 1분 17초 (4.58배) |
| 5천 건 소요 | 28분 59초 | 6분 3초 (4.79배) |
| 구독 한 건당 | 347ms | 72ms |
| 분당 처리 | 173건 | 827건 |
| 작업자 활성 / 대기 큐 | 1 / 없음 | 5 내내 / 0 |
| 커넥션 사용 최대 / 대기 | 1 / 0 | 5 / 0 |
| 커넥션 대기 타임아웃 | 0 | 0 |
| CPU | 5% | 7 ~ 20%, 시작 직후 31% |
| 두 번 결제된 구독 / 사고 | 0 / 0 | 0 / 0 |
| 원장 항등식 | 5000 = 5000+0+0 | 5000 = 5000+0+0 |
5천 건이 6분 3초에 끝났다. 한 건당 72ms로, 작업자 5개로 나눈 이론값 350ms ÷ 5 = 70ms보다 4% 길 뿐이다. 처리율은 초당 13.8건으로 이론 상한 5 ÷ 0.35초 = 14.3건의 96%다. 작업자 다섯이 6분 내내 쉬지 않고 돌았고 대기 큐는 0이었다. 커넥션 대기도 0이라 병목은 DB가 아니라 PG 응답 300ms 자체다.
1만 건이면 환산 12분 6초로 락 50분 안에 드니, 락 만료는 50분 그대로 두었다. 락 만료가 뜻을 갖는 건 배치가 죽었을 때뿐이고, 하루 한 번 도는 배치라 24시간 안에만 끝나면 다음 날 배치와 겹치지 않는다.
빨라진 것이 PG 응답을 건너뛴 탓이 아닌지도 확인했다. PG가 Idempotency-Key로 첫 응답을 재사용해 돌려준 건은 0이고, 가장 빠른 응답도 300ms였다. 5천 건 전부 PG를 실제로 거쳤다.

- 결과별 분당 건수가
830에서5분동안 평평하다. 고치기 전173의 다섯 배 높이로 다섯 배 짧게 끝난다. - 작업자 풀 패널에서 활성 작업자가
5로 평평하고 대기 큐는0이다. 건당 처리 시간은70ms근처다.

- 커넥션 사용이
0과4사이를 오가고 대기(pending)는0, 타임아웃도0이다. 표의 최대5는 그래프 점 사이에 있어 안 보인다. 작업자5개가 커넥션을 오래 쥐지 않는다.

- CPU가
6분동안7 ~ 20%다. 고치기 전5%와 비교하면 서버가 드디어 일을 한다. 스레드는36에서43으로 작업자5개와 알림2개만큼 늘었다.
같은 코드에서 작업자를 1개로 줄이자 고치기 전 속도로 돌아갔다
빨라진 것이 병렬 처리 몫인지 확인하려고, 고친 코드 그대로 작업자만 1개로 두고 1천 건을 다시 돌렸다.
| 코드 | 작업자 | 1천 건 소요 | 구독 한 건당 |
|---|---|---|---|
| 고치기 전 코드 | 순차 | 5분 52초 | 351ms |
| 고친 코드, 작업자 1개 | 1 | 6분 1초 | 361ms |
| 고친 코드, 작업자 5개 | 5 | 1분 17초 | 76ms |
작업자를 1개로 두니 6분 1초, 고치기 전 5분 52초와 같은 속도로 돌아갔다. 건당 10ms 늘어난 건 고친 코드에 같이 들어간 결제 직전 재확인과 빌링 서킷브레이커 통과 비용이다. 같은 코드에서 작업자 1 → 5만으로 4.70배다.

- 작업자
1개에서 분당166건으로5분평평하다. 활성 작업자1, 대기 큐0.

- 같은
1천 건을 작업자5개로 돌린 회차다. 분당760건까지 솟았다가1분 17초만에 끝난다.
PG가 답을 안 하면 구독마다 30초씩 걸려 60건에 30분
PG가 아예 답을 안 하면 구독 하나에 30초씩 걸리고, 건마다 사고 기록과 운영자 알람이 쌓였다. 60건에 30분 5초다.
결제 예정일이 지난 구독 60건을 만들고, PG가 응답을 아예 안 하게 한 뒤 배치를 돌렸다.
서킷브레이커 밖에 있던 빌링 호출은 60건 전부 30초를 기다렸다
| 항목 | 결과 |
|---|---|
| 소요 | 30분 5초, 구독 한 건당 30.09초 |
| 결과 | failed 60 / success 0 |
| 사고 기록 / 운영자 알람 | 60 / 60 |
| 고객 실패 알림 | 60 |
| 구독 상태 | 60건 전부 ACTIVE 유지, 다음 날 재시도 |
| PG에 도달한 호출 | 87 (빌링 44 + 결과 조회 43) |
| 돈 | 0 |
구독 하나의 30초는 이렇다. 빌링 호출이 15초를 기다리다 포기하고, 승인이 됐는지 모르니 주문번호로 결과를 조회하는데 그것도 15초를 기다린다. 조회까지 실패하면 승인 결과 불명으로 [CRITICAL] 사고 기록과 운영자 알람, 고객에게 갱신 실패 알림을 보내고 다음 구독으로 넘어간다. 앞 글에서 승인 호출에 서킷브레이커를 달았지만 빌링 호출은 그 밖에 있어서, PG가 죽은 걸 알고도 구독마다 15초 + 15초를 기다렸다.
5천 건이면 환산 41시간 45분이다. 01:00에 시작한 배치가 다음 날 01:00을 넘겨 다음 날 배치와 겹친다. 이때 두 배치가 같은 구독을 다시 결제한다.

- 결과별 분당 건수에
failed만 분당1 ~ 2건으로30분동안 톱니처럼 이어진다.30초에 한 건이라1분창에1건또는2건이다. - 건당 처리 시간이
27 ~ 55초톱니, 평평한 구간은28초다. 회차 소요 패널은20:44끝에30분점 하나가 찍힌다.
빌링 호출에도 서킷브레이커를 달자 5분 3초에 끝났다
타임아웃 단축과 재시도를 고르지 않은 이유는 앞 글과 같다. 빌링 호출에 승인 차단기와 실패율 규칙이 같은 서킷브레이커를 별개 상태로 달았다. 최근 20건 중 절반 넘게 실패하면 열리고, 판단은 최소 10건이 모인 뒤부터다. 느린 호출로 세는 기준만 10초로 다르다. 열려 있는 동안 배치는 남은 구독을 PG 호출 없이 pg_unavailable로 건너뛴다. 결과 조회도 사고 기록도 고객 알림도 없다. 구독은 ACTIVE에 결제 예정일도 그대로라 다음 날 배치의 대상에 다시 잡힌다. 하루 늦은 갱신은 설계상 유예 3일 안이라 같은 회차에서 다시 시도하지 않는다. 회차가 끝나면 건너뛴 건수를 [CRITICAL] 요약으로 남기고 운영자 알람을 1통 보낸다. 구독 첫 결제와 플랜 변경도 서킷브레이커가 열려 있으면 PG를 부르지 않고 HTTP 503으로 답한다.
| 항목 | 서킷브레이커 없음 | 빌링 서킷브레이커 |
|---|---|---|
| 소요 | 30분 5초 | 5분 3초 |
| 결과 | failed 60 | failed 10 + pg_unavailable 50 |
| 사고 기록 / 운영자 알람 | 60 / 60 | 10 / 11 |
| 고객 실패 알림 | 60 | 10 |
| 구독 상태 | 60 ACTIVE | 60 ACTIVE, 다음 날 재시도 |
| PG에 도달한 호출 | 87 | 20 (빌링 10 + 조회 10) |
| 돈 | 0 | 0 |
처음 10건은 그대로 30초씩 겪는다. 열린 판단에 최소 10건이 필요해서다. 10번째 빌링 호출이 실패한 직후 차단기가 닫힘에서 열림으로 바뀌고, 나머지 50건은 1초 안에 전부 건너뛴다. 운영자 알람 11통은 사고 10건과 회차 요약 1통이다. 30초 뒤 반열림으로 넘어갔지만 배치는 이미 끝나 있었다.
처음 10건 × 30초는 건수와 무관한 상한이고 그 뒤는 PG를 부르지 않고 건너뛰니, 5천 건이어도 환산 41시간 45분이 약 5분으로 줄어 다음 날 배치와 겹치지 않는다. 열린 뒤 30초마다 흘려보내는 시험 호출 3건이 그때마다 30초를 다시 겪는 몫은 남는다.

failed가30초에1건씩5분동안 이어지다12:01에 끊긴다. 고치기 전 캡처와 같은 모양이30분에서5분으로 짧아졌다.- 건너뛴
50건은 그래프에0으로 그려진다.pg_unavailable이 그 순간 처음 생긴 종류라 Prometheus가 첫 증가분을 세지 못한 것이다. 앞 글의 경보에서 본 첫 샘플 문제와 같고,50은 로그와 차단기 거절 카운터로 확인했다.
자정을 넘긴 배치와 다음 날 배치가 겹치면 152건이 두 번 결제됐다
오늘 배치가 자정을 넘긴 채 도는 동안 내일 배치가 시작되면, 두 배치가 같은 구독 152건을 각각 결제했다. 락도 주문번호 멱등 키도 있었지만 둘 다 막지 못했다.
결제 예정일이 지난 구독 200건을 만들고 PG 응답을 3초로 늦춘 뒤, 오늘 날짜로 배치를 돌리고 150초 뒤 내일 날짜로 두 번째 배치를 시작했다. 락 만료를 2분으로 줄여 두 번째 배치가 락을 잡게 했다. 무응답 41시간처럼 배치가 하루를 넘긴 상황을 약 10분에 재현한 것이다.
두 배치가 같은 구독 152건을 각각 결제했다
| 항목 | 결과 |
|---|---|
| 오늘 배치 | 200건, 10분 12초, 한 건당 3.06초 |
| 내일 배치 | 152건 (오늘 배치가 48건 처리한 시점의 잔여), 7분 45초 |
| PG 승인 건수 | 352 (구독은 200건) |
| 두 번 결제된 구독 | 152 |
| PG가 같은 주문번호로 거절한 건 | 0 |
| 구독 종료일이 8주 밀린 구독 | 152 |
| 고객 영수증 알림 | 352 (영수증 두 통) |
| 사고 기록 | 0 |
구독 9413을 보면 RENEW_9413_2026-09-07이 19:19:56에 80,000원, RENEW_9413_2026-09-08이 19:19:58에 다시 80,000원이다. 2.2초 간격으로 같은 카드에서 두 번 나갔다. 원인은 둘이고, 셋째는 왜 아무 검사에도 안 걸렸나다.
- 갱신 주문번호가
RENEW_{구독}_{배치 날짜}라 내일 배치는 같은 구독에 다른 주문번호를 만든다. PG는 주문번호가 다르면 별개 주문으로 승인하고, DB의 주문번호 유니크 제약도 걸리지 않는다. - 내일 배치는 시작할 때 결제 예정일이 아직 어제인 구독
152건을 목록으로 잡는다. 오늘 배치가 그 뒤에 갱신해도 결제 직전에 다시 확인하지 않는다. - 원장 항등식이
352 = 352 + 0 + 0으로 맞는다. PG도 두 번, DB도 두 번이라 매일 도는 양방향 대조도 불일치를 찾지 못한다. 사고 기록도0이라 어느 검사에도 걸리지 않았다.
락이 새서 난 사고가 아니다. 락은 50분 뒤 풀리도록 설계된 것이고, 자정을 넘긴 배치는 원래 락 밖에서 돈다. 주문번호가 멱등 키 노릇을 했지만 배치 날짜가 들어 있어서 다음 날 배치에는 다른 키였다.

- 오늘 배치가 다음 날 새벽
1시를 넘겨도 계속 도는 동안 내일 배치가 시작되고, 오늘 배치가 아직 못 한152건을 자기 목록으로 잡는다. - 같은 회원의 구독에 오늘 배치는 주문번호에 오늘 날짜를, 내일 배치는 내일 날짜를 붙여 각각 결제를 요청한다. PG는 주문번호가 다르면 다른 주문으로 보고 둘 다 승인하고, DB에도 결제가 두 건 남는다. 사고 기록은 없다.

19:20에 분당 갱신 건수가20에서40으로 정확히 두 배가 된다. 두 배치가 같은 구독들을 나란히 결제하는 구간이다.- 건당 처리 시간이
3초에서1.5초로 반감한다. 배치가 둘이라 같은 시간에 두 건씩 나가는 것이지 빨라진 게 아니다.
날짜를 빼는 것도 락을 늘리는 것도 답이 아니었다
- 주문번호에서 날짜를 빼고 구독 번호만 쓰는 방법은
4주뒤 다음 주기 결제가 같은 주문번호가 돼 PG가 거절해서 막힌다. 주문번호에는 어느 주기의 결제인지가 들어 있어야 한다. - 락을 하루 넘게 잡는 방법은 오늘 배치가 하루를 넘긴 상황이면 내일 배치를 통째로 건너뛰게 해서 막힌다. 내일 결제 예정인 구독까지 하루 밀린다. 배치가 죽었을 때 락이 그만큼 오래 남는 문제도 그대로다.
- 결제 직전에 구독을 다시 확인하는 것만으로는 두 배치가 같은 구독을
2초간격으로 밟는 구간을 못 막는다. 확인 시점엔 둘 다 아직 갱신 전이다. 그래서 주문번호를 결제 예정일로 고정해 PG가 두 번째를 거절하게 하고, 거절당한 쪽은 앞 결제에 합류하는 것까지 세 겹으로 골랐다.
주문번호를 결제 예정일로 고정하고 결제 직전에 다시 확인하자 이중 결제가 0이 됐다
고친 것은 세 겹이다.
- 갱신 주문번호의 날짜를 배치 날짜에서 결제 예정일로 바꿨다. 오늘 배치든 내일 배치든 같은 주기의 결제는 구독 하나에 주문번호 하나고, 고친 뒤 회차의 구독
9683이면RENEW_9683_2026-09-07뿐이다. - 결제 직전에 구독을 다시 읽어 결제 예정일이 이미 뒤로 밀렸으면 PG를 부르지 않고
already_done으로 끝낸다. - 재확인을 둘 다 통과한 구독은 PG가 같은 주문번호의 두 번째 시도를
DUPLICATED_ORDER_ID로 거절한다. 거절당한 배치는 그 주문번호를 조회해 앞 배치의 결제에 합류하고, 저장 단계에서 앞 배치가 먼저 저장한 것을 보면 그대로 둔다.
겹침과 별개로 PG에 Idempotency-Key도 같이 보내, 같은 회차 안의 재요청은 PG가 첫 응답을 재사용한다.
| 항목 | 배치 날짜 주문번호 | 결제 예정일 주문번호 + 재확인 + 거절 합류 |
|---|---|---|
| PG 승인 건수 / 구독 | 352 / 200 | 200 / 200 |
| 두 번 결제된 구독 | 152 | 0 |
| PG가 같은 주문번호로 거절한 건 | 0 | 77 |
| 거절 뒤 조회해 합류한 건 | 0 | 77 |
| 결제 직전 재확인으로 건너뛴 건 | 0 | 74 |
| 갱신 결과 | success 352 | success 200, already_done 152 |
| 구독 종료일이 8주 밀린 구독 | 152 | 0 |
| 고객 영수증 알림 | 352 | 200 |
| 사고 기록 | 0 | 0 |
| 원장 항등식 | 352 = 352+0+0 | 200 = 200+0+0 |
겹친 152건은 74건이 재확인에서 걸렸고, 77건은 두 배치가 나란히 밟아 재확인을 통과했지만 PG가 두 번째를 거절해 앞 결제에 합류했다. 1건은 DB에 이미 결제가 있어 건너뛰었다. 보상 취소는 0이다. 두 번째 배치 소요는 7분 45초에서 7분 47초로 거의 같다. 시간은 그대로고 돈은 정확해졌다. 시간을 줄인 건 작업자 5개 병렬 처리다.

success가 분당20건으로 평평하고,00:10부터already_done이 같은 높이로 합류한다. 고치기 전 캡처의20 → 40두 배가 이번엔 전부already_done으로 갈라졌다.
앞 글에서 남긴 승인 경로 용량은 동시 300건까지 전부 성공한다
앞 글 "남은 것"에 둔 승인 경로 용량이다. 같은 서버에서 같은 기간에 쟀다. PG 응답 300ms에 동시 50 → 100 → 200 → 300으로 올렸다. 총 650건이다.
| 동시 | 성공 | p50 | p95 | 커넥션 대기 최대 | 커넥션 사용 최대 | JVM 스레드 | CPU | TPS |
|---|---|---|---|---|---|---|---|---|
| 50 | 50 / 50 | – | – | 0 | 2 | 77 | 29% | 50 |
| 100 | 100 / 100 | 0.81초 | 1.13초 | 0 | 2 | 127 | 21% | 62 |
| 200 | 200 / 200 | 1.45초 | 2.06초 | 67 | 10 | 227 | 28% | 83 |
| 300 | 300 / 300 | 1.69초 | 3.25초 | 150 | 10 | 226 | 40% | 79 |
300건까지 실패 0, 커넥션 타임아웃 0, 서킷브레이커 열림 0이다. 꺾이는 지점은 100과 200 사이다. 200부터 커넥션 풀 10개가 전부 사용 중이고 대기가 67 → 150으로 늘며, 처리량은 83 → 79 TPS로 더 오르지 않는다. 풀 10개가 상한이다. 가장 느린 승인이 3.94초라 커넥션 타임아웃 30초까지는 약 8배 여유가 있다. CPU는 40%라 병목이 아니다. 50 단계의 p50과 p95는 첫 샘플이라 계산되지 않았다. 원장 항등식 650 = 650 + 0 + 0.

- 커넥션 대기(
pending)가200단계에서67로 솟고 사용 중은10에 붙는다.300단계의150은 그래프 간격에 빠져 안 보인다. 타임아웃은0이다. - 승인
p95가1.1 → 2.1 → 3.2초세 계단이다.50단계는 첫 샘플이라 그려지지 않았다.

- JVM 스레드가
34 → 77 → 127 → 227계단으로 톰캣 스레드200이 거의 찬다.HTTP 200만 있고 CPU 최대40%다.
구독 5천 건, 무응답 60건, 겹침 200건으로 잡은 이유
5천 건은 실서비스 구독 수를 크게 웃돌고, 순차50분한계인8,650건아래에서 정비례를 확인할 수 있는 크기다.1천 건과5천 건이 정비례로 나와1만 건은 환산으로 갈음했다.- 무응답은 구독 한 건에
30초라60건이면30분이다. 한 회차 안에서 차단기 판단10건을 넘기고도 건너뛴 구간이 충분히 보이는 수다. - 겹침은 PG 응답을
3초로 늦춰200건이 약10분을 돌게 하고, 그 안에서 락 만료2분을 넘긴150초뒤에 두 번째 배치가 들어오게 했다. - 승인 용량
300건은 앞 글과 같은 테스트 계정 한도다.
남은 것
- 스케줄러 스레드를
4개로 늘린 뒤 갱신이 도는 동안 다른 새벽 배치가 줄을 서지 않는지, 두 배치를 같이 돌려 재는 측정 - 빌링 호출의 응답 시간 지표. 지금은 분당 갱신 건수로 역산한다
- 실패 종류별 카운터를 기동 시
0으로 등록하는 사전 등록.pg_unavailable50건이 그래프에서 또0으로 그려졌다 - 승인 결과 지표를
CloudWatch로 보내는 운영 경보
정리
| 문제 | 고친 것 | 고치기 전 → 고친 뒤 |
|---|---|---|
| 순차 처리라 5천 건에 29분, 1만 건이면 락 50분 초과 | 작업자 5개 병렬 처리, 스케줄러 스레드 4개 | 5천 건 28분 59초 → 6분 3초, 1만 건 환산 58분 → 12분 6초 |
| PG 무응답이면 구독마다 30초, 건마다 사고와 알람 | 빌링 호출에 서킷브레이커 | 60건 30분 5초 → 5분 3초, 사고 기록 60 → 10, 운영자 알람 60 → 11 |
| 자정을 넘긴 배치와 다음 날 배치가 겹치면 이중 결제 | 결제 예정일 주문번호, 결제 직전 재확인, 거절 합류 | 두 번 결제된 구독 152 → 0 |
앞 글에서 남긴 승인 경로 용량은 동시 300건까지 650/650 성공, p95 3.25초, 약 80 TPS고 커넥션 풀 10개가 상한이다.
락도 주문번호 멱등 키도 코드에 있었다. 락은 50분 뒤 풀리고 주문번호에는 배치 날짜가 들어 있다는 사실은, 두 배치를 실제로 겹쳐 돌려 보기 전엔 문제로 보이지 않았다.