ACC 연합 프로젝트 회고록

‎김연수·2024년 8월 10일


ACC는 AWS Cloud Club의 약자로, aws에서 지원하는 대학생 클라우드 커뮤니티이다. 주로 학교별 활동을 하지만, 이번 방학에는 연합 프로젝트를 하였다.

프로젝트는 7/15 ~ 8/3 까지 3주 동안 진행되었고, 8/2 에는 성수 무신사 오피스에서 1박 2일 해커톤을 하였다.

1주차

  • 아키텍처 초안 및 유저 시나리오 작성

시스템 요구사항

  • 우리 팀은 채팅 시스템을 맡았고, 아래와 같은 요구사항이 제시되었다.


멘토님의 피드백은 아래와 같은 내용이었다.

  • 트래픽, 사용자 수, 컴퓨팅 노드 수
  • 트래픽 = 사용자 수 x 사용자 액세스 패턴(채팅방에 참여한다, 채팅 히스토리를 본다, 볼 컨텐츠를 고르기 위해 GET 요청) ⇒ 트래픽을 감당할 수 있는 컴퓨팅 노드 수의 선정
  • 액세스 패턴 분석을 위한 서버의 역할
    • 채팅 서버
    • API 서버
    • PubSub 서버

2주차

  • ERD / API 명세서 작성
  • 최종 아키텍처 제출

나는 채팅 서버의 메시지 수신 기능 구현을 담당하여 아래와 같이 API 명세서를 작성하였다. 양방향 통신인 Websoket 프로토콜을 사용하기로 했고, Websoket 위에서 동작하는 메시징 프로토콜인 STOMP 를 사용해 구현하기로 했다.

MESSAGE 
destination: /topic/5
message-id: d4c0d7f6-1
subscription: sub-1
{
		"roomId": "5",
		"memberId": "3jskl3k",
		"type": "TEXT",
		"comment":"Message"
		"createdBy":"user1",
		"createdAt" : "2024-07-25T10:10:00"
}^@

최종 아키텍처는 아래와 같다.

고민한 흔적들

  1. 메시지 큐잉 서비스인 Amazon MQ의 활성(active) 인스턴스와 대기(standby) 인스턴스가 비공개 서브넷에 위치해 있다. 그러나 멘토님의 피드백을 받고 이것이 오버 엔지니어링이라고 생각하여 결론적으로 MQ는 사용하지 않았다. 인프라팀에서 말씀하시기를, 인스턴스 여러 개의 분산구조를 이용하기 전에 인스턴스 한 대로도 충분한지에 대해 먼저 고려하지 않은 잘못이 있었다고 하셨다. MQ를 사용하면 scalable 한다고 해도 그 노드(인스턴스)의 개수가 적다면 굳이 MQ를 붙여야 할 이유가 없고, 오히려 시스템 복잡도가 늘어나고 indirect 한 통신으로 지연이 길어질 가능성이 있다. 그래서 우선 scalability를 버리고 단일 시스템으로 시작하기로 하였고, 이후 테스트 한 결과 요구사항인 10000명 규모에 매우 충분한 아키텍처였다고 한다.

  2. 데이터베이스와 캐싱 서비스로 RDS와 ElastiCache를 사용했다. 매칭서버에서 RDS는 영화 목록을 조회하는 용으로, ElastiCache는 6명만의 방을 만들어주기 위한 대기열 시스템 구현을 위해 이용했다. ElastiCache가 Redis/Memcached와 완전 호환되는 완전관리형 서비스여서, 사실상 Redis를 사용한 것이라고 생각하면 된다.

  3. Chat Service 와 Match Service 를 ECS Cluster 안에 두고 ECS를 사용해 컨테이너 기반의 배포를 하였다. 인프라 팀에게는 관리의 복잡성을 줄이고 많은 부분을 쉽게 자동화(저희의 경우는 배포 자동화)할 수 있으면서도, 개발팀은 익숙한 스택(스프링)을 이용해 개발할 수 있는 등의 장점이 있다. 그래서 컨테이너 기반 + ECS를 이용하는 것이 개발 팀과 인프라 팀 모두가 각자의 중요한 작업에만 집중하여 빠르게 제품을 만들 수 있는 방식이라고 생각하셨다고 한다. 다른 솔루션(EC2, Lambda 등)과의 비교는 .....!!

  1. 우리는 ALB의 경로 기반 라우팅을 사용해 경로에 따라 매칭 서비스 또는 채팅 서비스로 트래픽을 라우팅해주었다. 매칭 서비스와 채팅 서비스를 별도로 개발했기에 API Gateway를 이용하여 단일 엔드포인트를 만들 수 있었지만, 우리의 스펙에서는 ALB(Application Load Balancer)로도 충분히 구현할 수 있었다. AWS의 ALB에는 경로 기반 라우팅이라는, 경로에 따라 다른 서비스로 트래픽을 라우팅해주는 기능이 있어 API Gateway 없이 서로 다른 서비스로 적절히 라우팅할 수 있었다. 인증/인가와 같은 다른 기능들이 더 필요했다면 API Gateway를 사용했겠지만 그렇지 않았기에, 어쩌면 이것도 적정 아키텍처를 선택한 우리의 장점이라고 본다.

3주차

  • 최종 해커톤 진행

채팅 서버 구현에 다음과 같은 이슈가 있었다. 클라이언트와 Chat Server 를 어떻게 연결할 것인지 명확히 설명할 수 없어서 멘토님 말씀대로 도식화를 해보았다. 이후에 나는 메시지 수신 기능을 위한 pub/sub 구조를 제대로 이해하지 못했던 걸 깨달았다. 클라이언트 B가 채팅 서버에 메시지를 보내면(발행), 채팅 서버는 해당 채팅방(토픽)을 구독하고 있는 모든 클라이언트에게 메시지를 전달하는 것이다.

Websoket Config 설정

  1. enableSimpleBroker("/sub")
    스프링에서 제공하는 내장 브로커를 사용한다는 설정이다.
    메시지를 구독하는 요청의 prefix는 /sub 으로 시작하도록 한다. topic이라는 prefix는 메시지가 1:N 여러 명에게 송신될 때 주로 사용한다고 한다. 해당하는 경로를 subscribe 하는 client에게 메시지를 전달하는 작업을 수행한다.
    -> 규모가 더 큰 애플리케이션에서는 RabbitMQ와 같은 확장성 있는 메시지 브로커 시스템으로 선택될 수 있음.

  2. setApplicationDestinationPrefixes("/pub")
    메시지를 발행하는 요청의 prefix는 /pub 으로 시작하도록 한다. 메시지가 바로 브로커로 가는 것이 아니라, 메시지의 어떤 처리나 가공이 필요할 때 핸들러를 타게 한다. 공급자 역할을 해주고, @MessageMapping 메서드로 라우팅된다.
  1. addEndpoint("/ws")
    STOMP의 Websoket 연결 endpoint 는 /ws로 설정하였다. 개발 서버의 Websoket 접속 주소는 ws://localhost:8080/ws가 된다.

ChatController 설정


1. @MessageMapping("/chat/{roomId}"): 특정 엔드포인트(즉, "/chat/{roomId}")로 들어오는 메시지를 이 메서드가 처리하도록 매핑
2. @SendTo("/topic/{roomId}"): 메서드의 반환값이 전송될 대상 주제
3. @DestinationVariable String roomId:
@DestinationVariable은 경로 변수에서 값을 가져오는 역할. 여기서는 roomId를 채팅방 식별자로 사용.
4. @Payload ChatDto chatDto:
@Payload는 클라이언트로부터 전송된 메시지의 내용을 ChatDto 객체로 변환. ChatDto는 메시지의 데이터를 담고 있는 객체.

채팅 시스템 전체 코드는 깃허브 링크에서 확인할 수 있다.

로컬 테스트

아래는 apic 사이트에서 메시지 송신 및 수신 기능이 성공적으로 완성된 것을 로컬에서 테스트한 결과이다. 이후 Amazon ECS를 이용하여 컨테이너 기반으로 매칭/채팅 서버 애플리케이션을 배포하기로 했으므로 최종 빌드 형태를 도커 이미지로 빌드하였다.

후기

우선 아쉬운 점은 JWT Token 구현을 못한 점이다. 채팅방 생성 기능에서 JWT Token 을 이용하려고 했지만 여러 에러로 해결하지 못하고 삭제했다. 내가 맡은 기능이 아니라 해결에 도움이 되지 못했는데, 다음 기회가 있다면 이 토큰에 대해 공부하고자 한다.

피드백

우리팀은 최종 우승을 했다..! 이후에 멘토님께 우리 팀이 우승한 이유에 대해 호기심에 여쭤보았다.

0개의 댓글