| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- 자바
- 김영한
- redis
- 계산기
- 배치
- 빌더 패턴
- Spring
- spring boot
- 성능 개선
- 코드카타
- 스프링 배치
- 로드밸런서
- 백엔드
- lv1
- 디자인 패턴
- DB
- Effective Java
- 템플릿 메서드 패턴
- 스프링
- 스케줄러
- Spring Batch
- GoF 23
- 토스
- 트러블슈팅
- 추상클래스
- Til
- 이펙티브 자바
- 프로그래머스
- java
- 프록시 패턴
- Today
- Total
김코딩
Redis의 분산락을 활용한 동시성 제어 테스트 본문
지난번에는 프로젝트에서 어떻게 동시성 제어가 발생하였고, lock을 어떻게 구현하였는지 알아보았습니다.
https://ddokyun.tistory.com/60
같은 자원에 동시에 요청이 발생하였을 때, 어떻게 대처할까? (lock을 이용한 동시성 제어)
콘서트 예매 프로젝트를 진행하면서, 좌석 예매 시 동일한 좌석에 대해 여러 사용자가 동시에 요청을 보내는 상황이 발생했고, 이로 인해 중복 예매 문제가 실제로 발생하는 것을 확인했습니다.
ddokyun.tistory.com
이번에는 이전 글에서 구현한 Redis 기반 락을 실제로 적용했을 때,
정말로 동시성 제어가 잘 이루어지는지 확인해보기 위해 테스트를 진행해보았습니다.
이번 테스트는 JMeter를 사용하여 테스트를 진행하였습니다.
JMeter란?
- 아파치(Apache)에서 개발한 성능 테스트 도구로, 웹 애플리케이션, 데이터베이스, FTP 등 다양한 종류의 서버에 대한 성능 테스트를 수행할 수 있다.
- Java로 작성되어 있으며, GUI를 통해 테스트 계획을 작성하고 테스트 결과를 시각적으로 확인할 수 있다.
- 다양한 프로토콜을 지원하고, 분산 테스트를 지원하여 여러 대의 컴퓨터에서 테스트를 수행할 수 있다.
- 사용하기 쉽고 다양한 기능을 제공한다.
JMeter 다운로드 및 실행
- Jmeter를 사용하기 전에는 우선 자바 버전 8 이상이 있는지 확인합니다.
- 아래의 링크로 들어가서 JMeter를 다운로드 해줍니다.
https://jmeter.apache.org/download_jmeter.cgi#binaries
Apache JMeter - Download Apache JMeter
Download Apache JMeter We recommend you use a mirror to download our release builds, but you must verify the integrity of the downloaded files using signatures downloaded from our main distribution directories. Recent releases (48 hours) may not yet be ava
jmeter.apache.org
3. zip 파일을 다운받고 압축을 풀어줍니다.

4. cmd창으로 jmeter가 들어있는 폴더로 이동합니다.
5. 그 폴더 안에 bin 폴더로 한번 더 들어갑니다.
6. bin 폴더 안에서 jmeter.bat을 입력하면 JMeter가 실행이 됩니다.

쓰레드 수 설정
Jmeter를 사용해 동시성 테스트를 진행할 때, 쓰레드 수를 먼저 설정해주었습니다. 현재 저희 테스트에서는 서로 다른 50명이 한 좌석을 점유하는 테스트를 진행하였기에 Tread 수를 50으로 설정해주었습니다.
1. 처음에 Test Plan 우클릭 -> Add -> Treads(Users) -> Tread Group
- Number of Threads(사용자 수) : 동시에 작업을 수행할 사용자의 수
- Ramp-up period(램프업 기간) : 동시에 사용자가 추가되는 데 걸리는 시간
- Loop Count(반복 횟수) : 각 사용자가 액션을 수행하는데 반복할 횟수
저는 50, 0, 1로 설정을 해주었으니 → 50명의 사용자가 동시에 1번의 요청을 보낸다 라는 뜻입니다.

2. 저희 팀의 프로젝트 같은 경우에는 Spring Security + JWT 토큰을 사용한 인증 인가를 설정해주었습니다. 따라서 예매 요청을 보낼 시에 토큰을 넣어주어야하는데, 토큰을 수동으로 넣어주기에는 100명이라는 사용자가 너무 많다고 생각하여 회원가입과 동시에 토큰을 CSV 파일로 저장하도록 자동화 해주었습니다. (저희 프로젝트에서는 회원가입 응답으로 토큰을 보내줘서 가능합니다.)
2.1 생성한 쓰레드 그룹을 우클릭 -> Add -> Config Element -> CSV Data Set Config

- Filename에 회원가입 시 필요한 요청 csv 파일을 만들어서 그 파일을 올려주시면 됩니다. 저희 프로젝트의 경우에는 회원가입 시 email, nickname, birth, password 이렇게 4개의 값이 필요하므로 조건에 맞춰서 만들어주었습니다.
- 밑에 Variable Names 에서 필드 값을 지정해주면 됩니다. email, nickname, birth, password로 설정해주었습니다.
- Delimiter는 , 를 이용해서 구분하겠다 라는 뜻입니다.

- 이제 회원가입의 Request 요청을 생성해야합니다.
- Tread Group 우클릭 -> Add -> Sampler -> HTTP Reqeust

- 사진에 나온대로 protocal : http , Server Name or IP : localhost, Post Number : 8080, Http Reqeust : POST, Path : /api/auth/signup, Body Data에 알맞은 request 바디 값을 넣어주었습니다.
- 이제 요청에 대한 Heaer Manager를 설정해주어야합니다. 요청 우클릭 -> Add -> Config Element -> HTTP Header Manager를 사용해 Http 요청 안에 Header Manager를 설정해줍니다. (Content-Type : application/json;charset=UTF-8)

- 회원가입과 동시에 토큰을 추출해서 CSV 파일로 만들어야 하므로 BeanShell PostProcessor를 설정해주어야합니다.
- Http 요청 우클릭 -> Add -> Post Processors -> BeanShell PostProcessor

토큰 추출 스크립트
String token = vars.get("token");
if (token != null && !"null".equals(token) && !token.trim().isEmpty()) {
synchronized(this) {
try {
FileWriter fw = new FileWriter("C:\\Users\\user\\study\\token1.csv", true);
BufferedWriter bw = new BufferedWriter(fw);
bw.write(token);
bw.newLine();
bw.flush();
bw.close();
} catch (IOException e) {
log.error("❌ 토큰 저장 중 오류 발생: " + e.getMessage());
}
}
} else {
log.warn("⚠️ 유효하지 않은 토큰 값: " + token);
}
- 이제 회원가입과 동시에 token.csv 파일이 생기는 것을 확인할 수 있습니다.
- token1, token2 파일이 생긴 이유는 저는 회원가입을 각각 50명씩 진행해서 그렇습니다. (락을 적용하지 않았을 때 테스트와 락을 적용하였을 때 테스트를 동시에 진행하기 위해 회원가입을 2번 진행하였습니다.

동시성 제어를 적용하지 않았을 경우
이제 회원가입을 진행하여 토큰까지 모두 발행을 해서 저장을 하였으니, 동시성 제어 테스트를 진행하였습니다.
쓰레드 그룹은 동시성 제어가 적용될 때, 적용되지 않을 때 각각 따로 설정해주었습니다.

먼저 동시성 제어가 적용되지 않을 때 부터 말씀드리겠습니다.
- 락을 적용하지 않고 동시에 50개의 쓰레드를 가지고 테스트를 진행하였습니다.

- CSV Data Set Config에는 token1.csv를 넣어주었습니다. (50명의 사용자의 토큰이 존재합니다.)

- HTTP Header Manager도 설정해주었습니다. (회원가입과는 다르게 API 요청 시 헤더에 토큰이 필요하므로 토큰도 넣어주었습니다.)

- 예상 결과 : 동시성 제어를 적용하지 않았을 때, 50명의 사용자가 좌석 1,2 번에 동시에 요청을 보냈을 때, 50명 모두 1,2 번 좌석을 예매를 한다.
- 실제 결과 : 동시성 제어를 적용하지 않았을 때, 50명의 사용자가 좌석 1,2 번에 동시에 요청을 보냈을 때, 50명 모두 1,2 번 좌석을 예매를 한다.

동시성 제어를 적용하였을 경우
이전에는 락을 적용하지 않고 1,2 번 좌석에 대하여 50명이 동시에 요청을 보냈다면, 이번에는 3,4번 좌석에 대하여 락을 적용하고 동시에 50명이 요청을 보내도록 설정하였습니다.
- 모든 설정은 동일하고 HTTP Request부분에 Path 쪽만 동시성 제어를 적용한 URL로 변경해주었습니다.

- 예상 결과 : 동시성 제어(락)를 적용하였을 때, 50명의 사용자가 좌석 3,4 번에 동시에 요청을 보냈을 때, 1명만 3,4 번 좌석을 예매를 성공하고 나머지 49명은 예매에 실패한다.
- 실제 결과 : 동시성 제어(락)를 적용하였을 때, 50명의 사용자가 좌석 3,4 번에 동시에 요청을 보냈을 때, 1명만 3,4 번 좌석을 예매를 성공하고 나머지 49명은 예매에 실패한다.

동시성 제어가 제대로 적용되는 것을 확인할 수 있습니다.
추가적인 생각
저희 프로젝트는 아무래도 1인당 최대 2매까지의 좌석을 예매할 수 있게 만들어놓다보니까 동시성 제어에 대해서 조금 고민 해보았습니다.
-> 만약에 좌석 1,2 번을 50명이 예매를 동시에 신청하고, 좌석 2,3 번을 50명이 예매를 동시에 신청하면... 좌석 2번에 대한 락은 어떻게 하면 좋을까? 라는 생각을 하였습니다.
- 시나리오
이번 락 구현 작업은 좌석 중복 예매를 방지하고자 수행하였습니다.
우리 시스템에서는 유저가 한 번에 2개의 좌석을 예매할 수 있어,
예를 들어 [1,2]와 [2,3] 좌석을 각각 예매하려는 유저가 동시에 요청하면
seatId 2번을 두 요청이 동시에 점유하려고 시도하는 문제가 발생했습니다.
이를 해결하기 위해 concertId-seatId 조합을 키로 사용하는 락을
좌석별로 순차적으로 획득하도록 설계했으며,
중간에 하나라도 락 획득에 실패하면 기존에 획득한 락을 모두 해제하도록 처리했습니다.
락 적용 전에는 실제로 100명의 유저가 동시에 예매를 시도했을 때 모두 예매에 성공해
중복 예약이 발생했고,
락 적용 후에는 동일 테스트 조건에서 단 1명만 예매에 성공하고
나머지는 409 Conflict 응답을 받아 예매가 거절되었습니다.
이를 통해 동일 좌석에 대한 중복 예매 문제를 효과적으로 방지할 수 있었습니다.
1. 좌석 [1,2] 번 50명 + 좌석 [2,3] 번 50명 테스트(lock이 적용이 되어있지 않은 경우)
- 서로 다른 유저 100명을 가지고 테스트를 진행하였습니다.
- 유저 50명은 1,2 번 좌석에 대해서 동시에 예매를 진행하였고, 나머지 유저 50명은 2,3 번 좌석에 대해서 동시에 예매를 진행하였습니다. 현재 2번 좌석은 서로 다른 100명이 모두 예매를 하려고 하는중입니다.
예상 결과 : [1,2] 번 좌석을 예매한 사람 50명과 [2,3] 번 좌석을 예매한 사람 50명 총 합 100명 중에 100명 모두 좌석을 예매할 수 있다.
테스트 결과 : [1,2] 번 좌석을 예매한 사람 50명과 [2,3] 번 좌석을 예매한 사람 50명 총 합 100명 중에 100명 모두 좌석을 예매할 수 있다.


락이 적용되지 않았을 경우 결과를 확인해보았을 때
-> 1,2번 좌석을 예매하려는 유저 50명 + 2,3번 좌석을 예매하려는 유저 50명 모두 좌석 예약에 성공하였습니다.
2. 좌석 [1,2] 번 50명 + 좌석 [2,3] 번 50명 테스트(lock이 적용이 되어있는 경우)
- 서로 다른 유저 100명을 가지고 테스트를 진행하였습니다.
- 유저 50명은 1,2 번 좌석에 대해서 동시에 예매를 진행하였고, 나머지 유저 50명은 2,3 번 좌석에 대해서 동시에 예매를 진행하였습니다.현재 2번 좌석은 서로 다른 100명이 모두 예매를 하려고 하는중입니다.
예상 결과 : [1,2] 번 좌석을 예매한 사람 50명과 [2,3] 번 좌석을 예매한 사람 50명 총 합 100명 중에 1명만 좌석을 예매할 수 있다.
테스트 결과 : [1,2] 번 좌석을 예매한 사람 50명과 [2,3] 번 좌석을 예매한 사람 50명 총 합 100명 중에 1명만 좌석을 예매할 수 있다.


락이 적용되었을 경우 결과를 확인해보았을 때
-> 1,2번 좌석을 예매하려는 유저 50명 + 2,3번 좌석을 예매하려는 유저 총 100명 중 1명만이 2번 좌석을 가지고 좌석에 예매를 성공하고, 나머지 99명은 좌석 예매에 실패하였습니다.
마무리
이번 동시성 테스트를 통해, 실제 서비스에서 동시 요청이 얼마나 쉽게 충돌을 일으킬 수 있는지 몸소 체감할 수 있었습니다.
특히 좌석 예매처럼 자원을 점유하는 시나리오에서는 락의 설계가 곧 서비스의 안정성과 직결된다는 것을 느꼈고,
단순히 기능 구현을 넘어서 “동시성 제어까지 고려하는 백엔드 개발자”로 한 걸음 더 성장할 수 있는 기회가 되었습니다.
'개발팁' 카테고리의 다른 글
| [ 면접의 神 ] 동시 요청 환경에서 DB 병목 해결을 위한 캐싱 전략 수립 (0) | 2025.11.23 |
|---|---|
| 결제 시스템 성능 개선: 동기식에서 비동기식 처리로의 전환 (3) | 2025.08.06 |
| 같은 자원에 동시에 요청이 발생하였을 때, 어떻게 대처할까? (lock을 이용한 동시성 제어) (0) | 2025.07.13 |
| 코딩에도 밀키트가 있더라(docker) - 1 (2) | 2025.06.20 |
| InelliJ에서 GitHub Repository에 프로젝트 업로드하기 (0) | 2025.03.01 |