
들어가며
Deep Dive! 블로그에 각 게시물에 대한 방문자 간의 소통을 활성화할 실시간 채팅 기능을 추가하는 새로운 프로젝트를 시작했다. 프로젝트의 주된 목적은 단순히 기능을 구현하는 것을 넘어, 기존 AWS 플랫폼에서 구현한 서버리스 아키텍처와 별도로 추가적인 기술스택을 하고 싶었다. 특히 Golang, Docker, 쿠버네티스 등의 클라우드 네이티브 기술 스택을 깊이 있게 학습하고 적용하고 운영하고자 한다. 또한, AWS 이외의 플랫폼에서도 Terraform을 통한 IaC 와 GithubActions 와 Argo CD를 통해 인프라를 자동으로 관리할 계획이다.
이번 포스트에서는 프로젝트의 로컬 개발 과정의 첫 번째 기록으로, 전체 아키텍처 설계부터 실시간 통신의 기반이 될 백엔드 서버를 완성하기까지의 과정을 작성할 예정이다.
프로젝트 아키텍처 및 기술 스택
채팅 서비스는 기존 블로그 시스템과 완전히 분리된 마이크로서비스 아키텍처(MSA) 로 설계했다. 이는 각 서비스가 독립적인 기술 스택, 인프라, 배포 주기를 가질 수 있도록 하여 기술적 자율성을 극대화하기 위함이다. 최종적으로 두 서비스는 <iframe>을 통해 사용자에게 하나의 서비스처럼 보이도록 통합될 예정이다.
이번 프로젝트를 위해 선정한 핵심 기술 스택과 그 이유는 다음과 같다.
-
백엔드: Go
Go 언어의 강력한 동시성 지원 모델인 고루틴(Goroutine) 과 채널(Channel) 은 수많은 WebSocket 연결을 최소한의 리소스로 효율적으로 처리하는 데 최적화되어 있다. 실시간 통신 서버의 핵심 요구사항인 높은 동시성 처리 능력을 확보하기 위해 Go를 선택했다.
-
프론트엔드: React ( Vite )
React를 사용하여 프론트엔드 개발 속도를 확보하고, 새로운 백엔드 기술 학습에 더 많은 시간을 투자하고자 했다. 개발 도구로는 네이티브 ES 모듈 기반의 Vite를 채택하여, 매우 빠른 개발 서버 구동 속도와 최적화된 빌드 결과물을 기대했다.
-
개발 환경: pnpm Workspace 기반의 모노레포 (Monorepo)
하나의 Git 저장소 내에서 프론트엔드와 백엔드 프로젝트를 함께 관리하는 모노레포 구조를 채택했다. 패키지 매니저로는 pnpm을 사용하여 디스크 공간을 효율적으로 사용하고, 워크스페이스 기능을 통해 프로젝트 간의 의존성 관리와 스크립트 실행을 용이하게 했다.
개발 환경 구축 과정
본격적인 코드 작성에 앞서, 효율적이고 일관된 개발 환경을 구축하는 데 중점을 두었다.
- 프로젝트 초기화 및 구조 설정:
GitHub에 Public 저장소를 생성하고 로컬 환경 (WSL Ubuntu 24.04)에 복제했다.
pnpm워크스페이스를 설정하여apps/frontend와apps/backend디렉토리에서 각각의 프로젝트를 관리하는 모노레포의 기본 구조를 완성했다.
- 개발 서버 동시 실행 환경:
프론트엔드와 백엔드 개발 서버를 별개의 터미널에서 실행하는 번거로움을 없애기 위해
concurrently라이브러리를 도입했다. 루트package.json에pnpm dev스크립트를 정의하여, 단일 명령어로 Vite 개발 서버와 Go 애플리케이션을 동시에 실행할 수 있도록 구성했다. 각 프로세스의 로그는 터미널에서 이름과 색상으로 명확히 구분되도록 설정하여 가독성을 높였다.
백엔드 서버 핵심 기능 구현
개발 환경 구축 후, Go를 사용하여 실시간 채팅의 핵심 로직을 담당할 백엔드 서버 구현을 시작했다.
- WebSocket 서버 구축 및 Hub 패턴 적용:
Go의 경량 웹 프레임워크인 Gin을 사용하여 기본 HTTP 서버를 구축했다. 이후, 실시간 양방향 통신을 위해
gorilla/websocket라이브러리를 활용하여/ws엔드포인트로 들어오는 HTTP 연결을 WebSocket 연결로 업그레이드하는 핸들러를 구현했다.
여러 사용자가 참여하는 채팅방을 구현하기 위해, 모든 클라이언트의 연결을 중앙에서 관리하고 메시지를 중계하는 Hub 패턴을 적용했다. 특히, 다수의 사용자가 동시에 접속하거나 메시지를 보낼 때 발생할 수 있는 경쟁 상태 문제를 해결하기 위해 Go의 채널을 적극적으로 활용했다. 클라이언트의 등록, 해제, 메시지 브로드캐스트 요청을 각각의 채널을 통해 받아 단일 고루틴(Hub의 Run 메서드)에서 순차적으로 처리함으로써, 복잡한 잠금 없이 동시성을 안전하게 관리했다. - 상태 비저장(Stateless) 인증 API 구현 (JWT):
사용자를 식별하기 위해, 별도의 회원가입 없이 닉네임만으로 참여 가능한 익명 인증 방식을 채택했다.
POST /api/session엔드포인트를 구현하여, 클라이언트가 전송한anonymousId(브라우저 고유 식별자)와nickname정보를 페이로드(payload)로 담는JWT(JSON Web Token)를 발급하도록 했다.
이 JWT는 서버만 알고 있는 비밀 키로 디지털 서명된다. 서버는 추후 클라이언트로부터 요청을 받을 때마다 이 서명을 검증하여 토큰의 무결성을 확인한다. 이 방식은 서버가 각 사용자의 로그인 상태를 메모리에 저장할 필요가 없는 상태 비저장 아키텍처를 가능하게 하여, 향후 서버 확장에 용이한 구조를 만들었다.

3 서비스 보호를 위한 Rate Limiter 구현:
단시간에 과도한 메시지를 보내는 스팸 공격으로부터 서버를 보호하기 위해, 토큰 버킷 알고리즘 기반의 전송률 제한기능을 구현했다. Go의 golang.org/x/time/rate 패키지를 사용하여 각 클라이언트별로 Rate Limiter를 할당했다.
구현 초기에는 제한을 초과한 요청을 즉시 거절하는 Allow() 함수를 사용했으나, 이로 인해 처리되지 못한 메시지가 네트워크 버퍼에 남아 서버가 멈추는 버그를 발견했다. 이 문제를 해결하기 위해, 요청을 거절하는 대신 토큰이 채워질 때까지 실행을 대기시키는 Wait() 함수로 로직을 변경했다. 그 결과 메시지를 유실하지 않고, 서버가 자신의 처리 능력에 맞춰 안정적으로 요청을 처리할 수 있게 되었다.
다음 단계
지금까지 실시간 채팅 서비스의 두뇌와 뼈대에 해당하는 백엔드 서버의 핵심 기능을 완성했다. 다음 포스트에서는 이 백엔드와 통신하며 사용자에게 실제 채팅 화면을 보여주고, 인터랙션을 처리하는 프론트엔드 개발 과정을 기록할 예정이다.
