신규 서비스인 '마켓 서비스'를 개발하며 채팅 기능 구현을 담당하게 됐다. 채팅이라 하면 보통 웹소켓(WebSocket)을 활용한 양방향 통신을 먼저 떠올리게 마련이다. 하지만 우리 서비스는 서비스 극초기 단계였고, 백엔드 리소스의 효율적인 활용과 빠른 MVP 출시가 최우선이었다.
결국 팀과의 논의 끝에 웹소켓 대신 HTTP API 기반의 채팅 방식을 선택했다. 클라이언트 개발자로서 나의 숙제는 명확했다. '어떻게 하면 제한된 자원 안에서 사용자에게 실시간에 가까운 채팅 경험을 제공할 것인가?' 이 고민의 과정을 기록해 보려 한다.
가장 먼저 고려한 것은 기획의 요구사항이었다.
처음에는 주기적으로 데이터를 확인하는 폴링(Polling) 기법도 고려했지만, 무의미한 API 호출이 반복되어 서버와 클라이언트 모두에게 부하를 줄 것 같았다. 그래서 나는 Push 알림을 Trigger로 활용하기로 했다. 즉, FCM 리스너가 메시지를 감지하면 그 시점에 채팅 메시지 업데이트 API를 호출하는 방식이다.
Android의 경우 사용자가 시스템 알림은 거부했지만 앱 내에서 알람을 허용한 경우, Notification 페이로드 대신 Data 페이로드에 메시지 정보를 담아 전달하도록 설계했다. 이렇게 하면 시스템 수준의 알림 팝업은 띄우지 않으면서도, 앱 내 리스너가 이를 감지해 즉각적인 API 호출이나 UI 업데이트를 수행할 수 있다.
단순히 Push를 받는 것에 그치지 않고, 사용자가 현재 어느 화면에 머물고 있는지에 따라 동작을 다르게 설계했다.
이 방식을 통해 웹소켓의 onMessage 이벤트와 유사한 효과를 낼 수 있었다. 사용자는 별도의 액션을 취하지 않아도, 채팅방에 가만히 앉아서 실시간으로 대화가 업데이트되는 경험을 하게 된다.
개발 과정에서 안드로이드와 iOS의 운영체제 특성 차이로 인한 변수가 발생했다.
이 지점에서 팀장님과 상의했다. "iOS의 경우에만 폴링을 적용해야 할까?"라는 의견도 있었지만, 최종적으로는 '사용자가 메시지를 보낼 때 미수신 메시지를 동기화' 하는 방식을 보완책으로 택했다. 채팅이 주력인 서비스가 아니라는 점과 현재의 개발 리소스를 고려했을 때 가장 합리적인 트레이드오프였다고 생각한다.
매번 채팅방에 진입할 때마다 전체 메시지 목록을 서버에서 불러오는 것은 비효율적이다. 이를 해결하기 위해 SQLite를 활용해 앱 내 로컬 DB를 구축했다.
id를 기준으로 그 이후의 최신 데이터만 서버에 요청한다.채팅은 사용자가 가장 민감하게 '속도감'을 느끼는 기능이다. 전국민이 메신저 앱을 사용하는 만큼 눈높이도 높다. API 기반 채팅의 한계를 극복하기 위해 낙관적 UI(Optimistic UI)를 적용했다.

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

기획자, 디자이너분들께 개선 아이디어를 제안했고, 긍정적인 피드백을 받아 다음과 같이 수정했다.
결과적으로 사용자는 네트워크 지연을 거의 느끼지 못하며 대화를 이어갈 수 있게 되었다. 이번 프로젝트를 통해 낙관적 UI가 실질적인 사용자 만족도에 얼마나 큰 영향을 주는지 체감할 수 있었다.
처음 웹소켓 없이 채팅을 구현해야 한다는 과제를 받았을 때는 고민이 많았다. 하지만 주어진 환경 안에서 최선의 기술적 선택을 고민하고, FCM과 로컬 DB, 낙관적 UI를 조합해 문제를 해결해 나가는 과정이 정말 즐거웠다. 기술적 제약이 오히려 창의적인 해결책을 고민하게 만드는 좋은 원동력이 된 것 같다.
관련 포스팅: