Search

[YOUngChat] 사용할 수록 젊어지는, 실시간 채팅 서비스!

깃허브 주소 - 백엔드
서비스 주소
프로젝트 기간
2024/01/04 → 2024/02/08
프로젝트 최종 발표 영상
프로젝트 자료
YOUng Chat! 단순한 기능과 한 눈에 들어오는 UI로 남녀노소 누구나 쉽게 사용할 수 있는 실시간 채팅 서비스 입니다. 서비스 로직보다는 기술적인 도전을 더 중점적으로 진행하였습니다.
YOUngChat 바로 가기

 아키텍처

 사용 기술

BE

FE

DB

Infra

 주요 기술

RabbitMQ & STOMP
실시간 채팅
1:1 채팅 구현
단체 채팅 구현
채팅 알림
Message Broker인 RabbitMQ를 이용한 채팅 알림 서비스 구현
JMeter
부하테스트
JMeter를 이용한 부하테스트 진행
QueryDSL과 Pagination 처리를 통한 성능 개선의 근거로 활용
Spring Security & JWT & Redis
이메일 인증 및 회원 가입
회원 가입 시 이메일 인증 진행
Redis에 Authentication Code, User 정보 저장
인증 진행 후 삭제하여 재접근 제한
인증/인가
JWT를 이용한 AccessToken과 RefreshToken 발급 및 로그인 처리
Token 방식의 회수 불가능한 단점 보완을 위한 Redis TTL 방식 로그아웃 처리

 기술적 의사 결정

Cursor-based Pagination 도입
도입 배경
실시간 채팅의 경우 잦은 생성, 수정, 삭제가 반복되기 때문에 Offset 기반으로 할 경우 중복 데이터 처리 발생 우려 존재
Offset-based Pagination의 경우, 10억 번째 페이지에 있는 값을 찾고 싶다면 Offset에 매우 큰 숫자가 입력돼 쿼리 퍼포먼스 이슈 발생
따라서, Cursor-based Pagination 도입 고려
부하 테스트 진행
동시 사용자 1000명을 가정하고, 채팅의 개수는 2000개로 생성하여 부하 테스트 진행
페이징 처리 전 평균 속도 : 6280ms TPS(초당 데이터 처리량) : 49.3/sec
QueryDSL 기반 페이징 처리 후 평균 속도 : 1245ms TPS(초당 데이터 처리량) : 189.7/sec
결과
평균 속도 → 약 500% 성능 개선
TPS → 약 380% 성능 개선
부하/성능 테스트 툴 - Jmeter
도입 배경
서비스에 대량 트래픽을 발생시킬 수 있는 부하 테스트 툴 필요
의견 조율
부하 테스트 툴 중
NGrinder / JMeter/ K6 중 고민
의견 결정
NGrinder 지원 버전 문제
공식 홈페이지를 확인해본 결과 마지막 버전인 3.5.8까지 Java 17을 지원한다는 변경 사항이 없어 후보군에서 제외.
GUI 지원 여부
초기 진입 장벽이 낮으며, 시각적인 이해를 얻기 위해 GUI를 제공 하는 도구인 Jmeter를 선택 (K6는 CLI 기반 부하테스트 도구)
Polling/ WebSocket
도입 배경
채팅 서비스의 통신 방식으로 [HTTP-Polling, Websocket] 두 가지를
또한, WebSocket 사용 시 STOMP InMemory 문제 발생
의견 조율
서비스의 Scope를 작게 하여 두 개의 기술을 모두 접목해 이 둘의 차이점을 알아보고자 함
WebSocket 사용시 외부 메시지 브로커를 사용하기로 결정
후보는 RabbitMQ, ActiveMQ, Kafka
의견 결정
외부 메시지 브로커로 RabbitMQ를 사용하기로 결정
문제 발생 시 재시도가 가능하며, 대량의 세션 수용 가능
빠르게 구성이 가능하며, 모니터링에 용이
여러 대의 서버를 사용할 경우 데이터 정합성에 문제 발생
CI / CD
도입 배경
서비스를 개발하며 CI(지속적 통합)과 CD(지속적 배포)의 필요성 인지
코드 통합 자동화를 통해 개발 초기 단계부터 문제를 발견
배포 자동화를 통해 배포 사이클을 빠르고 효율적으로 개선하고자 함
CI/CD Tool 선택 과정
CI/CD Tool로 가장 자주 언급되는 Jenkins와 Github Actions를 비교함
Jenkins
장점
단점
• 다양한 플러그인으로 확장이 가능하여 확장성이 높음 • 관련한 큰 커뮤니티가 형성되어 있어 참고할 자료가 다수 존재함
• 설정과 관리를 직접 해야 하기 때문에 초기 설정에 많은 시간 소요 • 학습 커버리지 있음
Github Actions
장점
단점
• GitHub 저장소에 내장되어 있어, 손쉽게 CI/CD 구성 가능 • Github 내부 모든 이벤트와 연동 가능 • 학습 커버리지 적음
• 비교적 최근에 나온 기술로, 레퍼런스가 적음
Gihub Actions 선택 근거
Github과의 통합성
Github Actions는 Github 내부 모든 이벤트와 연동 가능하다는 장점이 존재함
또한 이미 프로젝트 Repository로 Github을 사용 중이었기에, 추가 연동 작업 없이 CI/CD 파이프라인을 구성하고 실행하기 수월함
사용 용이성
단기간 프로젝트인 만큼 CI/CD 환경을 빠르게 구성해야 했기에 비교적 사용자 친화적인 UI와 학습 커버리지가 적어 팀에게 적합하다고 판단함
로깅 전략
도입 배경
운영 서버에서 실행 중인 애플리케이션은 디버깅을 통한 문제 분석 불가능
서버 내의 문제 분석은 로그를 통해서만 간접적으로 관찰 가능
의견 조율
로그의 에러를 추적 전략을 어떻게 가져갈지 의견 조율
해당 에러가 어떤 서비스를 거쳐오는지 파악 불가능
의견 결정
MDC를 사용하여 traceId를 활용
xml 파일 로그 출력 포맷 설정
AOP를 활용하여 @Before, @AfrterReturning 으로 로깅 처리
@Before 에서는 MDC에 Trace ID 저장
@AfterReturning에서는 MDC 초기화

 트러블슈팅

AWS 배포 서버 중단 이슈
문제 상황
서비스를 배포 후 UserTest 진행 했으나 많은 유저가 몰리지 않았음에도 서버가 중단됨
발생 원인 분석
EC2(t2.micro) 단일 인스턴스로 배포했고 해당 인스턴스의 램은 1GB
단일 인스턴스에 Docker Compose를 이용하여 RabbitMQ, Springboot 컨테이너를 동시에 관리 하였고, Redis는 인스턴스 내부에 설치해 둔 상태
위의 환경 때문에 메모리 부족 및 CPU 사용량이 최대치로 솟아 인스턴스가 중단됨
문제 해결
단기적인 해결 방법으로 SWAP 메모리 설정을 통해 2GB SWAP메모리 확보
장기적인 해결 방법 및 대규모 트래픽 환경을 고려한 해결
Auto-Scaling 도입 :
서버의 부하 분산 처리를 위해 Scale-out을 도입
서버 하나가 다운돼도 다른 서버가 대체 할 수 있음
AWS AutoScaling을 도입해 서버 부하가 커질 때 자동으로 인스턴스가 증가하도록 하여 운영 서버 안정성 확보
LoadBalancer 도입 :
여러 대의 서버에 적절히 부하가 분산 되어 배포된 서버가 중단되지 않도록 처리
S3 Image 삭제 시 Bucket에서 찾지 못하는 이슈
문제 상황
사용자 프로필 업로드를 위해 S3 Bucket 사용
사용자가 프로필 사진을 다른 것으로 교체하거나 삭제할 경우, S3 Bucket에서도 삭제하는 로직
Bucket URL, Image file 이름까지 정확함에도 불구하고 S3 Bucket에서 찾지 못해 삭제되지 않는 이슈 발생
발생 원인 분석
기존 로직은 S3 Image를 [난수, 원본 파일명, 확장자]로 Bucket에 업로드하는 방식으로 동작
이 때, 파일명에 한글이나 특수 문자가 포함되어 있을 경우 난수로 생성되어 입력됨
객체 URL과 객체 이름이 달라 Bucket에 존재함에도 불구하고 찾지 못하는 것으로 원인 규명
원본 파일명
S3에 저장된 객체 URL
문제 해결
S3 Bucket에 Image 저장 시 원본 파일명을 제외한 [난수, 확장자]로만 구성하여 변수 제거
변경 후 S3에 업로드 된 객체 이름
변경 후 S3에 저장된 객체 URL
예상치 못한 난수 생성 없이, 지정한 난수와 확장자명으로만 저장하여 이슈 해결
로그아웃시 해당 사용자의 AccessToken 유효 기간 이슈
문제 상황
서비스를 사용하는 사용자가 로그아웃을 하였을 때 해당 AccessToken의 유효 기간이 남아있어 서버 측에서 유효한 Token으로 인식하는 문제 발생
발생 원인 분석
JWT 인증 방식은 Session 인증 방식과 달리 서버에서 관리하지 않으므로 이를 서버 측에서 처리 불가능
문제 해결
Redis의 TTL의 기능을 이용해 해당 Token의 [만료 시간 - 현재 시간]을 계산하여 Redis 서버에 저장해 이를 관리하게 구성
로그아웃을 한 사용자의 AccessToken은 BlackList 개념을 도입해 해당 AccessToken의 접근을 방지해 문제 해결

 팀원

 팀 노션
Search
이름
태그
github