웹소켓 없이 만드는 실시간 채팅 구현기: HTTP API와 FCM을 활용한 최적화 전략

🔥🔥🔥·2026년 1월 28일

신규 서비스인 '마켓 서비스'를 개발하며 채팅 기능 구현을 담당하게 됐다. 채팅이라 하면 보통 웹소켓(WebSocket)을 활용한 양방향 통신을 먼저 떠올리게 마련이다. 하지만 우리 서비스는 서비스 극초기 단계였고, 백엔드 리소스의 효율적인 활용과 빠른 MVP 출시가 최우선이었다.

결국 팀과의 논의 끝에 웹소켓 대신 HTTP API 기반의 채팅 방식을 선택했다. 클라이언트 개발자로서 나의 숙제는 명확했다. '어떻게 하면 제한된 자원 안에서 사용자에게 실시간에 가까운 채팅 경험을 제공할 것인가?' 이 고민의 과정을 기록해 보려 한다.


1. 웹소켓 대신 FCM을 활용한 실시간성 확보

가장 먼저 고려한 것은 기획의 요구사항이었다.

  1. 새로운 메시지가 오면 Push를 통해 알림을 받는다.
  2. 사용자가 현재 해당 채팅방에 접속 중일 때는 Push 노출을 하지 않는다.

처음에는 주기적으로 데이터를 확인하는 폴링(Polling) 기법도 고려했지만, 무의미한 API 호출이 반복되어 서버와 클라이언트 모두에게 부하를 줄 것 같았다. 그래서 나는 Push 알림을 Trigger로 활용하기로 했다. 즉, FCM 리스너가 메시지를 감지하면 그 시점에 채팅 메시지 업데이트 API를 호출하는 방식이다.

[Notification 대신 Data 메시지 활용]

Android의 경우 사용자가 시스템 알림은 거부했지만 앱 내에서 알람을 허용한 경우, Notification 페이로드 대신 Data 페이로드에 메시지 정보를 담아 전달하도록 설계했다. 이렇게 하면 시스템 수준의 알림 팝업은 띄우지 않으면서도, 앱 내 리스너가 이를 감지해 즉각적인 API 호출이나 UI 업데이트를 수행할 수 있다.

[화면 감지를 통한 스마트 업데이트 (Foreground Listener)]

단순히 Push를 받는 것에 그치지 않고, 사용자가 현재 어느 화면에 머물고 있는지에 따라 동작을 다르게 설계했다.

  1. 채팅방 외부일 때: FCM 리스너가 감지되면 상단 알림을 통해 새로운 메시지가 왔음을 알린다. (이때 위에서 언급한 Data 페이로드를 활용해 앱 내 커스텀 알림을 띄운다.)
  2. 해당 채팅방 내부일 때: 리스너가 작동할 때 현재 활성화된 화면(Route)을 체크하도록 로직을 짰다. 만약 보고 있는 채팅방 ID와 들어온 메시지의 ID가 일치한다면, 알림을 띄우는 대신 즉시 메시지 업데이트 API를 호출한다.

이 방식을 통해 웹소켓의 onMessage 이벤트와 유사한 효과를 낼 수 있었다. 사용자는 별도의 액션을 취하지 않아도, 채팅방에 가만히 앉아서 실시간으로 대화가 업데이트되는 경험을 하게 된다.

[OS별 대응과 예외 처리의 늪]

개발 과정에서 안드로이드와 iOS의 운영체제 특성 차이로 인한 변수가 발생했다.

  • Android: 시스템 알람이 꺼져 있어도 FCM 데이터 메시지를 수신할 수 있어 의도한 대로 동작했다.
  • iOS: 시스템 알람 설정이 꺼져 있으면 백그라운드나 종료 상태에서 데이터 메시지를 안정적으로 수신하기 어려웠다. iOS 운영체제 특성상 발생하는 제약이었다.

이 지점에서 팀장님과 상의했다. "iOS의 경우에만 폴링을 적용해야 할까?"라는 의견도 있었지만, 최종적으로는 '사용자가 메시지를 보낼 때 미수신 메시지를 동기화' 하는 방식을 보완책으로 택했다. 채팅이 주력인 서비스가 아니라는 점과 현재의 개발 리소스를 고려했을 때 가장 합리적인 트레이드오프였다고 생각한다.


2. SQLite를 활용한 로컬 데이터베이스 구축 및 메시지 관리

매번 채팅방에 진입할 때마다 전체 메시지 목록을 서버에서 불러오는 것은 비효율적이다. 이를 해결하기 위해 SQLite를 활용해 앱 내 로컬 DB를 구축했다.

  • 사용자가 채팅방에 진입하면 로컬에 저장된 마지막 메시지의 id를 기준으로 그 이후의 최신 데이터만 서버에 요청한다.
  • 장점: 서버 부하를 획기적으로 줄일 수 있고, 네트워크 연결이 불안정한 상황에서도 이전 대화 내용을 즉시 보여줄 수 있어 사용자 경험(UX) 측면에서 훨씬 유리하다. 자연스럽게 Pagination 처리도 매끄러워졌다.

3. 사용자 경험을 위한 낙관적 UI(Optimistic UI) 업데이트

채팅은 사용자가 가장 민감하게 '속도감'을 느끼는 기능이다. 전국민이 메신저 앱을 사용하는 만큼 눈높이도 높다. API 기반 채팅의 한계를 극복하기 위해 낙관적 UI(Optimistic UI)를 적용했다.

[개선 전: 정직하지만 답답한 UX]

초기에는 메시지를 보내면 서버의 응답이 올 때까지 로딩 바를 보여주거나 대기하는 방식이었다.

  • 문제점: 서버 통신 때마다 발생하는 딜레이, 메시지 전송 후 키보드가 사라지는 현상 등 흐름이 툭툭 끊겼다.

[개선 후: 속도감 있는 채팅]

기획자, 디자이너분들께 개선 아이디어를 제안했고, 긍정적인 피드백을 받아 다음과 같이 수정했다.

  1. 즉각적인 UI 반영: 사용자가 '전송'을 누르는 즉시 로컬 DB의 임시 ID를 사용해 화면에 메시지를 띄운다.
  2. 데이터 동기화: API 통신이 성공하면 서버에서 받은 실제 데이터로 해당 메시지를 치환한다.
  3. 키보드 포커스 유지: 연달아 메시지를 보내는 사용자 패턴을 고려해 전송 후에도 키보드가 유지되도록 처리했다.

결과적으로 사용자는 네트워크 지연을 거의 느끼지 못하며 대화를 이어갈 수 있게 되었다. 이번 프로젝트를 통해 낙관적 UI가 실질적인 사용자 만족도에 얼마나 큰 영향을 주는지 체감할 수 있었다.


마치며

처음 웹소켓 없이 채팅을 구현해야 한다는 과제를 받았을 때는 고민이 많았다. 하지만 주어진 환경 안에서 최선의 기술적 선택을 고민하고, FCM과 로컬 DB, 낙관적 UI를 조합해 문제를 해결해 나가는 과정이 정말 즐거웠다. 기술적 제약이 오히려 창의적인 해결책을 고민하게 만드는 좋은 원동력이 된 것 같다.

관련 포스팅:

0개의 댓글