WebSocket을 이용한 코드 실행 요청을 처리해보자

HKLeeeee·2023년 11월 30일
0

enhancement

목록 보기
2/3

작업이 오래걸리는 요청을 어떻게 응답할까?

위 글에 이어서 팀원들의 의견과 멘토님들의 의견을 모아 소켓을 이용한 방식으로 코드 실행 요청을 처리해보기로 했습니다.

코드 실행 요청 플로우는 다음과 같습니다.

  1. 코드 실행 버튼을 클릭합니다.
  2. api 서버에 소켓이 연결됩니다.
  3. 소켓 연결이 완료되면 소켓 아이디 값을 반환 받아 http POST 요청을 보냅니다.
    이때 서버는 해당 서버에 연결된 소켓 정보를 Map 형태로 저장합니다.
    Map<SocketID, Socket>
  4. http 요청을 받으면 코드 유효성 검사를 실시합니다.
    유효성 검사에 통과하지 못하면 403에러를 통과하면 202 코드를 반환합니다.
  5. 유효성 검사를 마치고 해당 코드 실행 요청 정보를 Redis MQ에 넣습니다.
  6. running 서버에서는 Redis MQ에 job이 들어왔다는 이벤트가 발생하면 consume하여 해당 job 정보를 이용해서 코드를 실행합니다.
  7. 코드 실행이 완료되면 그 결과를 Redis에 Pub 합니다.
  8. api 서버는 Redis를 Subcribe하고 있으며, 메세지를 받으면 메세지 정보에 포함된 소켓아이디가 아까 저장해두었던 Map 구조에 있는지 확인하고, 있다면 소켓 모듈에 해당 소켓 아이디의 코드 실행 요청이 완료되었다는 이벤트를 발생시킵니다.
  9. 소켓 모듈에서 코드 실행 요청 완료 이벤트를 수신하면 Map 구조에서 소켓을 찾아 해당 소켓으로 완료 결과를 보냅니다.
  10. 클라이언트는 연결해 둔 소켓으로 코드 실행 결과를 받은 후 소켓 연결을 해제합니다.

위의 로직으로 코드 실행 기능을 구현했고, 로컬에서 잘 작동해서 배포를 했습니다.

[BE] feature: 코드 실행 기능 v3 & fix: 로그인 리다이렉션 by HKLeeeee · Pull Request #115 · boostcampwm2023/web05-AlgoITNi

이슈다 이슈

그런데 배포환경에서 이슈가 생겼습니다.

api 서버는 2코어이고 pm2로 모든 프로세스를 코어 수 만큼 실행시키고 있습니다.

위에 명시한 코드 실행 기능 프로세스는 소켓 연결과 코드실행 요청을 한 서버에, pm2 환경에서는 같은 프로세스에 되어야 적절한 응답 메세지를 줄 수 있습니다.

하지만 pm2 cluster는 기본적으로 라운드로빈 방식으로 동작하여 항상 같은 프로세스로 연결되게 보장할 수 없습니다.

실제로 pm2 클러스터를 2개 운영해서 코드 실행 테스트를 해 본 결과 소켓 연결은 0번 프로세스에 연결되었지만 http 요청은 1번 서버로 들어가는 것을 확인 할 수 있었습니다.

소켓을 이용한 코드 실행요청에 개선이 필요했고 두 가지 선택지가 있었습니다.

  1. 항상 같은 프로세스로 연결을 유지할 수 있는 방법을 찾기
  2. http 요청을 소켓 통신으로 변경하기
  1. 항상 같은 프로세스로 http 요청을 유지하기

    0번 클러스터에 소켓이 연결되었다면 http 요청도 0번 클러스터로 연결되게 만들어주는 겁니다.

    서버단위가 아닌 프로세스 단위인 것이 접근하기 어려웠습니다.

    또한 이 방법을 택하기 위해서는 시그널링 서버를 다중 소켓 환경으로 구성했던 것처럼 연결 전 어떤 서버로 연결될 지 지정하는 별도의 트래픽 관리서버가 필요할 것 입니다.

  2. http 요청을 소켓 통신으로 변경하기

    코드 실행 요청에 소켓을 도입했음에도 http 요청으로 코드 실행을 했던 이유는 코드 실행 요청이 accept 되었음을 코드 실행 결과가 나오기 이전에도 전달하기 위함이었습니다.

    202 Accepted 는 요청이 처리를 위해 수락되었으나, 아직 해당 요청에 대해 처리 중이거나 처리 시작되지 않았을 수 있다는 것을 의미합니다.
    https://developer.mozilla.org/ko/docs/Web/HTTP/Status/202

    단순히 요청이 수락되었음을 위해 Http 요청을 보내기로 결정했다면, 소켓 통신으로 방식을 변경했을때 요청이 수락됨을 표시할 수 없을까요?

    그렇지 않다고 생각합니다. 코드 실행 요청이 실행되고 MQ에 넣었다는 것을 별도의 소켓 이벤트로 만들어 클라이언트에 알려줄 수 있어 충분히 대체제로 선택할 수 있습니다.

한 가지를 결정하기 전에 2가지 선택지를 모두 시도해보았습니다.

pm2로 소켓 연결을 할 때 @socket.io/pm2 를 설치해 사용할 수 있다는 공식문서를 발견해서 시도해보았습니다.

Usage with PM2 | Socket.IO

공식문서에 설명이 짧아 정확히 이 패키지가 어떤 역할을 할 수 있는지 파악하기 어려웠습니다.

websocket을 지원하지 않는 브라우저에서 polling으로 연결할 때, 그러니까 소켓 연결 시에만 sitkcy session을 지원해주는지,
or 소켓이 연결된 이후에도 sticky session을 지원해주는지 파악하기 어려웠습니다.

전자의 경우라면 위의 패키지를 사용할 필요가 없습니다. 최신브라우저는 websocket을 지원하지 않는 경우를 찾아보기가 더 힘드니까요.

WebSocket MDN : https://developer.mozilla.org/ko/docs/Web/API/WebSocket

그런데 @socket.io/sticky github에서
this package is not needed if you only use WebSockets (which might be a sensible choice as of 2021)
이런 메세지를 찾아볼 수 있었습니다.

이 메세지를 통해 이 패키지는 전자의 경우의 소켓 연결을 돕기위해 필요한 패키지가 아닐까?라고 유추해 볼 수 있었습니다.

정확히 @socket.io/pm2 이 패키지의 역할을 아시는 분이 있다면 알려주시면 감사하겠습니다!

지원 범위를 실제로 확인하는 것은 쉽지않았습니다. NestJS에 적용하는 예제를 찾아보기 힘들어 정상 구현이 어려웠습니다ㅜㅠ

오랜 탐색 끝에 1번 방법을 구현하기 어렵다 판단해 2번 방법을 채택하기로했습니다.

2번 방법도 현재 배포환경에서는 크게 문제가 없습니다. (어떤 예상하지 못한 문제들이 발생할 수 있을까요?)

변경한 코드 실행로직은 다음과 같습니다.

  1. 코드 실행 버튼을 클릭합니다.
  2. api 서버에 소켓이 연결됩니다.
    이때 서버는 해당 서버에 연결된 소켓 정보를 Map 형태로 저장합니다.
    Map<SocketID, Socket>
  3. 소켓 연결이 완료되면 코드 실행 요청 이벤트를 보냅니다.
  4. 서버에서 이벤트를 받으면 코드 보안 검사를 실행하고 실패시 바로 소켓 응답을 주고 클라이언트에서 소켓을 종료합니다.
    유효성 검사를 통과하면 그 이후 로직은 동일합니다.

[BE] feature : 코드 실행 요청 v3 - 소켓만 이용 by HKLeeeee · Pull Request #165 · boostcampwm2023/web05-AlgoITNi

결과 확인

코드 실행 기능 테스트를 위해서 로컬 환경을 구축해두고 이용하고 있는데요,

api 서버를 2개 클러스터로 켜두고 테스트를 해보면

잘 작동하는 것을 확인할 수 있었습니다 🥳
(gif가 안올라가네요..)

이제 성능테스트만이 남았습니다!

profile
야생의 개발자!

0개의 댓글

관련 채용 정보