Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 냄새라도
- 로그백
- JPA
- 클린아키텍처
- 설정세팅
- 공통규약
- JDBC
- MVC요청플로우
- Transaction
- 이벤트핸들러등록
- SEQUENCE
- 이벤트핸들러this
- TDD
- 단위테스트
- 문제선택근거
- 커뮤니티서버
- 전전긍긍
- AOP
- springsecurity
- Ajax
- index
- model
- 사면초가
- string
- 공통기술
- SpringLegacy
- OOP
- DDD
- 디스크i/o
- MVC
Archives
- Today
- Total
개발이군고구마
[Redis/Kafka 이용한 대용량 트래픽 예약 및 쿠폰 발급 서비스] 1.멀티 스레드의 동시성을 고려하지 못한 기존 구조 본문
SERVER/Kafka
[Redis/Kafka 이용한 대용량 트래픽 예약 및 쿠폰 발급 서비스] 1.멀티 스레드의 동시성을 고려하지 못한 기존 구조
김구황 2025. 3. 2. 20:13728x90
1. 기존 구조
1️⃣시퀀스 다이어그램

- 회원에 한해서 예약이 가능
- 예약 요청 인원을 재고 요청 인원과 비교하여, 요청 인원<재고인지 확인
- 재고 인원이 확보되는 경우 예약 테이블에 저장
- 쿠폰을 발급 받지 않은 회원인지 확인
- 쿠폰을 발급 받지 않은 회원에게 쿠폰 발급 (=쿠폰 이력 테이블에 저장)
2️⃣ 클래스 다이어그램

- 예약 처리 클래스 안에서 쿠폰 repository를 의존성 주입 받음
- 비즈니스 로직의 흐름에 따라 절차적으로 스레드가 실행되는 구조
2. 예약 요청
👩🏻🌾 HOST 세팅
- 총 10명의 인원을 수용할 수 있는 HOST 를 생성함
- 요청(=스레드)을 처리할 수 있는 스레드풀 사이즈 5개로 고정함
- 2명의 예약 신청 7개가 동시에 들어옴
👥 기대 결과

- 요청 시도 7개 중 최대 수용 가능 인원 10명에 맞추어 5개 요청 성공
- 5개 요청이 성공하면 HOST의 수용 가능 인원 0명이 되며, 이후 요청은 재고 부족으로 예약 거절
- 예약 성공 요청에 대해서는 쿠폰 발급
💥통합 테스트 실제 결과
executor 사용
final int numberOfOrders = 7; // 스레드 7개 요청됨
ExecutorService es = Executors.newFixedThreadPool(5);
CountDownLatch latch = new CountDownLatch(numberOfOrders);
for (int i = 0; i < numberOfOrders; i++) {
String taskName = "thread" + i;
es.execute(() -> {
try {
System.out.println("현재 스레드정보 :[" + taskName+"]");
resrvService.saveRequest(mockPrincipal, histRequestDto);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
latch.countDown(); // 주문 처리 후 countDown
}
});
}
latch.await(); // 모든 스레드가 끝날 때까지 대기
es.shutdown(); // 스레드 풀 종료
- 지정한 풀 사이즈에 따라 5개의 요청이 스레드에 배정됨
- 5개의 요청이 ‘동시에’ 재고를 확인함
- 스레드 요청 ‘순서에 상관없이’ 재고를 확인 및 처리


- 예약 완료 후 재고 차감을 할 때, 모든 요청 스레드가 HOST의 수용 가능 인원을 ‘공유’ 하기 때문에
- 5개의 요청이 바뀌기 전 값에서 요청 인원을 뺀 값을 저장함

- 7개의 요청 중 나머지 2개의 추가 요청이 처리되고, 마찬가지로 2개의 요청이 동시에 처리됨



🤦🏻♀️ 7개의 요청이 들어왔음에도 HOST의 수용 가능 인원이 6이 되는 결과
org.opentest4j.AssertionFailedError:
Expecting:
<6>
to be equal to:
<0>
but was not.
Expected :0
Actual :6
3. 무엇이 문제였을까
1️⃣ 우선 스레드에 대해서 알아보자
- 프로그램: 단순한 파일
- 프로세스 : 메모리에 실행 중인 프로그램의 인스턴스
- 프로그램 실행 : 프로세스가 생성 후 코드가 한 줄씩 실행되는 되는 것
- 스레드 : 코드를 한 줄씩 실행하는 주체 (= 프로세스 내에서 실행되는 작업의 단위)
✔️ 한 프로세스 내에 여러 개의 스레드가 존재할 수 있으며(=멀티스레드)
✔️ 스레드는 프로세스의 있는 동일한 메모리 공간을 공유

2️⃣ 스레드는 어떻게 작동할까
- 스케줄링 : 운영체제가 CPU에 어떤 프로그램이 얼만큼 실행될지 결정하는 방법
- 컨텍스트 스위칭 : 여러 개의 스레드가 하나의 코어를 공유하며 스레드를 번갈아 실행하는 멀티 태스킹을 진행할 때, 스레드 변경되는 시점 CPU의 값을 메모리에 저장하고 불러오는 과정
3️⃣ 멀티 스레드와 동시성
- 멀티 스레드 : 한 프로세스에서 여러 개의 스레드가 실행되는 방식. 스레드는 자신이 속한 프로세스의 메모리를 ‘공유’
- 동시성 : 하나의 코어에서 멀티 스레드가 ‘번갈아’ 실행하는 성질

4️⃣ 그래서 무엇이 문제였을까
- 우선, 운영체제의 스케줄링을 예측하여 CPU의 스레드 처리 ‘순서’ 도 보장할 수 없을 뿐더러
- 먼저 실행된 스레드가 데이터를 변경 커밋하기 이전에 다른 스레드들이 메모리 공유 자원에 접근하여 데이터를 확인함

5️⃣ 추가 문제의 소지 : 한 트랜잭션에 수 많은 디스크 I/O 존재
int curReqPpl = histRequestDto.getReqPpl();
int curIvtPplByHost = hostRepository.countIvtPpl(host.get().getHnum());
if (curReqPpl > curIvtPplByHost) {
throw new Exception("재고인원 부족");
} else{
host.get().updateIvtPpl(histRequestDto.getReqPpl());
hostRepository.save(host.get());
// 예약 완료
histRequestDto.setUserid(userId);
resrvHisRepository.save(histRequestDto.toEntity());
// 쿠폰 발급 로직
// 선착순 20명 CPN00001
Optional<Cpn> cpn = cpnRepository.findById("CPN00001");
// ** 회원이 쿠폰 가지고 있는지
int couponCntByUser = cpnRepository.searchCpnByUserId("CPN00001", userId);
log.info("사용자 쿠폰 확인 조회 : {}", couponCntByUser);
if (cpn.isPresent() && cpn.get().getIvtCnt() > 0 && couponCntByUser <= 0) {
// 재고 존재 + 사용자 발급받은 적 없음
// 쿠폰 재고 차감
cpn.get().updateIvtCnt();
cpnRepository.save(cpn.get());
log.info("쿠폰 재고 차감 후 현재 재고: {}", cpn.get().getIvtCnt());
// 회원에 따른 쿠폰 발급
cpnIssuRepository.save(CpnIssu.builder()
.cpnNum(cpn.get().getCpnNum())
.userid(user.get().getId())
.build()
);
log.info("사용자 쿠폰 발급 성공 : {}", cpn.get().getCpnNum());
} else {
log.info("쿠폰 발급 실패 재고 부족");
}
💥재고 확인
💥쿠폰 발급 확인 및 쿠폰 발급
- 대량의 트래픽 상황에서 스레드 풀의 모든 스레드가 요청을 처리하고 있는 경우 서버 과부화로 인한 나머지 예약 취소 현상
- 또는 한 스레드에서 지연이 발생하는 경우 알림 없이 무한 대기 현상
- 비즈니스 로직이 계속해서 증가하는 경우라면 ? (예, 포인트 적립, ERP연동 등) 계속해서 지연
4. 어떻게 해결할 수 있을까
1️⃣ 공유 자원 접근 막기

2️⃣ 로직 분리
- 예약과 관련되지 않은 부분은 추후에 처리해도 무방함
- 비동기 처리하여 확장성에 대비
3️⃣ 디스크 I/O 최소화
- 수 많은 재고 확인을 매번 데이터를 조회하는 것은 비효율
- 캐시 서버 사용 방법 고려
✔️ 캐시 서버와 DB 데이터를 어떻게 일치 시켜 줄 것인지 고민
참고 문헌
- https://connie.tistory.com/12 [자바 멀티 스레드 속성]
- 김영한 자바 고급 1 강의 [동시성]
서울우먼잇츠 스터디 1주차 스터디 발표 자료
'SERVER > Kafka' 카테고리의 다른 글
| [Kafka/Redis] Kafka 설치 (0) | 2026.02.18 |
|---|---|
| [Kafka/Redis] Kafka 개념 (0) | 2026.02.03 |
| [Redis/Kafka 이용한 대용량 트래픽 예약 및 쿠폰 발급 서비스] 3. Kafka와 Redis로 동시성, 대량 트래픽 제어 (0) | 2025.03.24 |
| [Redis/Kafka 이용한 대용량 트래픽 예약 및 쿠폰 발급 서비스] 2.대용량 트래픽을 견디기 위한 방법 고민 (0) | 2025.03.24 |
| [Kafka] 왜 카프카 인가 (0) | 2025.02.02 |