서버 없이 1초만에 익명 채팅 매칭하기: Supabase Realtime 아키텍처
안녕하세요, 톡톡랜덤 팀입니다.
개발자라면 한 번쯤 실시간 웹소켓 서버를 어떻게 구성할 것인가에 대한 고민을 해보셨을 겁니다.
과거에는 Socket.io나 스프링의 STOMP를 활용하여 독자적인 실시간 소켓 서버를 띄우는 것이 일반적이었습니다. 하지만 작은 스타트업이나 사이드 프로젝트에서 수많은 유저의 스레드와 세션을 직접 인메모리나 Redis로 동기화하며 관리하는 것은 인프라 비용과 관리 공수를 크게 증가시킵니다.
톡톡랜덤은 이러한 무거운 백엔드 아키텍처를 과감히 버리고, Next.js 15 App Router 환경에서 Supabase Realtime이라는 서버리스(Serverless) 스택만을 활용하여 '1초 매칭 시스템'을 구축했습니다.
오늘 기술 블로그에서는 그 마법 같은 실시간 연결의 핵심 코드를 공개합니다.
1. 매칭 큐(Queue)와 Postgres Changes
보통의 매칭 시스템은 대기열(Queue) 서버가 별도로 존재합니다. 하지만 저희는 Supabase의 PostgreSQL 데이터베이스 자체를 큐로 삼았습니다.
한 명의 유저가 '채팅 시작'을 누르면 DB의 chat_rooms 테이블에 wait 상태의 방이 있는지 탐색하고, 없다면 새로 만듭니다. 여기서 핵심은 상대방이 방에 입장하여 DB 상태가 active로 변하는 순간을 어떻게 실시간으로 감지(Listen)할 것인가입니다.
서버 없이 클라이언트에서 직접 DB 변경 사항을 구독하는 postgres_changes 기능이 그 역할을 대신합니다.
// 톡톡랜덤 실제 매칭 구독 로직의 일부
const channel = supabase.channel(`room:${id}`)
.on('postgres_changes', {
event: 'UPDATE',
schema: 'public',
table: 'chat_rooms',
filter: `id=eq.${id}` // 내가 속한 방만 모니터링
}, (payload: { new: Record<string, unknown> }) => {
const updatedRoom = payload.new;
// DB 상태가 'active'로 바뀌면 즉시 채팅 화면 진입!
if (updatedRoom.status === 'active') {
setStatus('active');
setMessages([
{
id: '1',
senderId: 'system',
text: '매칭 성공! 상대방과 연결되었습니다.',
timestamp: Date.now()
}
]);
} else if (updatedRoom.status === 'failed') {
// 중간에 상대방이 이탈한 경우 처리
handleMatchEnd('DB 상태 실패 감지', true);
}
})
2. 엄청나게 빠른 메시지 전송: Broadcast
방이 성사된 이후, 텍스트 메시지나 타이핑 상태(현재 입력 중...)를 교환하기 위해 다시 DB에 읽고 쓰기를 반복한다면 심각한 병목 현상이 발생할 것입니다.
톡톡랜덤은 상태 저장이 필요 없는 실시간 이벤트(상대방이 메세지를 보냈다는 신호)를 처리하기 위해 Supabase의 Broadcast 기능을 사용합니다. 이는 Node.js 서버에 소켓을 직접 연결해 데이터를 쏘는 것과 동일한 수준의 짧은 지연시간(Low-latency)을 보장합니다.
// 실제 채팅 메시지 및 타이핑 이벤트 수신 로직
channel
// 상대방이 보낸 채팅 메시지 수신
.on('broadcast', { event: 'message' }, (payload) => {
if (payload.payload.senderId !== userId) { // 내가 보낸 게 아닐 때만
const newMessage = {
id: payload.payload.id,
senderId: 'peer',
text: payload.payload.text,
timestamp: payload.payload.timestamp,
}
setMessages(prev => [...prev, newMessage]);
}
})
// 상대방이 키보드를 타이핑 중인지 감지하는 센서
.on('broadcast', { event: 'typing' }, (payload) => {
if (payload.payload.senderId !== userId) {
setPeerIsTyping(payload.payload.isTyping);
}
})
.subscribe(); // 채널 구독 시작
위의 로직만으로도 복잡한 상태 관리나 커스텀 서버 라우팅 규칙 없이, 완벽하게 격리된 1:1 채팅방(Channel) 클라이언트 로직이 완성됩니다.
3. 서버 유지비용 $0로 극강의 효율 내기
저희는 위의 두 가지 기능(DB 변경 사항 감지와 브로드캐스트)을 적절히 혼합하여 단 한 대의 WebSocket 미들웨어 서버도 관리하지 않고 있습니다. 프론트엔드는 Vercel을 통해 정점(Edge)에 배포되고, 상태 동기화는 글로벌 분산 처리된 Supabase Realtime 노드들이 알아서 처리해 줍니다.
따라서 동시 접속자가 천 명에서 만 명으로 늘어나더라도, 저희 시스템은 병목 없이 쾌적한 1초 매칭을 유지할 수 있습니다.
단순하지만 가장 강력한 조합, Next.js + Supabase. 서버 아키텍처 고민으로 골머리를 앓고 계신 스타트업 개발자분들이 계시다면, 톡톡랜덤처럼 백엔드리스(Backendless) 실시간 인프라를 도입해 보실 것을 적극 추천합니다!