홈으로 돌아가기

Titanium: Erlang 기반 메신저 백엔드

이 글은 맞춤 메신저를 위한 Titanium 백엔드의 진화를 해부합니다: XMPP/FastAPI를 버리고 Erlang/OTP 선택. pts-synchronization, sharding, 최대 5k RPS 벤치마크 설명. 시니어 개발자 대상.

Erlang으로 확장 가능한 메신저 백엔드 구축 방법
Advertisement 728x90

Titanium: 커스텀 메신저를 위한 확장형 백엔드 코어 — 프로토타입에서 프로덕션까지

커스텀 메신저를 개발하는 개발자들은 보통 XMPP/ejabberdMatrix/Synapse부터 시작합니다. 이 플랫폼들은 즉시 사용 가능한 전송 계층, 페더레이션, E2E 암호화를 제공하죠. 하지만 현대적인 UX에 맞게 커스터마이징하려면 벽에 부딪히게 됩니다.

Erlang 기반 ejabberd와 함께 사용하는 XMPP는 컴팩트하고 부하에 강하지만:

  • XML 스탠자(stanza)로 인해 파싱 오버헤드가 발생합니다.
  • MUC 룸은 참여자 증가에 따라 선형적으로 확장되지 않아 CPU가 급증합니다.
  • 부분적인 XEP 확장으로 비즈니스 로직에서 레이스 컨디션이 발생합니다.

Matrix는 강력하지만 계층화된 아키텍처 때문에 변경이 까다롭습니다. Python 기반 Synapse는 실시간 작업에서 GIL(Global Interpreter Lock)로 고전하죠.

Google AdInline article slot

하이브리드 설정—예를 들어 전송용 ejabberd + API/OTP/반응용 FastAPI—은 상태 분산(split-brain), XML↔JSON 변환, 추가 큐를 초래합니다. 단일 진실 원천(single source of truth)이 컴포넌트 간에 모호해집니다.

핵심 요약: 상태와 성능을 완벽히 제어하려면 처음부터 백엔드를 구축하는 게 더 간단합니다.

Titanium v1: FastAPI 부하 테스트

첫 번째 Titanium 버전은 WebSocket을 지원하는 FastAPI로 구현되었으며, 채팅, UIN, 미디어, pts-동기화 등의 핵심 기능을 담당했습니다. 프로토타입은 1000명 사용자 시나리오에서 120 msg/s, 40 media/min, 7% 연결 끊김 속도에서 500 RPS를 유지했습니다.

Google AdInline article slot

RPS는 전체 경로에서 측정: 송신 → 전달 → ACK → 동기화. 여기 포함:

  • 접근 권한 검증.
  • pts 업데이트 (델타 동기화를 위한 메시지 포인트).
  • 실시간 WebSocket 푸시.
  • 불안정한 네트워크에서의 재시도.
  • 오프라인 클라이언트용 업데이트 로그.

스케일업에서 문제 발생:

  • GIL로 WebSocket 동시성 제한.
  • pts 테이블 고부하 시 PostgreSQL 락업.
  • 미디어 저장소에 영구 링크 대신 임시 링크 필요.

Titanium 아키텍처: 진실이 살아있는 곳

Titanium은 엄격한 상태 관리 규칙을 정의합니다:

Google AdInline article slot
  • 서버가 진실의 원천: 클라이언트를 절대 신뢰하지 말고, 모든 변경은 API를 통해.
  • Pts 메커니즘: 메시지 포인트를 통한 델타 동기화, Telegram 스타일.
  • 업데이트 로그: 상태 재생을 위한 불변 로그.
  • 분산 상태: UIN별 샤딩, sticky 세션 없음.
  • 미디어: S3 스타일 임시 서명, 24시간 TTL.

주요 컴포넌트:

  • Gateway: WebSocket + HTTP/2, 속도 제한, JWT.
  • Core: 비즈니스 로직, pts, ACL (UIN/그룹별).
  • Storage: PostgreSQL (상태) + Redis (세션/큐) + S3 (미디어).
  • Workers: Celery/RQ로 비동기 작업 (미디어 처리, 봇).

Pts-동기화 예시 (의사코드):

% 클라이언트가 델타 요청
handle_get_updates(ReqPts) ->
  Updates = db:fetch_updates_after(ReqPts, UserId),
  NewPts = lists:max([U#update.pts || U <- Updates]),
  {reply, #{updates => Updates, pts => NewPts}}.

Erlang/OTP로 전환: 확장성과 탄력성

Python은 10k 동시 WebSocket을 감당하지 못했습니다. Erlang/OTP가 해결:

  • 액터 모델: 각 UIN에 gen_server를 포함한 슈퍼바이저 트리.
  • 제로 다운타임 VM 핫-리로드.
  • 저지연 저장소로 내장 Mnesia/ETS.
  • 노드당 100만+ 연결 WebSocket 확장을 위한 Cowboy.

벤치마크:

| 시나리오 | FastAPI (Python) | Titanium (Erlang) |

|----------|-------------------|--------------------|

| 1k 사용자, 120 msg/s | 500 RPS | 5k RPS |

| 10k WS | 20% CPU | 5% CPU |

| 미디어 100/min | 2s 지연 | 200ms |

Gen_server 세션 예시:

-module(session).
-export([start_link/1, handle_message/2]).

handle_call({send_message, Msg}, _From, State) ->
  case validate_rights(Msg) of
    ok ->
      NewPts = State#state.pts + 1,
      save_update(Msg#update{pts=NewPts}),
      broadcast_to_peers(Msg),
      {reply, ok, State#state{pts=NewPts}};
    {error, Reason} -> {reply, {error, Reason}, State}
  end.

그룹 채팅과 엣지 케이스

그룹 채팅은 난제입니다. MUC를 버리고:

  • 샤딩된 룸: UIN 리스트 해시로 리더 선정, 팔로워가 델타 복제.
  • Presence: 온라인/오프라인 ETS, 재연결 시 pubsub.
  • 악조건 네트워크: 낙관적 송신 + 서버 ACK, 로그로 갭 채움.

처리된 케이스:

  • 중복 송신 (msg_id로 멱등성).
  • 패킷 순서 변경 (타임스탬프 + 시퀀스).
  • 슬립/웨이크업 탭 (롱폴링 폴백).
  • 재시도 봇 웹훅 (데드 레터 큐).

핵심 요약

  • Titanium은 MTProto 클론이 아닙니다—pts, 로그, 샤딩 중심의 가벼운 실시간 스택입니다.
  • Erlang/OTP은 Python 대비 10배 RPS, CPU 1/4로 제공.
  • 서버 진실 원칙으로 클라이언트 불일치 방지.
  • 미디어/봇은 비동기 오프로드; 코어는 전달에 집중.
  • 스케일: 3노드로 50k 사용자, 1k msg/s (페더레이션 없이).

— Editorial Team

Advertisement 728x90

다음 읽기