시스템 디자인: LeetCode를 대규모로 설계하기
요약
대규모 트래픽을 처리하는 온라인 코딩 플랫폼 LeetCode의 시스템 디자인을 다룹니다. 기능적 요구사항부터 데이터베이스, 코드 실행 환경, 실시간 리더보드 아키텍처까지 확장 가능한 분산 시스템 설계 방안을 제시합니다.
핵심 포인트
- 수백만 사용자와 초당 수천 건의 제출을 처리하는 분산 시스템 설계
- 코드 실행 결과의 낮은 지연 시간(2~5초) 및 고가용성 확보
- 콘테스트 시 발생하는 급격한 트래픽 스파이크 대응을 위한 확장성
- 사용자 코드를 안전하게 실행하기 위한 보안 및 격리 환경 필요성
LeetCode는 겉보기에는 단순해 보입니다. 코딩 문제를 찾아보고, 코드를 작성하고, 즉각적인 피드백을 받는 과정 말이죠.
하지만 그 단순한 인터페이스 뒤에는 수백만 명의 개발자, 초당 수천 건의 코드 제출, 그리고 코딩 콘테스트 중 발생하는 엄청난 트래픽 급증을 처리할 수 있는 분산 시스템 (Distributed System)이 자리 잡고 있습니다.
이 글에서는 기능적 요구사항 (Functional Requirements)부터 데이터베이스 선택, 코드 실행, 리더보드 (Leaderboard) 아키텍처에 이르기까지 모든 것을 다루며, 확장 가능한 버전의 LeetCode를 처음부터 설계해 보겠습니다.
문제 정의 (Problem Statement)
사용자가 다음과 같은 작업을 수행할 수 있는 LeetCode와 유사한 온라인 코딩 플랫폼을 설계하세요:
- 코딩 문제 찾아보기
- 문제 설명 읽기
- 다양한 프로그래밍 언어로 코드 작성하기
- 솔루션 제출하기
- 몇 초 이내에 실행 결과 받기
- 주간 코딩 콘테스트 참여하기
- 실시간 리더보드 보기
기능적 요구사항 (Functional Requirements)
우리 시스템은 다음과 같은 기능들을 지원해야 합니다:
1. 코딩 문제 찾아보기
사용자는 다음을 할 수 있어야 합니다:
- 문제 검색
- 난이도별 필터링
- 태그별 필터링
- 인기순 정렬
- 페이지네이션 (Pagination) 지원
2. 문제 보기
각 문제에는 다음 내용이 포함됩니다:
- 제목
- 설명
- 제약 사항 (Constraints)
- 샘플 입력 (Sample Input)
- 샘플 출력 (Sample Output)
- 숨겨진 테스트 케이스 (Hidden Test Cases)
- 지원되는 언어
3. 솔루션 제출하기
사용자는 다음을 할 수 있어야 합니다:
-
프로그래밍 언어 선택
-
코드 작성
-
코드 제출
-
다음과 같은 판정 (Verdicts) 받기:
-
Accepted (정답)
-
Wrong Answer (오답)
-
Runtime Error (실행 시간 오류)
-
Compilation Error (컴파일 오류)
-
Time Limit Exceeded (TLE, 시간 초과)
-
Memory Limit Exceeded (MLE, 메모리 초과)
4. 콘테스트 지원
다음 기능을 포함하여 주간 및 격주 콘테스트를 지원합니다:
- 콘테스트 등록
- 실시간 순위
- 점수 계산
- 실시간 리더보드 업데이트
범위 외 사항 (Out of Scope)
설계의 집중도를 유지하기 위해 다음 사항들은 제외합니다:
- 인증 및 인가 (Authentication & Authorization)
- 사용자 프로필
- 결제
- 알림
- 추천 엔진 (Recommendation Engine)
- 분석 (Analytics)
비기능적 요구사항 (Non-Functional Requirements)
상용 수준의 코딩 플랫폼은 여러 가지 품질 속성 (Quality Attributes)을 충족해야 합니다.
고가용성 (High Availability)
콘테스트 중에는 수천 명의 사용자가 동시에 코드를 제출합니다.
개별 서비스가 실패하더라도 플랫폼은 가용성을 유지해야 합니다.
낮은 지연 시간 (Low Latency)
사용자는 코드를 제출한 후 2~5초 이내에 결과를 받기를 기대합니다.
지연 시간이 길어지면 사용자 경험 (User Experience)이 저하됩니다.
확장성 (Scalability)
시스템은 다음을 지원해야 합니다:
- 수백만 명의 사용자
- 수천 개의 동시 제출
- 콘테스트 중 발생하는 대규모 트래픽 급증 (Traffic Spikes)
수평적 확장 (Horizontal Scaling)이 권장됩니다.
보안 (Security)
임의의 사용자 코드를 실행하는 것은 매우 위험합니다.
실행 환경은 반드시 다음과 같아야 합니다:
- 샌드박스 (Sandboxed) 처리
- 격리 (Isolated)
- 리소스 제한 (Resource Limited)
- 악성 코드로부터 보호
결함 허용 (Fault Tolerance)
어떤 서비스도 단일 장애점 (Single Point of Failure)이 되어서는 안 됩니다.
개별 구성 요소가 실패하더라도 플랫폼은 계속 작동해야 합니다.
용량 산정 (Capacity Estimation)
다음과 같이 가정합니다:
- 등록 사용자 1,000만 명
- 일일 활성 사용자 (DAU) 200만 명
- 콘테스트 중 동시 접속 사용자 10만 명
- 분당 코드 제출 2만 건
이 수치들은 우리의 인프라 선택에 영향을 미칩니다.
핵심 엔티티 (Core Entities)
문제 (Problem)
ProblemID
Title
Difficulty
...
사용자 (User)
UserID
Username
Rating
...
제출 (Submission)
SubmissionID
ProblemID
UserID
...
콘테스트 (Contest)
ContestID
Start Time
End Time
...
리더보드 (Leaderboard)
ContestID
UserID
Score
...
REST APIs
문제 가져오기 (Get Problems)
GET /problems?page=1&difficulty=medium
페이지가 나뉜 문제 목록을 반환합니다.
문제 상세 정보 가져오기 (Get Problem Details)
GET /problems/{problemId}
전체 문제 정보를 반환합니다.
솔루션 제출 (Submit Solution)
POST /problems/{problemId}/submit
요청 (Request):
{
"language":"Java",
"code":"..."
...
응답 (Response):
{
"submissionId":"12345",
"status":"Queued"
...
제출 상태 (Submission Status)
GET /submissions/{submissionId}
반환 값:
- 실행 중 (Running)
- 통과 (Accepted)
- 오답 (Wrong Answer)
- 런타임 에러 (Runtime Error)
콘테스트 리더보드 (Contest Leaderboard)
GET /contests/{contestId}/leaderboard
High-Level Architecture (상위 수준 아키텍처)
Users (사용자)
│
Load Balancer (부하 분산기)
...
Why Use a Message Queue? (메시지 큐를 사용하는 이유?)
코드 실행 (Code execution)은 시간이 많이 소요됩니다.
코드를 동기적 (synchronously)으로 실행하는 대신:
User (사용자)
↓
...
우리는 제출 (submissions)을 큐에 넣습니다 (enqueue).
User (사용자)
↓
...
장점:
- 더 나은 확장성 (scalability)
- 실패한 실행에 대한 재시도 (Retry)
- 부하 분산 (Load balancing)
- API 타임아웃 (timeout) 방지
주요 선택지:
- Kafka
- RabbitMQ
- Amazon SQS
Code Execution Environment (코드 실행 환경)
이것은 가장 핵심적인 구성 요소입니다.
사용자의 코드를 서버에서 직접 실행하는 것은 안전하지 않습니다.
대신:
- 격리된 Docker 컨테이너 (containers)를 생성합니다
- CPU 및 메모리 제한 (limits)을 적용합니다
- 네트워크 액세스 (network access)를 제한합니다
- 실행 후 컨테이너를 파괴합니다
장점:
- 보안 (Security)
- 격리 (Isolation)
- 비용 효율성 (Cost efficiency)
- 가상 머신 (Virtual Machines) 대비 빠른 시작 속도
Why Containers Instead of Virtual Machines? (가상 머신 대신 컨테이너를 사용하는 이유?)
| Docker 컨테이너 (Containers) | 가상 머신 (Virtual Machines) |
|---|---|
| 가볍습니다 (Lightweight) | 무겁습니다 (Heavy) |
| ... |
온라인 저지 (online judges)의 경우, 컨테이너는 성능과 격리 사이에서 최적의 균형을 제공합니다.
Database Design (데이터베이스 설계)
플랫폼은 다양한 유형의 데이터를 저장합니다.
Relational Database (관계형 데이터베이스)
다음 항목에 적합합니다:
- 사용자 (Users)
- 콘테스트 (Contests)
- 순위 (Rankings)
- 트랜잭션 (Transactions)
예시:
- PostgreSQL
- MySQL
NoSQL Database (NoSQL 데이터베이스)
다음 항목에 이상적입니다:
- 문제 (Problems)
- 테스트 케이스 (Test Cases)
- 제출 (Submissions)
- 실행 로그 (Execution Logs)
문제 메타데이터 (metadata)는 거의 변경되지 않는 반면 제출 데이터는 급격히 증가하기 때문에, NoSQL 데이터베이스는 뛰어난 확장성 (scalability)을 제공합니다.
Caching (캐싱)
인기 있는 문제는 업데이트되는 빈도보다 읽히는 빈도가 훨씬 높습니다.
우리는 다음을 캐싱할 수 있습니다:
- 문제 상세 정보 (Problem details)
- 테스트 메타데이터 (Test metadata)
- 콘테스트 정보 (Contest information)
- 리더보드 (Leaderboards)
Redis를 사용하면 데이터베이스 부하를 크게 줄이고 응답 시간 (response times)을 개선할 수 있습니다.
Live Leaderboard (실시간 리더보드)
리더보드는 콘테스트 중에 빈번하게 업데이트됩니다.
매 제출마다 순위를 다시 계산하는 대신:
- 점수를 비동기적으로 업데이트 (Update scores asynchronously)
- Redis Sorted Sets에 순위 저장
- WebSockets를 사용하여 업데이트 푸시 (Push updates using WebSockets)
이를 통해 최소한의 지연 시간(latency)으로 실시간에 가까운 순위를 제공할 수 있습니다.
코드 실행 확장 (Scaling Code Execution)
실행 워커(Execution workers)는 독립적으로 확장되어야 합니다.
콘테스트 기간 동안:
일반적인 날
10명의 워커
...
Kubernetes 또는 컨테이너 오케스트레이션(container orchestration)을 사용하면 큐 길이(queue length)에 따라 자동 확장(automatic scaling)이 가능합니다.
병목 현상 (Bottlenecks)
잠재적인 병목 현상은 다음과 같습니다:
- 코드 실행 워커 (Code execution workers)
- 메시지 큐 백로그 (Message queue backlog)
- 데이터베이스 쓰기 처리량 (Database write throughput)
- 리더보드 업데이트
- 대규모 콘테스트 트래픽 급증
각 구성 요소는 독립적으로 확장 가능해야 합니다.
트레이드오프 (Trade-offs)
모든 시스템 디자인에는 트레이드오프가 수반됩니다.
가용성 vs 일관성 (Availability vs Consistency)
우리는 **가용성 (Availability)**을 우선시합니다.
제출 결과가 1초 정도 지연되는 것은 허용 가능합니다.
하지만 콘테스트 도중 플랫폼이 중단되는 것은 허용되지 않습니다.
이러한 특성 때문에 리더보드 업데이트에는 **최종 일관성 (Eventual Consistency)**이 실용적인 선택이 됩니다.
향후 개선 사항 (Future Improvements)
플랫폼은 다음과 같은 기능으로 확장될 수 있습니다:
- AI 기반 코드 리뷰 (AI-powered code review)
- 코드 유사도 탐지 (Code similarity detection)
- 표절 탐지 (Plagiarism detection)
- 사용자 정의 테스트 케이스 (Custom test cases)
- 인터뷰 모드 (Interview mode)
- 기업별 문제 세트 (Company-specific problem sets)
- 멀티 리전 배포 (Multi-region deployment)
- 분산 실행 클러스터 (Distributed execution clusters)
마치며 (Final Thoughts)
LeetCode와 같은 플랫폼을 설계하는 것은 단순히 코딩 문제를 저장하는 것 그 이상입니다.
이는 보안이 확보된 코드 실행, 확장 가능한 인프라, 비동기 처리, 분산 캐싱(distributed caching), 그리고 실시간 리더보드를 포함하며, 이 모든 과정에서 낮은 지연 시간과 높은 가용성을 유지해야 합니다.
견고한 디자인은 성능, 보안, 확장성의 균형을 맞추어, 가장 바쁜 코딩 콘테스트 중에도 개발자들이 즉각적인 피드백을 받을 수 있도록 보장합니다.
온라인 코딩 플랫폼이 계속 진화함에 따라, AI 지원 코드 분석, 분산 실행 환경, 지능형 추천 기능을 통합함으로써 이러한 시스템은 더욱 강력해질 것입니다.
코드 실행 엔진(code execution engine)을 어떻게 설계하시겠습니까? Docker, Firecracker microVMs, 또는 다른 샌드박싱(sandboxing) 기술 중 무엇을 선택하시겠습니까? 여러분의 생각을 댓글로 공유해 주세요!
#systemdesign #leetcode #backend #softwarearchitecture #distributedsystems #microservices #cloud #programming #developers #coding
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기