BEYOND SW 캠프 23기 23주차 회고

이지연·2026년 4월 26일
post-thumbnail

Week 23

기간: 2026.04.20 - 2026.04.24
커리큘럼: 최종 프로젝트 (HR 인사시스템 SaaS 플랫폼) - member-service 실시간 채팅(MemberChat) 도메인 설계 및 구현


1. Facts — 무엇을 했나?

이번 주는 HR 플랫폼 안에서 구성원들이 실시간으로 메시지를 주고받는 채팅 기능을 설계하고 구현했다.

1:1·그룹·공지 채팅방과 참여자 상태 관리, 카카오톡 스타일의 읽음 처리, Redis Pub/Sub 기반 실시간 팬아웃, WebSocket 보안(STOMP 인터셉터 체인), S3 파일 첨부 및 AV 스캔 흐름까지 채팅의 전체 라이프사이클을 처음부터 끝까지 직접 만들었다.

설계 내용은 별도 기술 포스팅에서 상세히 다룰 예정이다.

  • 조직도 기반으로 채팅방 개설된 예시 이미지

2. Feelings — 어떻게 느꼈나?

  • "채팅이 쉬운 기능 아닌가?"라는 생각으로 시작했다가, 연결·저장·전파·읽음·파일·권한이 동시에 맞물려야 한다는 걸 구현하면서 바로 깨달았다. 어느 하나가 빠지면 나머지가 의미를 잃는 시스템이었다.

  • STOMP 인터셉터 순서를 뒤바꿨을 때 에러 로그 하나 없이 메시지가 조용히 사라지는 버그를 겪었다. 수십 분을 디버깅하다 코드 한 줄의 순서가 원인이었다는 걸 알았을 때, 잘못 설정하면 에러 없이 그냥 안 된다는 WebSocket 특유의 까다로움을 몸으로 배웠다.

  • Redis 패턴 구독에서 공식 문서에 없는 동작을 마주쳤다. 라이브러리를 믿되 실제 동작은 직접 확인해야 한다는 걸 다시 한 번 새겼다.


3. Findings — 무엇을 배웠나?

  • HTTP와 WebSocket은 보안 모델이 다르다. HTTP는 요청마다 토큰을 검증하지만, WebSocket은 연결 시 한 번 확정한 신원을 세션 내내 신뢰한다. 이 원칙을 모르면 클라이언트가 payload를 조작해 타인인 척 메시지를 보낼 수 있는 허점이 생긴다.

  • 수평 확장과 실시간 전파는 처음부터 함께 설계해야 한다. 단일 서버에서는 인메모리 브로커만으로 충분하지만, 서버가 여러 대가 되는 순간 각 인스턴스는 자기에게 연결된 사람만 알기 때문에 외부 허브 없이는 팬아웃이 불가능하다.

  • 단순한 제약 하나가 복잡한 문제를 대체할 수 있다. 읽음 처리에서 lastReadMessageId를 단조 증가로만 갱신한다는 조건 하나가, 복잡한 멀티디바이스 동기화 로직을 통째로 대체했다.

  • 보안은 "허용"보다 "차단"을 설계하는 게 더 효과적일 때가 있다. 파일 업로드를 막는 것보다, 스캔이 완료되어 CLEAN 상태가 된 파일에만 다운로드 URL을 발급하는 방식이 실질적으로 악성 파일의 전파를 막는다.


4. Future — 다음에 어떻게 활용할까?

  • 현재 DB 저장과 Redis 발행이 같은 트랜잭션에 묶여 있어, Redis 장애 시 메시지 저장도 함께 롤백된다. Outbox 패턴으로 저장과 발행을 분리하는 것을 다음 개선 과제로 잡았다.

  • Rate Limiter를 현재의 Redis INCR + EXPIRE 방식에서 Lua 스크립트 기반 토큰버킷으로 전환해, 창 경계의 burst 문제와 원자성 이슈를 함께 해결할 예정이다.

  • 이번 구현에서 배운 "상태와 이력의 저장 계층을 목적에 따라 분리해야 한다"는 원칙을, 앞으로 설계할 모든 도메인에 기본 원칙으로 적용하겠다.


🧩 마무리 한 줄

"채팅 시스템을 만들며 깨달은 것 — 메시지를 전송하는 건 쉽지만, 그 메시지가 모든 서버의 모든 사용자에게 빠짐없이 전달되고, 멀티디바이스에서 읽음선이 일관되게 유지되어야 비로소 '쓸 수 있는' 채팅이 된다."

profile
Eazy하게

0개의 댓글