🎟️ 결제 API는 정말 중복 결제를 막을 수 있을까?

2026. 4. 27. 12:11·프로젝트/공연 티켓팅 프로젝트

티켓팅 서비스에서 예약만큼 중요한 게 결제입니다.

특히 이런 상황은 실제 서비스에서도 충분히 발생할 수 있습니다.

  • 사용자가 결제 버튼 연타
  • 네트워크 지연으로 여러 번 클릭
  • 브라우저 새로고침
  • 동일 요청 재전송
  • 악의적인 중복 요청

만약 이런 상황에서 결제가 여러 번 처리되면

  • 중복 결제 발생
  • 동일 예약에 여러 payment 생성
  • 환불 이슈 발생
  • 고객 CS 폭증

실제 서비스 장애로 이어질 가능성이 큽니다.

그래서 제 프로젝트에서는 결제 API에 idempotencyKey 기반 멱등성 처리를 추가했습니다.

하지만 구현보다 더 중요한 건

"정말 동시 요청에서도 중복 결제가 발생하지 않을까?"

였습니다.

이번에는 직접 동시성 테스트를 진행했습니다.


1️⃣ 현재 결제 구조

현재 결제 API는 아래 순서로 동작합니다.

결제 요청
→ 기존 payment 조회
→ Redis SETNX 중복 방어
→ DB unique key 검증
→ 결제 생성

현재 프로젝트 Spring Boot 에서는
총 3단계로 중복 결제를 방어하고 있습니다.

1차 방어

기존 결제 데이터 조회

이미 결제 완료된 데이터가 있으면 차단

2차 방어

Redis SETNX

동시에 동일 요청이 들어올 경우
가장 먼저 들어온 요청만 통과

3차 방어

MySQL unique key

최종적으로 DB 레벨에서도 중복 저장 방지

 

즉, 애플리케이션 + Redis + DB 총 3중 방어 구조입니다.


2️⃣ 테스트 목표

이번 테스트에서 검증하고 싶었던 것은 3가지였습니다.

👉 동일 결제 중복 방지

동시에 여러 요청이 와도 payment는 1건만 생성되어야 함

👉 동시성 안정성 검증

다수 요청 상황에서도 서버 오류가 발생하지 않아야 함

👉 데이터 정합성 유지

booking / payment 상태가 정상적으로 유지되어야 함


3️⃣ 테스트 전 데이터 초기화

먼저 기존 데이터를 초기화했습니다.

delete from payment 
where user_id = 1 
and concert_id = 1;

delete from booking 
where user_id = 1 
and concert_id = 1;

update concert
set booked_count = 0
where id = 1;

4️⃣ 예약 생성

결제를 위해 먼저 예약 데이터를 생성했습니다.

Redis 입장권 발급

SETEX admitted:concert:1:user:1 600 1

예약 생성 요청

POST /api/concerts/1/booking
Authorization: Bearer <JWT>

정상적으로 booking 생성 완료


5️⃣ JMeter 동시 결제 테스트

이제 핵심 테스트입니다.

Apache JMeter 설정

  • Thread : 20
  • Ramp-up : 1초
  • Loop Count : 1
  • Synchronizing Timer 사용

즉,

동일 사용자가 거의 동시에 결제를 여러 번 시도하는 상황을 만들었습니다.

요청 Body

{
  "idempotencyKey": "pay-jmeter-001",
  "paymentMethod": "CARD"
}

핵심은

모든 요청이 동일한 idempotencyKey를 사용했다는 점입니다.


6️⃣ 테스트 결과

JMeter 결과

  • 여러 요청 동시 실행
  • 일부 요청 성공
  • 일부 요청 중복 차단 응답

중요한 건

서버 에러(500)가 발생하지 않았습니다.

예외 상황에서도 시스템이 안정적으로 동작했습니다.


7️⃣ DB 검증

실제 payment 생성 여부를 확인했습니다.

select *
from payment
where idempotency_key = 'pay-jmeter-001';

조회 결과

id: 2
user_id: 1
concert_id: 1
amount: 10
status: COMPLETED
idempotency_key: pay-jmeter-001

결과적으로

payment는 단 1건만 생성됐습니다.


8️⃣ 왜 중복 결제가 발생하지 않았을까?

동시에 여러 요청이 들어오더라도

첫 번째 요청만 통과합니다.

요청 1 → 결제 성공
요청 2~20 → 중복 차단

Redis SETNX

이미 처리 중인 요청 차단

DB Unique Key

최종 중복 저장 차단

결과적으로 여러 요청이 와도
실제 결제는 한 번만 수행됩니다.


9️⃣ 이번 테스트에서 배운 점

처음에는 단순히

"payment 테이블에 unique key 걸면 끝 아닌가?"

라고 생각했습니다.

하지만 실제 동시 요청 환경에서는

  • 애플리케이션 레벨 방어
  • Redis 방어
  • DB 방어

여러 계층이 필요하다는 걸 느꼈습니다.

특히 결제는 돈과 직접 연결되기 때문에
훨씬 보수적으로 설계해야 했습니다.

'프로젝트 > 공연 티켓팅 프로젝트' 카테고리의 다른 글

🎟️ Docker-compose up 한 번으로 실행되는 백엔드 만들기  (1) 2026.05.18
🎟️ MySQL 인덱스 실험: 예약 만료 배치 쿼리 성능 개선 (10만 건 실험)  (1) 2026.05.13
🎟️ 예약 API 동시성 테스트 (다중 사용자 경쟁) 결과 보고  (1) 2026.04.27
🎟️ Redis 입장권 기반 예약 시스템은 정말 중복 예약을 막을까?  (0) 2026.04.27
🎟️ 선착순 강의에서 공연 티켓팅으로 확장한 이유  (2) 2026.04.17
'프로젝트/공연 티켓팅 프로젝트' 카테고리의 다른 글
  • 🎟️ Docker-compose up 한 번으로 실행되는 백엔드 만들기
  • 🎟️ MySQL 인덱스 실험: 예약 만료 배치 쿼리 성능 개선 (10만 건 실험)
  • 🎟️ 예약 API 동시성 테스트 (다중 사용자 경쟁) 결과 보고
  • 🎟️ Redis 입장권 기반 예약 시스템은 정말 중복 예약을 막을까?
hak0622
hak0622
개발하면서 “이게 뭐지?”라는 순간마다 궁금한 점을 바탕으로 정리한 개발 블로그입니다.
  • hak0622
    궁금한 개발 이야기 Why?
    hak0622
  • 전체
    오늘
    어제
    • 분류 전체보기 (81)
      • 공부 (36)
        • 1. 자바 ORM 표준 JPA 프로그래밍 - 기본.. (35)
        • 시험 (1)
      • 프로젝트 (45)
        • 스프링 부트 3 백엔드 개발자 되기 Blog + .. (35)
        • 공연 티켓팅 프로젝트 (10)
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
hak0622
🎟️ 결제 API는 정말 중복 결제를 막을 수 있을까?
상단으로

티스토리툴바