여러분은 X(전 트위터)에서 트윗을 올리고 몇 초 만에 전 세계 사람들이 그 트윗을 볼 수 있다는 사실에 의문을 가져본 적 있으신가요? 하루에도 수억 개의 트윗이 쏟아지는데, 서비스는 멈추지 않고 실시간으로 돌아갑니다. 이런 대규모 시스템이 어떻게 설계되는지 궁금하지 않으세요?
오늘은 초보 개발자 여러분도 이해할 수 있도록, X와 같은 트윗 서비스의 시스템 설계 전략을 낱낱이 파헤쳐 보겠습니다. 단순히 코드를 짜는 것을 넘어, 수억 명의 사용자를 지원하는 시스템을 어떻게 설계하는지 그 ‘왜’와 ‘어떻게’를 함께 알아보시죠.
대규모 시스템 설계, 왜 어려운가?
여러분이 간단한 게시판 서비스를 만든다고 상상해 보세요. 사용자 100명 정도라면 MySQL 데이터베이스 하나면 충분합니다. 하지만 사용자가 1억 명이라면? 하루에 5억 개의 트윗이 쏟아진다면? 단순한 설계로는 시스템이 순식간에 마비됩니다.
대규모 시스템 설계가 어려운 이유는 확장성, 신뢰성, 성능이라는 세 마리 토끼를 동시에 잡아야 하기 때문입니다. 사용자가 늘어나도 서비스가 멈추지 않아야 하고, 한 서버가 고장 나도 전체 시스템은 살아 있어야 하며, 사용자는 자신의 트윗이 즉시 타임라인에 나타나길 기대합니다.
이런 문제를 해결하려면 처음부터 시스템을 제대로 설계해야 합니다. 그렇다면 X는 어떻게 이 문제를 해결했을까요?
시스템 설계의 첫걸음: 기능 요구사항 정의하기
시스템을 설계하기 전에 가장 먼저 해야 할 일은 무엇을 만들 것인가를 명확히 하는 것입니다. X의 핵심 기능을 정리하면 다음과 같습니다.
사용자 인증과 계정 관리
사용자는 이메일과 비밀번호로 계정을 만들고 로그인할 수 있어야 합니다. 보안은 필수입니다. 비밀번호는 암호화되어 저장되어야 하고, 인증 과정은 안전하게 처리되어야 합니다.
트윗 생성과 미디어 첨부
사용자는 280자 이내의 짧은 메시지를 작성할 수 있습니다. 여기에 이미지나 동영상을 첨부할 수도 있죠. 단순해 보이지만, 동영상 파일을 어디에 저장하고 어떻게 빠르게 로딩할지는 복잡한 문제입니다.
팔로우 시스템과 타임라인
사용자는 다른 사용자를 팔로우하여 그들의 트윗을 자신의 타임라인에서 볼 수 있습니다. 만약 여러분이 1,000명을 팔로우한다면, 타임라인에는 이 1,000명의 최신 트윗이 시간순으로 정렬되어 나타나야 합니다. 실시간으로요.
검색, 리트윗, 다이렉트 메시지
해시태그나 키워드로 트윗을 검색하고, 다른 사람의 트윗을 공유하며, 개인 메시지를 주고받는 기능까지. 각각이 독립적인 서비스처럼 설계되어야 합니다.
이런 기능 요구사항을 명확히 정의하고 나면, 이제 실제 시스템 아키텍처를 설계할 차례입니다.
트윗 서비스의 핵심 아키텍처
트윗 서비스는 X 전체 시스템에서 가장 핵심적인 부분입니다. 트윗의 생성, 조회, 삭제를 담당하는 이 서비스는 어떻게 설계되어 있을까요?
마이크로서비스 아키텍처
X는 단일 거대 애플리케이션이 아닙니다. 트윗 서비스, 타임라인 서비스, 검색 서비스, 알림 서비스 등 여러 개의 독립적인 서비스로 나뉘어져 있습니다. 각 서비스는 자신의 역할에만 집중하고, 필요할 때 다른 서비스와 통신합니다.
사용자가 앱에서 트윗을 올리면 다음과 같은 일이 일어납니다:
- 로드 밸런서가 수백 대의 서버 중 가장 여유 있는 서버로 요청을 전달합니다.
- API 게이트웨이가 요청을 검증하고 적절한 서비스로 라우팅합니다.
- 트윗 서비스가 실제 트윗 생성 로직을 실행합니다.
데이터는 어디에 저장될까요?
트윗 서비스는 두 가지 저장소를 사용합니다:
- 데이터베이스 (아파치 카산드라, DynamoDB)
- 트윗 ID, 사용자 ID, 텍스트 내용, 작성 시간 같은 메타데이터를 저장합니다. NoSQL 데이터베이스를 사용하는 이유는 확장성 때문입니다. 데이터가 여러 서버에 분산 저장되어 한 서버가 죽어도 서비스가 계속 돌아갑니다.
- 객체 저장소 (아마존 S3)
- 이미지와 동영상 같은 큰 미디어 파일은 별도의 객체 저장소에 저장됩니다. 데이터베이스에는 파일의 고유 식별자만 참조로 저장되고, 실제 파일은 S3에 있습니다. 이렇게 하면 데이터베이스가 가벼워지고, 미디어 파일은 CDN을 통해 전 세계 어디서든 빠르게 로딩될 수 있습니다.
트윗 생성, 그 복잡한 과정
여러분이 “오늘 날씨 좋다!”라는 트윗을 올린다고 가정해 봅시다. 겉보기엔 단순해 보이지만, 내부에서는 여러 단계를 거칩니다.
- 1단계: 요청 전송
- 앱이 POST /tweets 엔드포인트로 요청을 보냅니다. 요청에는 트윗 내용, 사용자 ID, 그리고 첨부한 사진(있다면)이 포함됩니다.
- 2단계: 유효성 검증
- 트윗 서비스는 즉시 검증을 시작합니다. 트윗 길이가 280자를 초과하지는 않는지, 사용자가 로그인되어 있는지, 스팸 필터에 걸리지는 않는지 확인합니다.
- 3단계: 미디어 업로드
- 사진을 첨부했다면 객체 저장소에 업로드하고 고유 URL을 받아옵니다. 이 과정은 비동기로 처리되어 트윗 게시 속도를 늦추지 않습니다.
- 4단계: 데이터베이스 저장
- 고유한 트윗 ID를 생성하고, 트윗 데이터를 데이터베이스에 저장합니다. 파티션 키로 트윗 ID를 사용하여 데이터가 여러 노드에 고르게 분산되도록 합니다.
- 5단계: 응답 반환
- 새로 생성된 트윗 객체를 사용자에게 반환합니다. 이제 여러분의 화면에 트윗이 나타납니다.
- 6단계: 메시지 큐 발행
- 마지막으로 트윗 정보를 아파치 카프카 같은 메시지 큐에 발행합니다. 타임라인 서비스는 이 메시지를 구독하고 있다가, 여러분을 팔로우하는 사람들의 타임라인을 업데이트합니다. 검색 서비스도 이 메시지를 받아 검색 인덱스를 갱신합니다.
이 모든 과정이 1초도 안 되는 시간에 일어납니다. 놀랍지 않나요?
성능의 비밀: 캐싱 전략
수억 개의 트윗을 매번 데이터베이스에서 가져온다면 시스템은 곧 마비될 것입니다. 여기서 캐싱이 핵심적인 역할을 합니다.
캐시란 무엇일까요?
캐시는 자주 사용되는 데이터를 메모리에 임시로 저장해 두는 저장소입니다. 레디스(Redis) 같은 인메모리 데이터베이스를 사용하면 디스크 기반 데이터베이스보다 수백 배 빠른 속도로 데이터를 가져올 수 있습니다.
예를 들어 유명인이 올린 트윗은 수백만 명이 조회합니다. 이 트윗을 매번 데이터베이스에서 가져오는 대신, 캐시에 저장해 두면 빠르게 응답할 수 있습니다.
캐싱 전략의 종류
- 시간 기반 슬라이딩 윈도우
- 최근 24시간 이내의 트윗만 캐시에 유지합니다. 오래된 트윗은 조회 빈도가 낮으므로 캐시에서 제거하여 메모리를 절약합니다.
- 인기도 기반 캐싱
- 좋아요와 리트윗 수를 계산하여 인기 점수가 높은 트윗만 캐시에 저장합니다. “트렌드”에 오른 트윗들이 이 전략의 혜택을 받습니다.
- 하이브리드 접근
- 최근 2시간 이내의 트윗은 무조건 캐시하고, 그 이후에는 인기도 기준을 적용합니다. 이렇게 하면 실시간성과 효율성을 동시에 잡을 수 있습니다.
캐시 계층화
캐시도 계층으로 나눌 수 있습니다. 핫 캐시는 초당 수천 번 조회되는 트윗을, 웜 캐시는 중간 빈도 트윗을, 콜드 캐시는 거의 조회되지 않는 트윗을 저장합니다. 이렇게 하면 메모리를 효율적으로 사용할 수 있습니다.
조회 과정의 최적화
이제 여러분이 타임라인을 열어 트윗을 보는 과정을 살펴봅시다.
- 1단계: 캐시 확인
- 트윗 서비스는 먼저 캐시를 확인합니다. 요청한 트윗이 캐시에 있다면 즉시 반환합니다. 이것을 캐시 히트라고 합니다.
- 2단계: 데이터베이스 조회
- 캐시에 없다면(캐시 미스) 데이터베이스에서 트윗을 가져옵니다. 동시에 이 트윗을 캐시에 저장해 두어 다음 요청 때는 빠르게 응답할 수 있도록 합니다.
- 3단계: 미디어 로딩
- 트윗에 이미지가 첨부되어 있다면 CDN에서 가져옵니다. CDN은 전 세계 여러 지역에 서버를 두어, 사용자와 가장 가까운 서버에서 파일을 전송합니다. 서울에 있는 여러분이 미국 서버까지 갈 필요가 없는 것이죠.
메시지 큐의 역할
여러분이 트윗을 올리면 그 트윗은 여러분을 팔로우하는 모든 사람의 타임라인에 나타나야 합니다. 만약 팔로워가 100만 명이라면? 100만 개의 타임라인을 즉시 업데이트해야 합니다.
이 작업을 동기적으로 처리하면 트윗 게시에 수십 초가 걸릴 것입니다. 대신 메시지 큐를 사용합니다. 트윗 서비스는 “새 트윗이 생성되었습니다”라는 메시지를 큐에 넣기만 하면 됩니다.
타임라인 서비스는 이 큐를 구독하고 있다가, 메시지를 받으면 백그라운드에서 천천히 타임라인을 업데이트합니다. 사용자는 즉시 트윗이 올라간 것을 확인할 수 있고, 타임라인 업데이트는 비동기로 처리됩니다.
확장성을 고려한 데이터베이스 설계
NoSQL 데이터베이스를 사용하는 이유는 수평적 확장이 가능하기 때문입니다. 사용자가 늘어나면 서버를 추가하기만 하면 됩니다.
- 파티셔닝을 통해 데이터를 여러 노드에 분산 저장합니다. 예를 들어 트윗 ID가 1-1000인 트윗은 서버 A에, 1001-2000인 트윗은 서버 B에 저장하는 식입니다.
- 복제(Replication)를 통해 같은 데이터를 여러 서버에 저장해 둡니다. 한 서버가 고장 나도 다른 서버에서 데이터를 가져올 수 있어 서비스가 멈추지 않습니다.
시스템 설계의 핵심은 트레이드오프
여기까지 읽으신 여러분은 궁금할 것입니다.
그럼 모든 데이터를 캐시에 넣으면 안 되나요?
좋은 질문입니다.
하지만 캐시 메모리는 비싸고 용량이 제한적입니다. 모든 트윗을 캐시에 넣으면 비용이 천문학적으로 늘어납니다. 또한 캐시와 데이터베이스 사이의 일관성을 유지하는 것도 어려운 문제입니다.
시스템 설계는 결국 트레이드오프입니다. 성능을 높이려면 비용이 늘어나고, 일관성을 보장하려면 성능이 떨어집니다. 완벽한 시스템은 없습니다. 주어진 제약 조건 안에서 최선의 선택을 하는 것이 설계자의 역할입니다.
여러분도 대규모 시스템을 설계할 수 있습니다
처음에는 복잡해 보이지만, 하나씩 뜯어보면 대규모 시스템도 이해할 수 있는 개념들의 조합입니다. 로드 밸런싱, 캐싱, 메시지 큐, 데이터베이스 파티셔닝… 이런 개념들은 X뿐만 아니라 인스타그램, 유튜브, 넷플릭스 등 모든 대규모 서비스의 기반입니다.
초보 개발자라면 먼저 작은 프로젝트로 이런 개념들을 직접 구현해 보세요. 간단한 트위터 클론을 만들어 보고, 사용자가 늘어나면 어떤 병목이 생기는지 경험해 보세요. 그리고 캐시를 추가하고, 데이터베이스를 분산하면서 성능이 어떻게 개선되는지 체감해 보세요.
시스템 설계는 단순히 이론을 아는 것이 아니라, 실제 문제를 해결하면서 체득하는 것입니다. 오늘 배운 X의 설계 원칙을 여러분의 다음 프로젝트에 적용해 보시는 건 어떨까요?
대규모 시스템 설계는 마이크로서비스 아키텍처, 효율적인 캐싱 전략, 메시지 큐를 활용한 비동기 처리, 그리고 확장 가능한 데이터베이스 설계의 조화입니다.