쿠폰동시발급 오류 개선

WAS·2026년 7월 20일

사이드프로젝트

목록 보기
6/6
post-thumbnail

💡 테스트 규모를 1만명으로 정한 이유
처음에는 1만명 이상의 규모로 직접 검증을 시도했다. 그러나 현재 사용 중인 인스턴스가 Oracle Cloud Always Free 티어(4 OCPU) 로 제한되어 있어, 애플리케이션이 아니라 서버 자체가 응답 불능 상태로 다운되는 것을 반복적으로 확인했다. 이는 로직상의 결함이 아니라 테스트 환경의 물리적 한계(CPU/메모리 자원 자체가 부족)으로, 실제 인프라 스펙을 유료로 증설하지 않는 한 안정적으로 재현하기 어려운 조건이었다. 따라서 이후의 모든 성능 검증은 서버가 안정적으로 응답을 유지하면서도 동시성 문제를 명확히 드러내는 1만명 규모를 기준으로 진행했다.

추가로 카프카 Consumer 처리마다 의도적으로 지연시켰는데, 실제 재고차감 (Redis 연산) 방식의 속도가 너무 빠르기 때문에, 대기열에 요청이 몰려도 순식간에 소진되기 때문에 사용자가 대기 중 상태를 체감할 수 없어서 시각적인 검증을 위해 의도적으로 지연시켰다.

💡 스레드풀 / DB 커넥션 풀 증설을 채택하지 않은 이유

  • 부하 시점 CPU 사용률이 이미 94.1%로 포화 상태였다. 스레드는 동시에 접수 가능한 슬롯일 뿐 실제 연산은 CPU가 수행하므로, CPU가 이미 포화된 상태에서 스레드만 늘리면 컨텍스트 스위칭 비용만 증가하고 실질적 처리량은 개선되지 않는다고 판단했다.

  • 사용 중인 Oracle Autonomous Database가 Always Free 티어로 동시 세션 수 자체가 낮게 고정되어 있어, 애플리케이션 쪽에서 풀 크기를 늘려도 DB가 초과 연결을 거부한다. 실질적으로 늘리려면 유료 티어 전환이 필요했는데, 이는 비용 대비 효과를 고려했을 때 우선순위가 낮다고 판단했다.

  • 두 방법 모두 비용을 들이지 않고는 늘릴 수 없는 리소스를 늘리려는 접근이었으며 리소스 증설이 아니라 처리 방식 자체를 동기→비동기로 전환하는 Kafka 기반 구조로 방향을 바꾸게 되었다.

k6로 테스트를 진행해보자 (그라파나로 모니터링 같이 진행)


  • ① keepalive 64; — "안 쓰는 전화기(연결)를 끊지 말고 최대 64개까지 대기시켜놔" 라고 nginx한테 시키는 거예요. 다음 요청 올 때 이 대기 중인 것 중 하나를 바로
    재활용해요.

  • ② proxy_http_version 1.1; — 사실 nginx가 백엔드(WAS)한테 말 걸 때 쓰는 프로토콜 버전은 기본값이 구식(HTTP/1.0)이에요. 이 구버전은 애초에 "전화 안 끊고 계속
    통화" 기능 자체가 없어요. 그래서 최신 버전(1.1)으로 바꿔줘야 재사용이 가능해져요. ①이 있어도 이게 없으면 재사용 자체가 안 됨.

  • ③ proxy_set_header Connection ""; — 이게 은근 중요한데, nginx는 백엔드로 요청 넘길 때 기본적으로 Connection: close라는 헤더를 자동으로 붙여요. 이건
    한국어로 치면 "이거 처리하고 바로 끊어주세요"라고 매번 먼저 말해버리는 거예요. ①②를 설정해도 이 헤더가 남아있으면 백엔드(스프링부트)가 "어, 끊으라고 했으니
    끊을게요" 하고 알아서 닫아버려요. 그래서 이 자동 헤더를 빈 값으로 지워서 "끊으란 말 안 할 테니 계속 갖고 있어"로 바꾼 거예요.

    정리하면: ②③이 "재사용 가능한 상태"를 만들어주고, ①이 "실제로 재사용할 창구(연결 풀)"를 만들어줘요. 셋 다 있어야 keepalive가 진짜로 작동해요.

✅ k6로 1만명의 유저가 쿠폰을 동시에 발급받았을 경우 테스트

Problem : k6로 1만명이 쿠폰 발급 API에 동시 요청을 보내는 부하테스트를 진행한 결과, HTTP 500 에러가 136건 발생

Action : NGINX 에러 로그를 확인하니, 동시 연결 처리 한도(worker_connections, 기본값 768) 으로 설정되어 있어서 초과한 요청은 서버로 전달되지도 못하고 nginx 단계에서 즉시 500 에러로 거부된다는 것을 파악하고 worker_connections를 768 → 4096으로 상향

Result : 동일 조건 재검증 결과 HTTP 500 에러 136건 → 0건으로 해결


✅ 카프카 비동기 대기열 도입

Problem : nginx 문제 해결 후에도, 순간적으로 몰리는 요청을 그 자리에서 즉시 처리하려는 동기 처리 구조 자체의 한계가 발생 -> 사용자는 응답이 올때까지 알 수없는 상태로 대기해야하는 문제 발생

Action : 위에서 언급한 리소스를 늘리는 방식(스레드풀/DB 풀 증설) 대신, 비동기 방식으로 전환
카프카 메시지 큐를 활용하여 요청을 순차처리하도록 구조를 변경하며 사용자가 쿠폰 발급 시 앞에 순번이 있을 경우 처리 진행률을 실시간으로 브로드캐스트 해주는 대기열 팝업 UI 구현

result : 1만명 동시 요청 검증 결과 접수 성공률 100% (coupon_accepted)
실패율 0%(http_req_failed), 서버 순수 응답시간 평균 176ms(http_req_duration)아래 영상은 실제 부하테스트 영상으로 GIF 변환으로 인해 속도가 두배 느림

Reflection : 동시 요청으로 인해 발생되는 문제점을 알게되었고, 테스트를 통해 근본 원인을 분석하고 해결하는 것이 중요하다고 배웠음

🚀 앞으로 해야할 것

이번작업의 핵심은 자원을 늘리지 않고, 비동기 구조 전환 으로 병목을 해소 하는 것이었다.
이 프로젝트의 트래픽이 더 커진다면, 아래와 같은 방법의 확장이 필요할 것이다.

  • Kafka 파티션 확장
  • WAS 인스턴스 다중화 + 로드밸런싱
  • DB 커넥션 풀 / 세션 한도 증설
profile
우측 상단 햇님모양 클릭하셔서 무조건 야간모드로 봐주세요!!

0개의 댓글