1. 문제 상황 파악
- 프로젝트에 STOMP를 통해 1:1 채팅 기능을 구현함
- 페이지네이션 적용해 무한 스크롤까지 구현된 상황
- 채팅 전송
위의 그림과 같은 순서를 가지며 매번 채팅을 전송할 때마다 DB에 접근함
- 채팅 조회
⇒ 다수의 사람이 채팅을 전송 및 조회할 때 모든 요청이 DB에 접근하게 된다면 과부하가 발생하거나 성능이 저하될 것이라고 판단해 성능 개선하려고 함
2. 해결 방법 및 현재 상황에 적용
일반적인 방법 혹은 채팅 아키텍처가 아니지만 프로젝트 진행 도중 조금이라도 성능 개선할 수 없을까하는 생각에 도입함
2-1. 캐싱(caching)
- 자주 사용되는 데이터를 임시 저장소 ( = 캐시, cache)에 저장해 동일한 요청이 들어왔을 때 더 빠른 응답이 가능하게 하는 기술
- 캐시
- 일반적으로 메모리에 위치함 (대표적으로 Redis, Memcached 등 존재)
- DB와 같은 느린 저장소에 비해 빠른 속도가 특징임
2-2. 상태 관리
- 시스템이나 애플리케이션에서 데이터의 현재 상태를 추적하고 관리하는 과정을 의미
- 상태
- 애플리케이션에서 특정 시점의 데이터 (ex. 온/오프라인 상태, 읽음 여부, 웹 소켓 연결 인원 등)
- 상태 관리의 필요성
- 데이터 일관성: 여러 사용자나 서버가 동시에 데이터에 접근할 때, 상태 관리를 통해 데이터의 일관성 유지 가능
- 시스템 안정성: 서버 장애나 네트워크 문제로 인해 상태가 불일치할 수 있는데, 이를 관리함으로써 시스템의 안정성을 높일 수 있음
2-3. Redis 캐시 선택 이유
1. 현재 Redis에 해당 채팅방의 웹 소켓 연결 인원 수를 저장 (상태 관리) 함으로써 읽음 여부 기능 구현함
더보기
구현한 웹 소켓 연결 인원 수에 따른 읽음 여부 기능 간단 설명
- 채팅방 페이지에 접속하면 웹 소켓 연결함
- 연결 인원 수 : 0명 ⇒ 아무도 채팅방 페이지에 머무르고 있지 않음
- 연결 인원 수 : 1명 ⇒ ‘나’ 만 채팅방 페이지에 머무르고 있음
- 채팅 전송 시 읽지 않음으로 저장 - 연결 인원 수 : 2명 ⇒ ‘나’와 ‘상대방’ 모두 채팅방 페이지에 머무르고 있음
- 채팅 전송 시 읽음으로 저장
⇒ 새로운 캐시를 도입하는 것보다 기존 사용하는 Redis를 사용하는 것이 좋겠다고 판단
2. 추후 Redis의 메세지 브로커 사용 가능성
- 현재 상황: 스프링의 Simple Message Broker 사용 중
- 내부 메모리 기반: 스프링 서버의 내부 메모리에서 동작 ⇒ 서버가 다운되면 메시지 브로커의 데이터가 유실될 수 있음
- 다중 서버 환경의 제약: 다중 서버 환경에서는 서버 간 채팅방을 공유할 수 없음 ⇒ 다른 서버의 사용자와 채팅이 불가능함!
- Redis의 메시지 브로커
- Pub/Sub 기능 활용: Redis가 제공하는 Pub/Sub 기능을 통해 메시지 브로커로 사용 가능
- 실시간 데이터 처리: 메시지 전송 후 삭제되기 때문에 실시간 데이터 처리에는 적합하지만, 메시지가 유실될 수 있는 위험 존재
- 분산 서버 환경 지원: 여러 서버에 Redis 인스턴스를 배포함으로써 서버 간 채팅방을 공유할 수 있음 ⇒ 다른 서버의 사용자와도 채팅이 가능함!
⇒ 추후 scale-out 하게 된다면 Redis의 메세지 브로커 사용을 고려하고 있기에 현재 상황에서 Redis를 캐시로 사용하는 것이 좋겠다고 판단
2-4. 현재 프로젝트에 적용
2-4-1. 채팅 전송 (캐싱 + 상태 관리)
- 캐싱 (임시 저장)
- 서버에서 메세지 처리를 완료하고 (기존: DB에 저장) Redis에 임시 저장함
- DB에 저장
- Redis에 저장된 해당 채팅방의 웹 소켓 연결 인원이 0명이 되는 순간 (상태 관리) Redis에 임시 저장한 채팅들을 DB로 영구 저장함
더보기
웹 소켓 연결 인원이 0명이 되는 순간 : “해당 채팅방을 현재 사용중인 유저가 없다는 것!”
⇒ 따라서 비교적 시간이 오래걸리는 DB를 거치는 작업을 할 여유가 생겼다는 것으로 해석!
2-4-2. 채팅 조회
현재 무한 스크롤이 구현되어 있는 상황 ⇒ 두 가지 조회 상황 존재
1. 첫 페이지 조회
- 채팅방에 입장했을 때
- 채팅이 진행중 일 때: 생성된 채팅이 추가되면 첫 페이지가 로드 되어야 함 (채팅 생성일자 기준 내림차순 정렬이므로 최신 채팅은 첫 페이지에 존재하므로)
2. 이후 페이지 조회
- 스크롤을 올려서 현재 로드된 채팅 목록 이후의 페이지를 조회할 때
1. 첫 페이지 조회
- 위에서 도입한 채팅 전송 프로세스에 따라 Redis에 임시 저장되어 있는 모든 채팅을 조회함
2. 이후 페이지 조회 (혹은 첫 페이지 조회 시 Redis에 채팅이 존재하지 않는 경우)
- Redis에 현재 존재하는 모든 채팅을 이미 로드했기 때문에 이후의 채팅은 모두 DB에 저장되어 있음
3. 결과 검증
3-1. 채팅 전송 메서드 검증
- 서비스 단의 채팅 전송 메서드 전체의 소요 시간을 측정함 ⇒ 서버의 메세지 처리 + 메세지를 저장하는데 걸리는 시간 측정
기존 방법 대비 약 41% 성능 개선 확인!
바로 DB에 저장하는 기존 방법 | Redis에 저장하는 개선 방법 | |
소요 시간 | 3회 측정: 평균 약 54ms 소요 |
3회 측정: 평균 약 32ms 소요 |
3-2. (채팅 전송 시) 메세지 저장 시간만 검증
- 위의 방법대로 측정 시 유저 검증, 채팅방 검증 등의 로직이 포함
- 따라서 저장 시간만을 비교하는데에는 다소 무리가 있다고 판단
- 서비스 단의 채팅 전송 메서드에서 DB 혹은 Redis 에 저장하는 코드의 실행 시간 측정
기존 방법 대비 약 53.4% 성능 개선 확인!
바로 DB에 저장하는 기존 방법 | Redis에 저장하는 개선 방법 | |
소요 시간 | 3회 측정: 평균 약 10.1ms 소요 |
3회 측정: 평균 약 4.7ms 소요 |
3-3. 채팅 조회 검증
- 현재 한 페이지 당 채팅 개수를 20개로 설정함
- 만약 Redis에 저장되어 있는 채팅 개수가 20개보다 현저히 많다면 성능 개선 효과가 미비할 수 있지만 측정은 20개를 기준으로 해봄
기존 방법 대비 약 65.2% 성능 개선 확인!
DB에서 가장 최근 20개 채팅 조회하는 기존 방법 | Redis에서 가장 최근 20개 채팅 조회하는 개선 방법 | |
소요 시간 | 약 13.5ms 소요 |
약 4.7ms 소요 |
4. 정리 및 개선사항
- 채팅에서 일반적으로 사용하는 성능 개선 방식은 아님
- 그러나 성능 개선의 효과는 확실히 존재한다고 판단
- 일반적으로 사용할 수 있는 개선 방식 중 하나인 읽기/쓰기가 빠른 DB를 도입하는 방법에 대해서도 시도해 볼 예정