PG가 느려지면 웹훅까지 줄을 선다: 동시 호출 상한의 비용은 식으로 나온다
PG 응답이 5초로 느려지자 PG를 부르지도 않는 웹훅 수신 API의 p95가 13초가 됐고 26.1%가 토스의 10초 규약을 넘겼다. PG 동시 호출 상한 40은 이것을 9ms로 되돌리는 대신 결제의 84%를 거절한다. 그 84%는 실험 전에 리틀의 법칙으로 계산되는 값이었다.
검색 결과가 없습니다
제목, 태그, 카테고리로 검색
PG 응답이 5초로 느려지자 PG를 부르지도 않는 웹훅 수신 API의 p95가 13초가 됐고 26.1%가 토스의 10초 규약을 넘겼다. PG 동시 호출 상한 40은 이것을 9ms로 되돌리는 대신 결제의 84%를 거절한다. 그 84%는 실험 전에 리틀의 법칙으로 계산되는 값이었다.
PG 타임아웃으로 쌓인 미확정 결제 887건이 전부 확정되기까지의 시간은 복구 배치의 속도가 아니라 코드 상수 1분이 정했다. 청크를 줄이자 그때부터는 청크 ÷ 주기가 정했다. 두 식으로 기본 설정에서 1,351건은 약 3.7분, 1만 건은 약 20분이 걸린다고 미리 계산된다. 같은 이유로 적체 알림은 건수가 아니라 가장 오래된 건의 나이에 건다.
결제 API 앞 Redis 필터에 사용자별 5건/초, 전역 100건/초 제한을 두었다. 하지만 단일 계정과 닫힌 루프 부하는 전역 용량을 측정하지 못했다. 개방 루프에서 100~120건/초의 성능 꺾임을 확인하고, 요청률 제한과 과부하 보호를 구분한 과정을 정리한다.
"정산 배치가 결제를 느리게 한다"는 가설은 배치를 7번 연달아 돌려도 결제 p95가 변하지 않아 기각했다. 커넥션 풀 20개 중 최대 15개를 썼고 대기는 0이었다. 대신 그 실험의 1차 실행에서 결제 승인 70%가 500으로 무너졌고 원인은 포인트 적립의 낙관적 락 충돌이었다. 적립을 DB 원자 증가로 바꿔 같은 최악 조건의 승인 성공률을 39.6%에서 100%로 올렸다.
GenPage처럼 홈을 몇 행씩 나눠 만들고 다음 쪽이 앞 쪽과 그 사이의 세션 활동을 입력으로 받게 했다. 쪽 상태는 서버에 두지 않고 커서에 실었다. 사용자 30명 실측에서 쪽 사이 중복은 0(커서가 없으면 2쪽 평균 8.6개)이었고 세션 반영은 96.7~100%였다. 1차에서는 인기 표에 아동복이 0개라 세션 신호가 조용히 버려지고 있었다.
Stripe는 원장 품질을 clearing·timeliness·completeness 세 축으로 재고 Adyen은 경로마다 우선순위를 나눕니다. 그 틀을 BE-commerce 규모의 질문 넷으로 줄이고 지금 지표로 있는 것과 목록에만 있는 것을 코드 기준으로 나눴습니다.
승인 재시도는 이중 결제를 만들 수 있고, 조회 재시도는 retry storm을 만들 수 있다. BE-commerce에서 결과 확정 여부를 기준으로 재시도 경계를 나누고, 재시도 횟수보다 장애 중 호출량과 정상 API 영향을 측정하는 이유를 정리했다.
Modern Treasury는 ACH 입금을 pending 단계에서 잠정 대사해 하루를 앞당기고 posted가 오면 다시 대사합니다. BE-commerce 지급 대사는 금액이 맞아도 posted가 아니면 MATCHED로 올리지 않고, 그 결과가 지급 확정의 유일한 입구입니다. 코드로 강제한 것과 합성 report로만 확인한 것을 나눠 적었습니다.
배민은 카드 결제에 PG 세 곳을 붙이고 Adyen은 오케스트레이션이 복잡성을 없애지 않는다고 씁니다. BE-commerce에는 failover 라우팅 코드와 규칙 테스트가 있지만 기본값은 꺼져 있고 경로는 가짜 PG 둘입니다. 무엇을 코드로 강제했고 무엇을 검증하지 못했는지 나눠 적었습니다.
원장에 강한 일관성을 적용해도 특정 계정과 판매자에 쓰기가 몰리면 처리량이 떨어진다. append-only 원장, 잔액 스냅샷, 직렬화 배치 사이에서 무엇을 측정하고 언제 선택해야 하는지 정리했다.
토스 문서에 따르면 가상계좌는 입금 기한이 지나도 웹훅이 오지 않고 일부 은행은 입금 완료를 보낸 뒤 최대 2분 뒤에 되돌립니다. BE-commerce는 그 두 자리를 상태머신과 만료 배치로 막았지만 입금 웹훅을 그 상태머신에 잇는 연결은 아직 없습니다. 만든 것과 이어지지 않은 것을 코드 기준으로 나눴습니다.
Stripe Radar는 결제마다 100ms 안에 판정합니다. BE-commerce는 FDS 판정을 결제가 끝난 뒤에만 돌리며 그 이유를 지연이라고 적어 두었지만 재 보니 판정은 p99 3~5ms였습니다. 지연을 지운 뒤에도 켜지 못한 세 가지와 그중 하나를 잘못 짚었던 이야기입니다.
1만 원 승인으로 대사를 MATCHED로 확정한 뒤 3천 원 부분취소가 왔다. 내부 스냅샷은 7,000원이 됐는데 확정된 판정은 10,000원 그대로였다. 승인 행을 덮어쓰던 것을 사건별 행으로 쌓게 바꾼 기록이다.
배포 단위 분리, 원장 잔액 조회, 아웃박스 복구, 데이터 이관처럼 문서와 테스트만으로 통과시킨 경로를 실제로 실행했다. 설계가 있어도 실행권·잔액 조회·커밋 후 발행·이관 중 변경에서 무엇이 빠지는지 확인한 과정을 정리한다.
정산 배치는 틱마다 읽는 양과 순서(id 오름차순)가 정해져 있다. 큰 가맹점 하나가 틱을 다 채우면 뒤 가맹점은 다음 틱으로 밀린다. 정책을 만들어 실측하고 기본값은 꺼 두었다.
정산금을 판매자 계좌로 보내기 전 제재 명단과 대조하는 장치를 실제 지급 경로에 연결했다. UN 명단을 넣어 보니 파서가 세 군데 틀렸고 한글 이름은 한 건도 없었다. 이름만으로 자동 통과하지 않고, 무엇을 놓치고 무엇을 사람에게 넘길지 정한 과정을 정리한다.
결제 운영 자동화 세 곳에서 모델을 켜고 끈 기준. 장애 로그 진단과 운영자용 요약은 켰고 대사 원인 분류는 껐다. 그 과정에서 평가 장치도 세 번 틀렸고, 마지막 한 건은 모델이 아니라 제 라벨이 틀렸을 가능성으로 남겼다.
자체 목 PG가 승인 응답 뒤에만 웹훅을 보내도록 만들어 선행 웹훅 경로를 놓쳤다. 결제 행이 아직 없을 때 예외를 던지면 보류 상태까지 롤백되는 문제를 재현하고, 먼저 온 웹훅을 보류 후 재처리하는 구조로 바꾼 과정을 정리한다.
규칙이 설명하지 못한 대사 예외에 모델을 붙였지만, 실제 잔여 분포에서 규칙 분류기와 같은 답을 냈다. 개발 표본의 91~100%만 보고 자동화를 켜지 않고, 홀드아웃에서 개선이 0임을 확인한 뒤 기본값을 규칙으로 유지한 이유를 정리한다.
결제 상담 초안을 자동 채점하는 루브릭이 평균 4.97/5점을 줬지만, 사람이 블라인드로 쓴 답과 비교하니 편집 거리 중앙값이 45.6%였다. 루브릭을 조이면 정상 문장을 놓치고, 고객 영향을 요구하면 모델이 모르는 것까지 단언했고, 다른 계열 심판도 틀렸다.
모델을 붙이기 전에 포트와 숫자 검증기부터 만들었다. 실제 데이터를 넣자 검증기가 분류기가 계산한 진짜 270원을 출처에 없다며 반려했고, 고친 뒤에도 반복 실행에서만 드러나는 결함이 둘 더 나왔다.
결제 대사 담당자가 한 주문의 원인을 판단하려면 API 7회와 목록 검색 6회를 거쳐야 했다. 여러 도메인의 근거를 읽기 전용 타임라인으로 모으고, 확정 가능한 원인만 근거와 함께 제안하도록 바꾼 과정을 정리한다.
API 인스턴스를 둘로 늘리자 같은 정산 배치가 중복 실행됐고, 한 대로 유지하자 배포 중 결제 요청이 실패했다. 유니크 제약만으로는 실행권을 정할 수 없어서, MSA 분리 대신 API와 워커의 실행·배포 단위를 나눈 과정을 정리한다.
Argon2id는 BCrypt보다 메모리를 많이 써 동시 로그인 100건에 약 2GB를 요구했다. 512MB 힙에서는 OOM이 발생했고, 인코더 테스트를 통과한 뒤 해시 컬럼 길이 문제도 발견했다. 비밀번호 해시 교체에는 보안 강도뿐 아니라 동시성 예산과 이관 종료 조건이 필요했다.
재고 20개에 스레드 30개를 동시에 앞세워 세 가지 차감 전략을 실 MySQL에서 쟀습니다. 인메모리에서 앞섰던 낙관적 락의 순위가 실 DB에서 뒤집혀 조건부 UPDATE를 골랐습니다. 그 뒤 재고를 잡는 시점이 다시 바뀌었고 한 행이 버티는 처리량 상한과 느슨한 카운터와의 교환도 다시 쟀습니다.
결제의 실패와 모름을 구분하고 조회·보상·웹훅으로 정합성을 복구한 과정. 타임아웃은 UNKNOWN으로 남겨 복구 배치가 확정한다. 승인 뒤 내부 실패는 보상으로 되돌리고, failover는 요청이 PG에 닿지 못한 경우로 한정한다.