🏢 Discord 기술 스택 분석

24 min read
discordelixirrustreactwebrtccloudflaregcpscylladbcassandrareal-timevoicechatgamingerlang

디스코드가 전 세계 1.5억 명에게 실시간 음성/비디오 통화와 채팅을 제공하는 기술 스택 심층 분석 - Elixir, Rust, WebRTC로 구축한 차세대 커뮤니케이션 플랫폼

🏢 Discord 기술 스택 분석

게이머를 위해 시작했지만 전 세계 커뮤니티의 홈이 된 Discord의 기술 스택을 심층 분석합니다.
"Imagine a place..." - 실시간 커뮤니케이션의 미래를 만드는 기술


📊 회사 개요

서비스 규모

  • 월간 활성 사용자: 1.5억 명+
  • 일일 메시지: 40억+ 건
  • 음성 통화: 월 40억+ 분
  • 서버(커뮤니티): 1,900만+ 활성 서버
  • 동시 접속자: 피크 1,500만+

엔지니어링 조직

  • 엔지니어: 300명+ (전체 직원 700+)
  • 문화: "Build Belonging"
  • 원격 우선: 전 세계 분산 팀
  • 기술 다양성: Elixir, Rust, Python, Go

기술적 도전과제

  1. 초저지연: 전 세계 실시간 음성/비디오
  2. 대규모 동시성: 수백만 동시 연결
  3. 안정성: 게임 중 끊김 없는 통신
  4. 확장성: 급격한 성장 대응

🏗️ 아키텍처 Overview

시스템 다이어그램

┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Clients │────►│ Gateway │────►│ Guilds │ │(Desktop/Web)│ │ (Elixir) │ │ (Elixir) │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ ┌─────▼─────┐ ┌─────▼─────┐ │ │WebRTC SFU │ │ Message │ │ │ (Rust) │ │ Store │ │ └───────────┘ └───────────┘ │ │ │ └────────────────────┤ │ ┌─────▼─────┐ ┌─────▼─────┐ │ Media │ │ ScyllaDB │ │ Server │ │(Cassandra)│ └───────────┘ └───────────┘ 

핵심 설계 원칙

  1. Erlang/OTP: 고가용성과 fault tolerance
  2. Actor Model: 수백만 개의 경량 프로세스
  3. Event Sourcing: 모든 상태 변경 추적
  4. Microservices: 서비스별 최적 언어 선택
  5. Edge Computing: 글로벌 엣지 네트워크

기술 진화

  • 2015: Python 모놀리스로 시작
  • 2016: Elixir/Erlang 마이그레이션
  • 2017: Rust로 성능 최적화
  • 2019: Go 서비스 추가
  • 2021: 대규모 인프라 재설계
  • 2023: AI 기능 통합

🧩 기술 스택 상세

Backend 기술

Elixir/Erlang 코어

# Discord의 Guild (서버) GenServer defmodule Discord.Guild do use GenServer defstruct [:id, :name, :owner_id, :channels, :members, :roles] # Guild 시작 def start_link(guild_id) do GenServer.start_link(__MODULE__, guild_id, name: {:via, Registry, {Discord.GuildRegistry, guild_id}}) end # 메시지 처리 def handle_cast({:message, %{channel_id: channel_id, content: content} = message}, state) do # 권한 확인 if authorized?(message.author_id, channel_id, state) do # 메시지 저장 MessageStore.save(message) # 온라인 멤버에게 브로드캐스트 state.members |> Enum.filter(&Member.online?/1) |> Enum.each(fn member -> Gateway.push(member.session_id, {:message, message}) end) # Presence 업데이트 Presence.track(self(), "guild:#{state.id}", message.author_id, %{ status: "online", typing: false }) end {:noreply, state} end # 음성 채널 참가 def handle_call({:join_voice, channel_id, user_id}, _from, state) do case VoiceServer.allocate(state.region) do {:ok, server} -> # WebRTC 연결 정보 생성 credentials = generate_webrtc_credentials(user_id) # 음성 상태 업데이트 new_state = update_voice_state(state, user_id, channel_id, server) {:reply, {:ok, %{server: server, credentials: credentials}}, new_state} {:error, reason} -> {:reply, {:error, reason}, state} end end # 대규모 Guild 최적화 defp maybe_lazy_load_members(state) do if length(state.members) > 1000 do # 대규모 서버는 멤버 지연 로딩 %{state | members: :lazy} else state end end end 

Rust 고성능 서비스

// Discord의 음성 서버 (Rust) use tokio::net::UdpSocket; use webrtc::peer_connection::RTCPeerConnection; use std::sync::Arc; use dashmap::DashMap; pub struct VoiceServer { connections: Arc<DashMap<u64, VoiceConnection>>, mixer: Arc<AudioMixer>, config: ServerConfig, } struct VoiceConnection { user_id: u64, peer_connection: RTCPeerConnection, audio_track: Arc<dyn MediaStreamTrack>, opus_encoder: OpusEncoder, jitter_buffer: JitterBuffer, } impl VoiceServer { pub async fn new(config: ServerConfig) -> Result<Self> { let socket = UdpSocket::bind(&config.bind_address).await?; Ok(Self { connections: Arc::new(DashMap::new()), mixer: Arc::new(AudioMixer::new(config.sample_rate)), config, }) } pub async fn handle_audio_packet(&self, packet: AudioPacket) -> Result<()> { // Opus 디코딩 let pcm_data = self.decode_opus(packet.data)?; // Jitter buffer에 추가 if let Some(mut conn) = self.connections.get_mut(&packet.user_id) { conn.jitter_buffer.push(pcm_data, packet.timestamp); // 믹싱을 위한 오디오 준비 if let Some(frame) = conn.jitter_buffer.pop() { self.mixer.add_source(packet.user_id, frame); } } // 다른 참가자에게 전송 self.broadcast_mixed_audio(packet.channel_id).await?; Ok(()) } async fn broadcast_mixed_audio(&self, channel_id: u64) -> Result<()> { let participants = self.get_channel_participants(channel_id); for user_id in participants { // 각 사용자별 맞춤 믹스 (자신 제외) let mixed = self.mixer.mix_excluding(user_id); // Opus 인코딩 let encoded = self.encode_opus(mixed)?; // WebRTC DataChannel로 전송 if let Some(conn) = self.connections.get(&user_id) { conn.peer_connection .send_audio(encoded) .await?; } } Ok(()) } } // 저지연 오디오 믹서 struct AudioMixer { sources: Arc<DashMap<u64, AudioSource>>, sample_rate: u32, } impl AudioMixer { fn mix_excluding(&self, exclude_user_id: u64) -> Vec<i16> { let mut output = vec![0i32; 960]; // 20ms @ 48kHz self.sources.iter() .filter(|entry| *entry.key() != exclude_user_id) .for_each(|entry| { let source = entry.value(); for (i, &sample) in source.samples.iter().enumerate() { output[i] = output[i].saturating_add(sample as i32); } }); // 클리핑 방지 output.iter() .map(|&s| (s.max(-32768).min(32767)) as i16) .collect() } } 

Backend 기술 스택:

  • 언어: Elixir (50%), Rust (20%), Python (15%), Go (15%)
  • 프레임워크: Phoenix (Elixir), Actix (Rust), FastAPI
  • 실시간: WebSocket, WebRTC
  • 메시징: NATS, RabbitMQ
  • RPC: gRPC, Protobuf

Frontend 기술

React Desktop App

// Discord Desktop App (Electron + React) import { useEffect, useState } from "react"; import { Gateway } from "@discord/gateway"; import { VoiceConnection } from "@discord/voice"; interface ChannelViewProps { channelId: string; guildId: string; } export function ChannelView({ channelId, guildId }: ChannelViewProps) { const [messages, setMessages] = useState<Message[]>([]); const [voiceState, setVoiceState] = useState<VoiceState | null>(null); const gateway = useGateway(); useEffect(() => { // 실시간 메시지 구독 const unsubscribe = gateway.subscribe(`channel:${channelId}`, (event) => { switch (event.type) { case "MESSAGE_CREATE": setMessages((prev) => [...prev, event.message]); break; case "MESSAGE_UPDATE": setMessages((prev) => prev.map((msg) => msg.id === event.message.id ? event.message : msg ) ); break; case "TYPING_START": showTypingIndicator(event.userId); break; } }); return unsubscribe; }, [channelId]); // 음성 채널 연결 const joinVoiceChannel = async () => { const connection = new VoiceConnection({ guildId, channelId, selfDeaf: false, selfMute: false, }); // WebRTC 연결 설정 await connection.connect(); // 로컬 오디오 스트림 const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true, }, }); connection.setInputStream(stream); setVoiceState(connection); }; return ( <div className="channel-view"> <MessageList messages={messages} /> <MessageInput channelId={channelId} /> <VoiceControls connected={!!voiceState} onJoin={joinVoiceChannel} onLeave={() => voiceState?.disconnect()} /> </div> ); } // 최적화된 메시지 렌더링 const MessageList = React.memo(({ messages }: { messages: Message[] }) => { const virtualizer = useVirtualizer({ count: messages.length, getScrollElement: () => scrollRef.current, estimateSize: () => 50, overscan: 5, }); return ( <div ref={scrollRef} className="message-list"> {virtualizer.getVirtualItems().map((virtualItem) => ( <MessageItem key={messages[virtualItem.index].id} message={messages[virtualItem.index]} style={{ height: `${virtualItem.size}px`, transform: `translateY(${virtualItem.start}px)`, }} /> ))} </div> ); }); 

Frontend 기술:

  • Desktop: Electron + React
  • Web: React + TypeScript
  • Mobile: React Native
  • 상태관리: MobX, Zustand
  • UI: 자체 디자인 시스템

Data Infrastructure

메시지 저장소

# Discord의 메시지 저장 시스템 from cassandra.cluster import Cluster from cassandra.policies import DCAwareRoundRobinPolicy import msgpack import zstd class MessageStore: def __init__(self): self.cluster = Cluster( contact_points=['scylla-1', 'scylla-2', 'scylla-3'], load_balancing_policy=DCAwareRoundRobinPolicy(local_dc='us-west') ) self.session = self.cluster.connect('discord') async def save_message(self, message): # 메시지 압축 compressed = zstd.compress( msgpack.packb({ 'content': message.content, 'embeds': message.embeds, 'attachments': message.attachments, }), level=3 ) # ScyllaDB에 저장 query = """ INSERT INTO messages ( channel_id, message_id, author_id, content, timestamp, edited_timestamp ) VALUES (?, ?, ?, ?, ?, ?) USING TTL ? """ # 메시지 보관 정책에 따른 TTL ttl = self.get_message_ttl(message.channel_id) await self.session.execute_async( query, ( message.channel_id, message.id, message.author_id, compressed, message.timestamp, message.edited_timestamp, ttl ) ) # 검색 인덱스 업데이트 await self.update_search_index(message) async def get_messages(self, channel_id, before=None, limit=50): query = """ SELECT * FROM messages WHERE channel_id = ? AND message_id < ? ORDER BY message_id DESC LIMIT ? """ rows = await self.session.execute_async( query, (channel_id, before or MAX_ID, limit) ) messages = [] for row in rows: # 압축 해제 data = msgpack.unpackb( zstd.decompress(row.content) ) messages.append(Message.from_db(row, data)) return messages 

데이터 인프라:

  • 주 데이터베이스: ScyllaDB (Cassandra 호환)
  • 캐시: Redis Cluster
  • 검색: Elasticsearch
  • 분석: Google BigQuery
  • 메시지 큐: NATS, Kafka

Infrastructure & DevOps

글로벌 인프라

# Kubernetes 배포 설정 apiVersion: apps/v1 kind: StatefulSet metadata: name: discord-guild-service namespace: discord-prod spec: serviceName: guild-service replicas: 100 podManagementPolicy: Parallel selector: matchLabels: app: guild-service template: metadata: labels: app: guild-service spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - guild-service topologyKey: kubernetes.io/hostname containers: - name: guild image: discord/guild-service:v2.3.0 resources: requests: memory: "4Gi" cpu: "2" limits: memory: "8Gi" cpu: "4" env: - name: BEAM_SCHEDULER_COUNT value: "8" - name: ERL_MAX_PORTS value: "1048576" livenessProbe: httpGet: path: /health port: 4000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 4000 initialDelaySeconds: 5 periodSeconds: 5 

인프라 기술:

  • 클라우드: Google Cloud Platform + 자체 데이터센터
  • CDN: Cloudflare (글로벌 엣지)
  • 컨테이너: Kubernetes
  • 모니터링: Prometheus, Grafana, Datadog
  • CI/CD: GitLab CI, ArgoCD

💡 핵심 기술 인사이트

1. Elixir/Erlang의 힘

# Discord의 분산 시스템 설계 defmodule Discord.Presence do use Phoenix.Presence, otp_app: :discord, pubsub_server: Discord.PubSub # 수백만 사용자의 presence 추적 def track_user(guild_id, user_id, meta) do track(self(), "guild:#{guild_id}", user_id, meta) end # CRDT 기반 동기화 def handle_diff(diff, state) do # Conflict-free 상태 병합 for {user_id, %{metas: [meta | _]}} <- diff.joins do # 사용자 온라인 이벤트 broadcast_user_presence(user_id, :online, meta) end for {user_id, _} <- diff.leaves do # 사용자 오프라인 이벤트 broadcast_user_presence(user_id, :offline, %{}) end {:ok, state} end end 

2. WebRTC 최적화

// 커스텀 WebRTC SFU (Selective Forwarding Unit) pub struct DiscordSFU { router: MediaRouter, bandwidth_estimator: BandwidthEstimator, quality_controller: QualityController, } impl DiscordSFU { pub async fn handle_rtp_packet(&self, packet: RtpPacket) -> Result<()> { // 1. 대역폭 추정 self.bandwidth_estimator.update(&packet); // 2. 품질 제어 (시뮬캐스트/SVC) let target_quality = self.quality_controller.decide_quality( packet.ssrc, self.bandwidth_estimator.get_estimate() ); // 3. 선택적 포워딩 let recipients = self.router.get_recipients(packet.ssrc); for recipient in recipients { // 수신자별 최적화 let adapted_packet = match recipient.connection_quality { Quality::High => packet.clone(), Quality::Medium => self.downscale_packet(&packet, 720), Quality::Low => self.downscale_packet(&packet, 480), }; // NACK, FEC 처리 recipient.send_with_redundancy(adapted_packet).await?; } Ok(()) } } 

3. 대규모 실시간 동기화

// Go로 작성된 Presence 서비스 type PresenceService struct { shards map[uint64]*PresenceShard ring *consistent.Ring redisCli *redis.ClusterClient } type PresenceShard struct { mu sync.RWMutex users map[uint64]*UserPresence lastSync time.Time } func (ps *PresenceService) UpdatePresence(userID uint64, status Status) error { // Consistent hashing으로 샤드 결정 shardID := ps.ring.Get(fmt.Sprintf("user:%d", userID)) shard := ps.shards[shardID] shard.mu.Lock() defer shard.mu.Unlock() // 로컬 업데이트 presence := &UserPresence{ UserID: userID, Status: status, UpdatedAt: time.Now(), } shard.users[userID] = presence // Redis에 비동기 동기화 go ps.syncToRedis(userID, presence) // 구독자에게 브로드캐스트 ps.broadcastUpdate(userID, presence) return nil } // 효율적인 bulk 동기화 func (ps *PresenceService) BulkSync() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for range ticker.C { for _, shard := range ps.shards { shard.mu.RLock() updates := make([]*UserPresence, 0, len(shard.users)) for _, presence := range shard.users { if presence.UpdatedAt.After(shard.lastSync) { updates = append(updates, presence) } } shard.mu.RUnlock() if len(updates) > 0 { ps.bulkSyncToRedis(updates) shard.lastSync = time.Now() } } } } 

📈 성과 & 지표

기술적 성과

  • 메시지 지연: < 100ms (글로벌 평균)
  • 음성 지연: < 60ms (동일 지역)
  • 가용성: 99.95%+
  • 동시 음성 채널: 수백만 개
  • 메시지 처리량: 초당 수백만 건

확장성 성과

  • 서버당 사용자: 최대 80만 명 (Midjourney)
  • 최대 음성 참가자: 10,000명 (Stage Channels)
  • 파일 업로드: 500MB (Nitro)
  • 화면 공유: 4K 60fps 지원

🎓 Discord에서 배울 점

✅ 적용 가능한 패턴

  1. Actor Model: Erlang/OTP의 강력함
  2. 언어별 최적화: 적재적소에 최적 언어 사용
  3. WebRTC SFU: 대규모 실시간 통신
  4. Consistent Hashing: 분산 시스템 샤딩
  5. Event Sourcing: 상태 관리와 히스토리

❌ 주의사항

  1. Elixir 학습곡선: 함수형 + Actor 모델
  2. 다중 언어 복잡성: 운영 오버헤드
  3. 실시간 요구사항: 네트워크 품질 의존
  4. 스케일 도전: 대규모 서버 관리

📚 추천 리소스


🔮 미래 전망

현재 집중 분야

  1. AI 통합: 대화 요약, 번역, 모더레이션
  2. Forum Channels: 구조화된 대화
  3. Activities: 앱 내 미니게임/활동
  4. 크리에이터 수익화: 서버 구독, 티켓팅
  5. 엔터프라이즈: Slack 대체 시장

기술 투자 영역

  • Rust 확대: 더 많은 핵심 서비스
  • AI/ML: 콘텐츠 모더레이션, 추천
  • 엣지 컴퓨팅: 더 낮은 지연시간
  • 블록체인: Web3 커뮤니티 지원
  • AR/VR: 메타버스 통합

"우리는 단순한 채팅 앱이 아닌, 전 세계 커뮤니티가 모이는 디지털 공간을 만들고 있습니다."

  • Discord Engineering

마지막 업데이트: 2025-01-28

Found this helpful? Share it with others!
Tweet

🔗 Related Content

You might also be interested in these articles

🏢 company

🏢 Uber 기술 스택 분석

우버가 매일 2,500만 건의 라이드를 실시간으로 매칭하는 기술 스택 심층 분석 - Go, Java, Node.js로 구축한 글로벌 실시간 마켓플레이스

15 min read
uber, microservices+12
Read more
🏗️ stack

🔄 Realtime Collaboration Stack

Figma, Notion 같은 실시간 협업 앱 구축을 위한 기술 스택 - WebSocket, CRDT, WebRTC로 만드는 동시 편집 시스템

21 min read
nodejs, socketio+13
Read more
🏢 company

🏢 Figma 기술 스택 분석

피그마가 브라우저에서 실시간 협업 디자인 툴을 구현한 기술 스택 심층 분석 - C++, WebAssembly, WebGL로 만든 차세대 디자인 플랫폼

22 min read
figma, c+++11
Read more
🏢 company

🏢 Netflix 기술 스택 분석

넷플릭스가 전 세계 2.4억 사용자에게 스트리밍 서비스를 제공하는 기술 스택 심층 분석 - 마이크로서비스, AWS, Chaos Engineering의 교과서

9 min read
netflix, microservices+11
Read more
🏢 company

🏢 Notion 기술 스택 분석

노션이 수천만 사용자에게 All-in-One 워크스페이스를 제공하는 기술 스택 심층 분석 - 블록 기반 에디터, 실시간 협업, 그리고 확장 가능한 데이터베이스

24 min read
notion, typescript+11
Read more
🏢 company

🏢 Airbnb 기술 스택 분석

에어비앤비가 전 세계 400만 개의 숙소를 연결하는 글로벌 마켓플레이스 기술 스택 심층 분석 - Ruby on Rails에서 Service-Oriented Architecture로의 진화

24 min read
airbnb, ruby+12
Read more

Found this helpful?

Help us improve this content by contributing on GitHub or sharing your feedback with the community.