늦었지만, 꼭 필요했던 성능 테스트
프로젝트 초기, WebSocket 기반의 채팅 메시지를 MySQL에 직접 저장하는 방식으로 구현했습니다. 당시에는 기능 구현에 집중했지만, 시간이 지날수록 ‘과연 이 구조가 수많은 사용자가 몰리는 실시간 서비스에 적합할까?’라는 의문이 들기 시작했습니다.
이러한 고민 끝에 실시간 처리 성능 향상을 위해 Redis를 도입하여 임시 저장 후 스케줄러를 통해 MySQL에 저장하는 방식으로 구조를 개선했습니다. 단순히 ‘동작하는 시스템’을 넘어, ‘성능적으로 설계된 시스템’이라는 확신을 얻고 싶었기에, Redis 도입의 효과를 정량적으로 검증하고 현재 구조의 한계를 파악하기 위해 성능 테스트를 수행하게 되었습니다.
테스트 환경 구성
이번 성능 테스트를 통해 얻고 싶은 점은 Redis 기반 임시 저장 구조가 실시간 채팅 시스템에서 실제로 성능에 긍정적인 영향을 주는지 정량적으로 확인하는 것이었습니다.
테스트 결과의 순수성을 보장하고 외부 요인으로 인한 간섭을 최소화하기 위해 기존 프로젝트 환경을 일부 조정했습니다.
테스트 환경 구성을 위한 선행 작업
인증 무력화
프로젝트는 JWT 기반의 WebSocket&STOMP 채팅 시스템이었기 때문에, 채팅 메시지를 전송하기 위해서는 먼저 JWT 인증을 거쳐야 했습니다. JMeter와 같은 부하 도구로 테스트를 진행할 때마다 JWT 토큰을 발급받아 요청에 포함하는 것은 비효율적이라고 생각했습니다.
따라서 WebSecurityCustomizer를 활용하여 웹소켓 경로(/ws/**)를 Spring Security 필터 예외 처리했습니다. 이를 통해 테스트 시 불필요한 인증 과정이 성능 측정에 영향을 미치지 않도록 했습니다.
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring()
.requestMatchers("/ws/**");
...
}
리스너 내 인증 의존성 제거
기존 프로젝트의 WebSocketEventListener는 채팅방 접속 시 세션에 저장된 사용자 정보(AuthUser)를 기반으로 로직을 수행하도록 구현되어 있었습니다. 이는 클라이언트의 STOMP CONNECT 요청에 응답하여 AuthChannelInterceptor가 JWT를 검증하고 AuthUser를 세션에 저장하는 정상적인 흐름을 전제로 합니다.

하지만 JMeter와 같은 부하 도구로 테스트를 진행할 경우, 이 인증 절차가 생략되면서 AuthUser가 null이 될 수 있습니다. 이는 NullPointException과 같은 예외를 발생시키고, 불필요한 오류 로그를 대량으로 생성해 테스트 결과의 정확성을 저해할 위험이 있습니다.
따라서 테스트 중 발생하는 오류를 사전에 방지하기 위해 AuthUser가 null일 경우, data.sql에 미리 준비해 둔 테스트용 더미 데이터로 대체하도록 로직을 수정했습니다. 이로써 인증 과정과 무관하게 리스너가 정상적으로 동작하도록 만들었고, 순수하게 채팅 메시지 처리 성능에만 집중할 수 있는 환경을 구축했습니다.
테스트용 초기 데이터 구성
원활한 테스트 진행을 위해, data.sql 파일을 활용하여 회원, 전시회, 채팅방 데이터를 사전에 데이터베이스에 미리 구성했습니다. 이는 테스트 시작과 동시에 부하를 발생시킬 수 있는 환경을 마련하기 위함이었습니다.
-- 1. 테스트용 회원 데이터 생성
INSERT INTO tbl_member (id, email, nickname, password, ...)
VALUES (1, 'testMember@naver.com', 'solmoon', '...', ...);
-- 2. 테스트용 전시회 데이터 생성 (Gallery)
INSERT INTO tbl_gallery (id, title, content, ...)
VALUES (1, 'JMeter 테스트 전시', '성능 측정을 위한 전시 설명입니다.', ...);
-- 3. 테스트용 채팅방 데이터 생성 (ChatRoom)
INSERT INTO tbl_chat_room (id, title, gallery_id, ...)
VALUES (1, 'JMeter 테스트 채팅방', 1, ...);
테스트 설정 및 실행, RDB vs. Redis
테스트 환경 구성이 완료되었으니, 이제 Apache JMeter를 활용해 MySQL에 직접 저장하는 방식과 Redis에 임시 저장하는 방식의 성능을 비교했습니다.
테스트 설정

﹒ 부하 도구: Apache JMeter
﹒ 테스트 대상: WebSocket 기반 채팅 메시지 쓰기/읽기 API
﹒ 부하 설정: 1,000 Threads (동시 접속자 1,000명 시뮬레이션), 50초에 걸쳐 부하를 점진적으로 증가, 총 60초간 테스트
테스트는 쓰기/읽기 작업의 성능을 각각 측정하기 위해 총 4개의 스레드 그룹으로 나누어 진행했습니다.

| 테스트 그룹 | 작업 유형 | 시나리오 |
| MySQL 성능 테스트 | 쓰기/읽기 | 웹소켓 연결, 메시지 전송 및 RDB 메시지 조회 |
| Redis 성능 테스트 | 쓰기/읽기 | 웹소켓 연결, 메시지 전송 (Redis 임시 저장) 및 Redis 메시지 조회 (캐시 미스 시 RDB 조회) |
성능 결과 분석
JMeter 테스트 결과는 Redis를 도입한 후 응답 시간이 개선되었음을 확인할 수 있었습니다.
그래프를 확인해 보면 Redis의 평균 응답 시간 (Average Response Time)이 MySQL에 비해 압도적으로 낮다는 것을 한눈에 알 수 있습니다.

| Label | Samples | Average | Error % | Throughput | Received | Sent |
| MySQL 저장 | 157577 | 39 | 0.45% | 2625.01458 | 169.34 | 152.35 |
| MySQL 조회 | 111237 | 10 | 0.00% | 1861.271 | 4754.69 | 274.46 |
| Redis 저장 | 148545 | 7 | 0.46% | 2475.33744 | 160.5 | 144.29 |
| Redis 조회 | 112997 | 5 | 0.00% | 1889.36078 | 4826.45 | 278.61 |
응답 속도 측면에서 Redis의 도입은 실시간 채팅 서비스의 사용자 경험을 크게 향상할 수 있는 긍정적인 수치였습니다. 하지만 JMeter 리포트의 초당 처리량(Throughput)을 살펴보면 의문이 들었습니다.
| 저장소 | 쓰기 처리량 (TPS) | 읽기 처리량 (TPS) |
| MySQL | 2,625(TPS) | 1,861(TPS) |
| Redis | 2,475(TPS) | 1,889(TPS) |
응답 속도는 크게 개선되었지만, 초당 처리량(TPS)은 큰 차이를 보이지 않거나 오히려 소폭 낮은 결과가 나왔습니다.
이 결과는 “Redis는 이론적으로 수만에서 수십만 TPS를 처리할 수 있는데, 예상했던 것보다 낮은 수치에 머물러 있을까?”라는 의문을 남겼습니다. 이 의문은 다음 개선 방향을 찾는 중요한 계기가 되었습니다.
마치며, 발견된 병목과 다음 개선 방향
성능 테스트를 통해 응답 속도 개선이라는 긍정적인 결과를 얻었지만, 동시에 Redis를 제대로 활용하지 못하고 있다는 구조적 한계를 발견했습니다.
Redis에 데이터를 저장하기 전, 매번 RDB에서 채팅방 및 발신자 정보를 조회하는 비효율적인 로직이 존재했고, 이것이 바로 처리량 저하의 주된 원인이었습니다. 이 병목 현상과 더불어, Redis List에 저장된 100개의 데이터를 MySQL에 저장할 때 100번의 개별 쿼리가 나가는 비효율적인 구조 또한 개선이 필요하다고 판단했습니다.
이러한 문제점을 해결하기 위해 Redis Stream과 Spring Batch를 활용하여 이 문제를 근본적으로 해결하고, 더욱 안정적이고 효율적인 채팅 시스템으로 고도화했는지 다뤄보겠습니다.
'REFLECTION > 7️⃣ LUCKY7 트러블 슈팅' 카테고리의 다른 글
| [ 7️⃣ LUCKY-SEVEN ] 실시간 채팅 시스템 구조 재설계 이야기 (0) | 2025.03.08 |
|---|---|
| [ 7️⃣ LUCKY-SEVEN ] 배포 성공을 위한 삽질의 기록 (0) | 2025.02.26 |
| [ 7️⃣ LUCKY-SEVEN ] 로그인 시스템을 위한 JWT 고군분투기 (0) | 2025.02.24 |
