MSA 소모임 프로젝트를 고도화 하면서 알람 기능을 추가하였습니다. 이 때, sse방식도 있지만 멀티 소켓 서버운영을 경험하고 싶어서 소켓으로 알람을 구현하도록 팀원과 이야기를 하였습니다. 아래는 과정을 적어두었습니다.
1) 초기 단일 소켓서버 구성입니다.
클라이언트가 특정 커뮤니티에 가입을 하면 커뮤니티 서버에서 소켓 서버에 특정 커뮤니티에 연결되어 있는 유저 모두에게 가입 메세지를 발급합니다.
아키텍쳐 구성은 단일 소켓 서버입니다. 이 방식의 경우 커뮤니티 가입에 대한 메세지를 발급하므로 매번 커뮤니티에 가입되어 있는 유저 리스트를 탐색하여 소켓 메세지를 발급하는 방식을 사용했습니다.
2) 고도화를 진행한 멀티 소켓 서버입니다. 카프카에서 3개의 소켓서버에 커뮤니티 가입 이벤트를 전송하고, 클라이언트에 대한 부하가 공평하도록 설계가 되었습니다.
소켓 서버당 연결되는 유저의 수가 일정하게 하기 위해서 userId % 3의 값에 따라서 특정 소켓 서버에 연결되도록 설정하였고, 소켓 메세지 발행을 각 유저에게 하는 행위를 소켓서버단에서 하지 않고, 커뮤니티 멤버 서버단에서 레디스 캐시서버에 저장된 커뮤니티-유저 정보를 기반으로 소켓 메세지를 발급하도록 구현하였습니다.
또한 각 멤버에게 소켓 토픽이 하나씩 발급되므로, 미 전송 메세지 처리로직을 구현하기도 용이했습니다.
1) 초기 서버 부하테스트 방식입니다. 파이썬과 노드를 이용해서 커뮤니티에 가입해 소켓서버 부하테스트를 진행했습니다.
테스트를 진행하며 만난 오류입니다. 이 오류에 SocketException이라고 표시가 되어 있어서 소켓서버의 문제인 줄 알았지만, 알고보니 kafka template를 할당 후 반납하지 않아서 포트가 모자라게 되었을 때 발생하는 에러메세지 였습니다. 이후, 해당 코드를 수정하니 node와 python을 통한 부하테스트로는 서버에 적절한 부하를 줄 수 없어서 다른 부하 테스트 방식을 찾았습니다.
1) 두번째 서버 부하 테스트 모듈입니다. 이름은 Artillery 모듈입니다.
1) 초기에 초당 1000개의 요청을 5초동안 했습니다. 게이트웨이에 요청을 보내면 유레카가 커뮤니티 가입 서버로 요청을 보내도록 설정했습니다.
결과는 단일 소켓 서버, 멀티 서버 모두 정상적으로 동작 했습니다.
2) 부하를 가중하기 위해서 초당 3000개의 요청을 5초동안 했습니다.
서버 단에서 10000개의 정보가 정상적으로 저장되었고, 단일서버, 소켓서버 모두 10000개의 가입 메세지가 소켓의 손실 없이 정상적으로 동작했습니다. 하지만, 500에러가 계속 발생했습니다.
500에러를 찾기 위해 try, catch로 다 오류를 확인해보고, 카프카, 소켓서버, mysql 모두 단일 테스트를 진행했으나 500에러를 발견하지 못했습니다.
네트워크 문제라고 판단하여 커뮤니티 서버의 톰캣 max_thread값을 500으로 설정해주었으나 결과는 동일했습니다.
이 문제를 해결하기 위해서 하루를 투자했으나 결국 찾지 못했습니다. 그러다가 우연히 게이트웨이 서버를 확인해보니 게이트웨이 서버에서 오류가 발생하는 것을 확인할 수 있었습니다. spring cloud가 초당 3000개의 요청을 처리하지 못해서 발생한 오류였습니다.
3) spring cloud의 문제인 것을 확인했으므로 spring cloud를 제거하고 요청을 커뮤니티 서버에 직접적으로 보냈습니다. 결과적으로 일부 문제를 해결해서 소켓서버 부하테스트를 계속 진행했습니다.
500 에러가 해결 되었습니다.
이제 소켓서버의 부하를 테스트하기 위하여 초당 5000개의 요청으로 3초동안 직접적으로 커뮤니티 서버에 요청을 하였습니다. 하지만 결과적으로 소켓서버 부하테스트는 실패하였습니다.
이유는 커뮤니티 서버에서 요청의 부하를 처리하지 못했기 때문입니다. 단일 소켓서버, 멀티 소켓서버가 초당 4000개의 요청까지 무리없이 부하를 견뎌내었기 때문에 횟수를 늘려 초당 5000개의 요청을 하였지만 모두 소켓서버가 아닌 커뮤니티 서버에서 부하를 견디지 못하고 요청에 실패했습니다.
4) spring cloud 재배치, 커뮤니티 서버 증설, gateway thread 늘리기
이를 해결하기 위하여 다시 spring cloud를 연결하고, gateway의 max_thread수를 500으로 늘려주고, 커뮤니티 서버를 2개로 증설해서 유레카 서버에 등록 했습니다.
결과적으로 이번에도 게이트웨이가 부하를 견디지 못하고 일정 요청부터 500에러를 반환했습니다. 결국 소켓서버의 부하테스트는 실패했습니다.
결론적으로 단일 소켓 서버, 멀티 소켓 서버간의 부하 테스트 자체는 실패하였지만, 소켓 서버가 생각보다 부하에 강하다는 결론을 얻었습니다.
아키텍쳐 설계에 대한 중요성을 이해하여 아키텍처 구현을 위해 이틀 정도 회의를 진행하였습니다.
외부 브로커를 사용할지, 내부 브로커를 사용할지, 멀티서버를 어떻게 구현할지에 대해 많은 자료조사와 회의가 있엇습니다.
위 과정에서 소켓과 브로커, 메세지 큐에 대한 지식을 얻을 수 있어서 좋았습니다.
부하 테스트를 여러 방식으로 진행하면서 부하테스트에 대해 이해할 수 있어서 좋았습니다.
MSA방식 서버 구현시에 Spring Cloud의 한계를 경험했습니다. MSA 구현을 위한 한가지 방식으로서 Spring Cloud를 이용했지만, 서버 증설에 따른 부하 분산에 대해서도 경험해보지 못했고, 한계에 대해서만 느낀점이 많습니다. 결론적으로 MSA 구현을 할 때에는 Spring Cloud보다는 Kubernetes방식이 더 적합하다고 생각한 프로젝트였습니다. 이후에는 kubernetes로 구현해서 부하테스트를 진행해볼 계획입니다.
이기종 데이터베이스간의 일관성 문제를 해결하기 위한 kafka connect 개념에 대하여 학습했습니다. MSA 방식의 서버 구성에는 동일 기종 혹은 이기종 데이터 베이스 간의 일관성을 유지하기 위한 여러가지 개념이 있습니다.
부하테스트를 진행하기 전에는 단순히 kafka를 이용한 서버간의 EDA 방식을 통해 데이터베이스의 일관성을 유지했었고, 무리없이 일관성을 유지하는걸 볼 수 있었습니다.
하지만 이번 멀티 서버 소켓 프로젝트를 진행하며 mysql과 캐시서버 redis간의 데이터 불일치 현상을 경험했고, 이를 해결하기 위한 방법을 찾아보다가 코드레벨에서 일관성을 유지하는 것이 아닌 application단에서 일관성을 유지하는 기술을 발견했습니다.
해당 기술 중 하나가 kafka connect이고, 이 방식을 이용해서 일관성을 유지하기 위한 기술을 학습하고, 앞으로 서버에 적용해서 프로젝트를 진행하자고 팀원과 협의했습니다. 공부한 개념과 프로젝트 고도화에 대해서 다음 글 작성하겠습니다.