졸업 프로젝트를 하며 참 많은 생각이 들었다.
내가 개발자로서 대체되기 어려운 사람일까?
여러 논문들과 이에 기반한 화려한 용어들로 이해가 안되는 AI나 비전(특히 AI)들 사이에서 내가 임팩트를 어떻게 낼까.
혹은 AI를 하는 팀원들도 내가 하는 일에 어려움을 느낄까 하는 생각이 많았던 것 같다.
실제로 교수님 역시 AI 같은 논문에 기반한 연구에 더 높은 점수를 주실 것이 분명했기에, 백엔드 개발자를 희망하는 내가 Computer Scientist 라고 하려면 무엇을 해야할지 고민을 했다.
팀이 선정되고 주제 자체가 연구에 가까운 MRI 파일 기반 알츠하이머 진단 AI 서비스를 메인 피쳐로 들고 갔기에 할 수 있는 것이 많지는 않았다.
그래도 그 주제에 연결점을 찾아서 기존에 해보고 싶었던 실시간 채팅을 구현해볼 수 있었다.
우리 수업은 매주차마다 조마다 한명씩 본인의 연구(?)에 대한 발표 자료를 준비했어야 했는데, 다음은 나의 발표 내용들이다.
대부분 프로젝트의 전반적인 아키텍처와 실시간 채팅방을 설계하며 했던 고민들과 그에 대한 해결 과정을 서술했다.
1. 긴 시간의 AI 분석, 다중 AI 서버 환경
메세지 브로커 도입 -> 유저에게 즉각적인 응답 가능, Backend는 AI의 존재 여부를 모름
비동기 처리의 속도적인 강점과 메시지 큐를 활용한 모듈간 결합도 감소를 경험했다.
메시지 큐라는 도구를 처음 써봤는데 제법 적절한 상황에 적용한 것 같아 만족스러웠다.
2. 접속 중인 유저에게 실시간 레포트 생성 알림 보내기
새로운 요구사항: 실시간 알림
분석과 같이 장시간 소요되는 작업이 완료되었을 때, 웹페이지에 접속 중인 사용자는 즉시 푸시 알림을 받아야 한다는 요구사항이 추가되었다. 사용자가 웹사이트에 접속 중임에도 작업 완료 시 어떠한 피드백도 받지 못하는 것은 비효율적인 사용자 경험을 초래한다.
이를 해결하기 위해 웹소켓 기반의 양방향 연결을 도입하기로 결정했다.
단일 서버 환경에서의 동작
단일 서버 환경에서는 구현이 비교적 단순하다.
- 메시지 브로커에서 분석 완료 메시지를 수신한다.
- 서버 내 WebSocket 세션 매니저를 통해 접속 중인 사용자 세션을 조회한다.
- 해당 클라이언트로 알림 메시지를 전송한다.
이 흐름은 정상적으로 동작하지만, 서버 확장 시 문제가 발생한다. AWS Free Tier 기준, 단일 서버는 통상 5천 개에서 최대 1만 개의 세션을 유지할 수 있으므로 스케일 아웃은 언젠가 마주할 문제이다.
물론 우리 서비스에서는 절대로 마주하지 않을 것이다.
스케일 아웃 시 문제점
스케일 아웃된 환경에서는 특정 이벤트 발생 시 모든 서버가 메시지를 수신한 후, 각자 관리하는 세션 풀을 확인해야 하는 비효율이 발생한다.
서버가 5대일 경우, 중복 검사와 불필요한 브로드캐스트 비용이 5배로 증가한다. 이는 비활성 사용자나 다른 서버에 연결된 사용자를 확인하는 과정에서 리소스를 낭비하는 결과로 이어진다.
해결 방안: Redis와 동적 큐 도입
이 문제를 해결하기 위해 WebSocket 연결 시점에 연결 정보를 Redis에 저장하기로 결정했다.
- Key: userId
- Value: serverId (사용자가 연결된 서버의 고유 ID)
이 방식을 통해, 알림 메시지 처리 시 모든 서버의 세션 매니저를 조회할 필요 없이, Redis 조회 한 번으로 메시지를 수신해야 할 특정 서버를 식별할 수 있다.
또한, 각 서버는 실행될 때 자신만의 동적 큐(Dynamic Queue)를 RabbitMQ에 선언한다.
큐 이름에 serverId를 포함시켜, 메시지 발행 시 'userId → serverId' 매핑 정보를 통해 해당 서버의 큐로 직접 라우팅한다. 이 구조는 메시지 브로커의 설정 변경 없이 백엔드 서버를 유연하게 추가 및 제거할 수 있게 한다.
이러한 과정 역시 리소스 소모이다. 오히려 현 시점에선 Redis가 SPOF가 될수도 있다.
이는 적절한 기준을 잡는 것이 좋겠지만, 거의 대부분의 상황의 실시간 시스템은 유저의 접속 정보를 저장하는 편이 더 유리할 것으로 생각된다.
최종 처리 로직 흐름
- AI 분석 작업이 완료된다. 여러 서버들 중 하나가 API를 요청을 통해 최종 결과를 저장한다.
- 해당 서버가 분석 결과와 userId를 포함한 메시지를 메시지 브로커에 발행한다.
- 브로커는 Redis에서 userId에 매핑된 serverId를 조회하고, 해당 serverId의 동적 큐로 메시지를 라우팅한다.
- 메시지를 수신한 특정 서버가 WebSocket을 통해 담당 클라이언트에게 푸시 알림을 전송한다.
이 과정을 통해 사용자는 백엔드 내부 로직과 무관하게 즉시 알림을 수신할 수 있다.
백엔드 간 알림 위임
예를 들어, user1은 Backend1 서버에 접속해 있고, 분석 완료 처리는 Backend2에서 수행되었다고 가정한다.
- Backend2는 Redis 조회를 통해 'user1 → Backend1' 정보를 획득한다.
- Backend2는 Backend1의 동적 큐로 알림 메시지를 발행하여 알림 전송 책임을 위임한다.
- Backend1이 최종적으로 user1에게 푸시 알림을 담당한다.
이로써 해당 알림에 대해 모든 서버에 메시지를 브로드 캐스팅 할 필요가 사라졌다.
만약 이게 실제 서비스였고 확장성이 필요없었다면 브로드캐스팅 방식이 오히려 복잡성도 적어 관리하기 편했을 것이다.
또한 채팅과는 달리 AI 서비스의 응답은 자주있는 일이 아니기에 브로드캐스팅 비용도 적을 것이다.
(따라서 해당 아키텍처는 단순히 공부를 위해 적용한 아키텍처이다.)
'회고' 카테고리의 다른 글
@RequestBody - Json Data의 null은 왜 0 이 됐을까? (0) | 2025.01.16 |
---|---|
[회고] 우아한테크코스 프리코스 7기 1주차 (0) | 2024.10.29 |