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의 접근을
방지해 문제 해결
팀원
팀 노션