채팅 기능의 logic은 테스트 해보았고, 우리 팀원들과 함께 테스트했을 때는 만족스러운 결과가 나왔다.
현재는 채팅 서버가 따로 분리되어있지도 않고, ec2 사양도 프리티어로 돌고있다.
현재 상황에서 얼마나 많은 채팅 connection을 버틸 수 있는지 확인해보려고 한다.
테스트는 JMeter를 활용해서 진행하려고 한다. JMeter를 활용하여 웹소켓을 테스트하려면
JMeter 설치 -> plugins manager 설치 -> websocket sampler 설치 해야한다.
자료가 많아 참고하여 설치하였다.
1. 로컬 환경에서 JMeter 연습해보기
테스트 생성 -> 쓰레드그룹 생성 -> 쓰레드 수 설정 -> 설정과 sampler들 등록




대략 이런식으로 설정하였다.
프론트에서 SockJS로 웹소켓 연결을 한다고 하는데, 이 경우 SockJS 연결시에는 헤더를 보낼 수 없기 때문에 SockJS 연결 직후에 오는 STOMP CONNECT 메시지 헤더에 JWT 토큰을 담아주게 된다. 따라서, 이 상황을 구현하려면 STOMP 메시지 프로토콜에 대한 기본적인 이해가 필요했다.

이와같이 클라이언트에서 보내는 STOMP 메시지의 형식을 갖추어야한다.


또한, CONNECT와 Websocket 커넥션같이 응답이 오는 경우에는 그 뒤에 websocket read sampler를 달아주어야 응답 latency를 알 수 있으며, DISCONNECT시 밀려있던 다른 프레임의 메시지를 받아 오류가 나는 경우를 방지할 수 있다.


websocket connection은 평균 8ms, STOMP connection은 평균 13ms가 나왔다.

1000명의 사용자가 connect / subscribe / disconnect 하는 경우, 에러없이 처리가 되긴 하지만 평균 latency가 어느정도 늘어난 것을 확인할 수 있다. 현재는 연습을 위해 일부 로직(사용자가 입장 시 lastMessageId를 클라이언트에 전달하는부분)을 주석처리 해둔 상태임에도 처리시간이 늘어나고 있다.


에러가 나기 시작했다.

확인결과, STOMP CONNECT 요청 후에 응답으로 CONNECTED 프레임을 받는 것이 timeout나면서 버퍼에 쌓여 close시에 DISCONNECT 프레임이 아닌 CONNECTED 프레임을 받아 에러가 나는 것이었다.
2.Baseline 설정 / 서버 테스트
위에서 설정한 쓰레드 수가 1000이라고 해서 서버가 1000개의 socket connection을 동시에 유지한다고 보기는 어렵다. ramp-up 시간동안 쓰레드가 생성될 때, 기존에 생성된 쓰레드는 disconnect 요청을 마치고 connection을 반환했을 것이기 때문이다. 따라서, ramp-up period는 길게 유지하되 subscribe 이후에 send 요청에 루프를 걸고, think-time을 주어서 유저가 10초에 한번씩 지속적으로 채팅을 전송하는 상황을 재현하였다. 루프는 6 이상을 주어 하나의 스레드가 ramp-up time보다 길게 연결을 지속하게 하였다.
<테스트환경 정리>
thread : 1000
ramp-up : 60
scenario : connect -> subscribe -> loop (send + 10 sec think time) * 20 -> unsub -> disconnect
timeout : 6000ms (default)
로컬에서는 무난히 버텨주었다.


ec2 인스턴스는 약 400개의 CONNECTION부터 에러가 나기 시작했다.

그냥 CPU 문제였다.. 병목이고 나발이고 EC2 프리티어는 이렇게 빠르게 들어오는 요청을 감당할 수 없었던 것이다.
요청이 너무 빠르게 들어오면 못버티는 것 같다. 현재는 짧은 시간 안에 많은 connection을 만드는 것 보다,
최대 몇 개의 connection을 유지하고 채팅기능이 작동하는지가 관심사기 때문에 ramp-up 시간을 늘리고 쓰레드수를 줄여보았다.


마찬가지로 정확히 thread 수가 370을 넘어가면서 connection 에러가 나기 시작했다.
이번엔 CPU가 튄것도 아니고, 메모리 부족도 아닌데 이유가 궁금했다.
여기저기서 주워들은 것들로 3가지 가설을 세웠다.
1. chat을 publish하기 전에 db에 저장하는 부분에서 병목이 생기고, 이로인한 connection timeout
==> db 혹은 redis가 병목이었으면 로컬에서도 마찬가지였어야함
2. connect, subscribe, send, unsubscribe 모두 redis를 사용하기 때문에 redis에서의 병목
==> db 혹은 redis가 병목이었으면 로컬에서도 마찬가지였어야함
3. 우분투 file descriptor 제한 문제(이건 지금 당장은 아닌 것 같다)
==> 로컬과 서버의 ulimit file open 설정이 1024로 동일하고, 이 설정을 올렸을 때도 서버 테스트 결과가 같았다.
4. tomcat의 connection 제한이나 jvm 메모리 문제
이것들을 모두 검증해보기엔 내가 사용할줄 아는 모니터링 툴이 제한적이고, 탐색범위가 너무 넓기 때문에 일단은 지금 서버가 감당할 수 있는 채팅 유저를 300으로 생각하고 테스트를 마무리 하려고 한다. 먼저 redis에 호출이 과하게 많은 부분을 개선하고, 외부 message broker를 사용해 채팅 서버를 분산해본 뒤 다시 한번 테스트 해봐야겠다.
3.결론 & 느낀점
결론적으로 현재 moyiza 서비스의 ec2 서버는 약 300명정도가 채팅서버에 연결되어 채팅을 주고받을수 있는 상황이다.
경험이 없어 ec2 프리티어 스펙에 어느정도를 목표로 잡아야할지 모르는 것이 너무 안타깝다. 로컬과 ec2 차이가 cpu, 메모리밖에 없는데 도대체 왜 ec2 CPU와 메모리가 놀고있는 상황에도 차이가 나는지 알 수가 없다.
느낀점
1. 아무것도 모르는 상태에서 테스트하는 것이 정말 쉽지 않다. Jmeter 사용법 알아내서 익히면 websocket 테스트하는법 배워야하고, connection 이후에 구독, 전송 등 STOMP 프로토콜에 specific하게 테스트 시나리오 작성해야하고, 새로운 것을 사용하는건 항상 어려운 일인 것 같다.
2. 적절한 모니터링 툴을 잘 사용하는 것이 중요하다. 테스트 시나리오를 짜서 테스트를 했는데, 막상 일정 load 수치에서 장애가 나도 어느 부분을 개선해야할지 막막하다. 알아야할 것이 굉장히 많고, 어디를 봐야할지 몰라서 이것저것 툴을 설치해도 정확하게 파악하기가 어렵다.
비슷한 상황인 것 같은데 답변이 없다 ,,,,,
++추가


WebSocket Connection은 잘 맺어지는데 STOMP Connection이 먼저 망가지는 것을 보고 자바 어플리케이션에 할당된 메모리가 차서 그런 것인가 싶어 -Xmx 옵션을 통해 로컬에서 실행하는 어플리케이션의 최대 힙 메모리를 제한하고 실행해도 결과는 바뀌지 않았다.. 로컬에서 바라보는 redis와 db를 개발서버와 동일하게 맞춰주어도 로컬은 문제 없는 것을 보면 저장소 쪽의 병목은 아닌 것 같은데 도저히 이유를 모르겠다. 문제 상황이 재현이 안되니까 너무 슬프다....
삽질한 김에 명령어 정리 : java 실행 명령어 -Xmx512m ==> JVM 최대 힙 메모리 설정, -Xms는 최소
jstat ==> jvm 메모리관련, gc관련 모니터링 제공 jstat -gcutils {PID} -h20 10000 : 10초마다 찍어주고 20줄마다 헤더출력
gccapacity 등 여러가지 옵션 있음
ec2 메모리 모니터링은 https://vlee.kr/4958 참고해 cloudwatch agent 설치해서 진행했고,
spring actuator 사용하면 threaddump, heapdump 뜰 수 있음 -> fastthread.io에서 쓰레드덤프 분석 쉽게 가능
'공부 > Project' 카테고리의 다른 글
Moyiza - 채팅 서버 구현 (2) : redis 도입 / 읽은사람 카운트하기 (0) | 2023.06.22 |
---|---|
Moyiza - 채팅 서버 구현 (1) : STOMP / SimpleBroker (0) | 2023.06.21 |