Skip to content
sweepty

가상 면접 사례로 배우는 대규모 시스템 설계 기초 1편 정리

— 40 min read

팀원들과 『가상 면접 사례로 배우는 대규모 시스템 설계 기초』를 한 챕터씩 읽고 이야기 나누는 스터디를 했습니다. iOS로 시작해서 웹·서버까지 영역을 넓혀가는 중이라 인프라 쪽은 낯선 개념이 많았는데, 챕터마다 핵심 개념과 읽으면서 든 생각을 함께 정리해봤습니다.

1장. 사용자 수에 따른 규모 확장성

사용자 한 명을 위한 단일 서버에서 시작해, 수백만 사용자를 감당하는 구조로 조금씩 확장해가는 장입니다.

  • 수직적 규모 확장(scale-up) vs 수평적 규모 확장(scale-out): 서버 한 대의 사양을 올리는 건 간단하지만 한계가 있고 장애 시 대안이 없다. 대규모 서비스는 서버를 여러 대로 늘리는 수평 확장이 기본이다.
  • 로드밸런서: 트래픽을 여러 웹 서버에 고르게 나눈다. 한 서버가 죽어도 나머지로 트래픽을 보내 가용성이 올라간다.
  • DB 다중화: 쓰기는 주(primary) DB, 읽기는 부(replica) DB가 담당한다. 대부분의 서비스는 읽기가 훨씬 많아서 성능이 좋아지고, 주 DB가 죽으면 부 DB를 승격시켜 안정성도 확보된다.
  • 캐시: 자주 읽고 잘 바뀌지 않는 데이터를 메모리에 두어 DB 부하를 줄인다. 만료 정책, 일관성, 캐시 서버 장애(SPOF)를 함께 고려해야 한다.
  • CDN: 이미지·JS·CSS 같은 정적 콘텐츠를 사용자와 가까운 서버에서 내려준다.
  • 무상태(stateless) 웹 계층: 세션 같은 상태 정보를 웹 서버가 아니라 공유 저장소에 두어야 서버를 자유롭게 늘리고 줄일 수 있다.
  • 메시지 큐: 생산자와 소비자를 분리해 각각 독립적으로 확장할 수 있게 한다.
  • 샤딩: 데이터를 여러 DB에 나눠 담는다. 어느 샤드에 넣을지는 샤딩 키를 해시해서 정한다. 특정 샤드에 데이터가 몰리는 문제, 샤드 간 조인이 어려운 문제가 따라온다.

그동안은 바이브코딩으로 적은 사용자를 대상으로만 구현해봤는데, 실 서비스에서는 대규모 사용자를 전제로 구조를 설계해야 한다는 걸 새삼 느꼈습니다. 업무에서 "즉시 반영이 필요한 것과 아닌 것이 있다"는 이야기를 들었을 때 막연했는데, 캐시와 CDN을 이해하고 나니 왜 그런지 와닿았어요. 샤딩은 여러 DB에 순서대로 쌓는 줄 알았는데 해시 함수로 위치를 정한다는 것도 이번에 알게 됐습니다.

회사에서는 DB 다중화 같은 걸 인프라팀이나 SRE가 담당하는지, 쿠버네티스 같은 용어는 어디까지 알아야 하는지도 궁금해졌습니다.

2장. 개략적인 규모 측정

시스템 설계 면접에서는 "QPS가 얼마쯤 나올까", "5년 동안 저장 공간이 얼마나 필요할까" 같은 질문을 받게 됩니다. 정확한 값보다 합리적인 가정을 세우고 빠르게 계산하는 과정을 봅니다.

  • 2의 제곱수: 1KB = 2^10바이트, 1MB = 2^20, 1GB = 2^30, 1TB = 2^40. 데이터 볼륨 단위를 감으로 알고 있어야 한다.
  • 응답 지연 값: 메모리는 빠르고 디스크는 느리다. 디스크 탐색은 가능한 피하고, 데이터는 압축해서 보내고, 데이터센터 간 왕복은 시간이 오래 걸린다.
  • 가용성 수치: 99.9%면 연간 약 8.8시간, 99.99%면 약 53분의 장애를 허용한다는 뜻이다.
  • 계산 팁: 숫자는 반올림해서 단순하게, 가정은 적어두고, 단위는 꼭 붙이자.

예를 들어 DAU 1억 명이 하루 2번 글을 올리면 초당 쓰기 QPS는 대략 1억 × 2 ÷ 86,400 ≈ 2,300, 피크는 그 두 배 정도로 잡는 식입니다.

규모를 계산하는 것도 하나의 능력이구나 싶었어요. 결국 경험이 쌓여야 감이 생기는 영역인 것 같습니다.

3장. 시스템 설계 면접 공략법

책은 면접을 네 단계로 나눕니다.

  1. 문제 이해 및 설계 범위 확정: 바로 답하지 말고 질문으로 요구사항을 좁힌다. 어떤 기능이 필요한지, 사용자는 얼마나 되는지, 얼마나 빨리 커질지 묻는다.
  2. 개략적인 설계안 제시 및 동의 구하기: 큰 그림을 그리고 면접관과 합의한다. 2장의 규모 계산도 여기서 활용한다.
  3. 상세 설계: 면접관이 관심 있어 하는 컴포넌트를 우선순위에 따라 깊게 파고든다.
  4. 마무리: 병목 지점, 장애 대응, 규모 확장 방안 등 개선점을 스스로 이야기한다.

가장 와닿은 건 두 가지였습니다.

  • 면접관과 소통하라. 질문의 의도를 파악하는 게 핵심이다. 설계를 하라고 하면 바로 그리기 시작하지 말고, 질문을 통해 요구사항을 정확히 알아내자.
  • "완벽한데요?" 금지. 면접관이 개선할 지점을 찾아보라고 할 때 열린 태도를 보여주자. 완벽한 설계는 없다.

사실 이건 면접뿐 아니라 실무에서 기획을 받을 때도 똑같이 적용되는 이야기 같아요.

4장. 처리율 제한 장치의 설계

처리율 제한 장치(rate limiter)는 클라이언트가 일정 기간 보낼 수 있는 요청 수를 제한합니다. DoS 공격을 막고, 유료 API 비용을 아끼고, 서버 과부하를 방지합니다.

어디에 둘까? 클라이언트 측은 위변조가 쉬워서 믿을 수 없고, 보통 서버 앞단의 API 게이트웨이나 별도 미들웨어에 둡니다.

처리율 제한 알고리즘

알고리즘동작특징
토큰 버킷버킷에 주기적으로 토큰을 채우고, 요청마다 토큰을 하나 소비짧은 시간 몰리는 트래픽(burst)을 허용. 깜짝 세일 같은 상황에 적합
누출 버킷요청을 큐에 넣고 일정한 속도로 처리처리 속도가 고정되어 안정적. 몰리는 트래픽엔 불리
고정 윈도 카운터정해진 시간 창마다 카운터를 셈구현이 쉽지만 창 경계에서 트래픽이 두 배로 몰릴 수 있음
이동 윈도 로그요청 타임스탬프를 모두 기록정확하지만 메모리를 많이 씀
이동 윈도 카운터고정 윈도와 이동 윈도를 섞음메모리 효율과 정확도의 절충

카운터는 보통 Redis에 둡니다. Redis(REmote DIctionary Server)는 데이터를 주로 메모리에 저장하는 오픈소스 저장소라 읽기·쓰기가 매우 빠르고, 만료 시간(TTL)도 지원합니다.

제한에 걸린 요청에는 HTTP 429(Too Many Requests)를 돌려주고, 남은 요청 수나 재시도 가능 시간을 헤더로 알려줍니다. 분산 환경에서는 여러 서버가 카운터를 동시에 갱신하는 경쟁 조건과, 서버 간 카운터 동기화 문제를 해결해야 합니다.

가장 인상 깊었던 건 정답은 없다는 부분이었어요. 회사의 기술 스택, 인력, 우선순위, 목표에 따라 선택이 달라집니다. 클라이언트 쪽에서도 캐시를 활용해 API 호출 횟수를 줄이고, 짧은 시간에 너무 많이 요청하지 않도록 해야 합니다. 예전에 지도 API를 쓰다가 호출 제한에 걸렸던 게 생각났어요 😅

우리 회사는 어떤 방식을 쓰고 있을지도 궁금합니다. 오픈런처럼 사람이 몰리는 화면과 일반 화면은 전략이 다를 것 같거든요.

5장. 안정 해시 설계

요청과 데이터를 여러 서버에 어떻게 균등하게 나눌 것인가에 대한 장입니다.

일반 해시의 문제: 서버 번호 = hash(key) % 서버 수 방식은 서버가 하나 추가되거나 빠지면 나머지 연산 결과가 거의 전부 바뀝니다. 대부분의 키가 다른 서버로 재배치되고, 캐시라면 대규모 캐시 미스가 터집니다.

안정 해시(consistent hashing)

  • 해시 값의 범위를 원형 링(해시 링)으로 만들고, 서버와 키를 모두 링 위에 배치한다.
  • 키는 링을 시계 방향으로 돌다가 처음 만나는 서버에 저장된다.
  • 서버가 추가·삭제되면 그 서버 근처의 키만 재배치된다. 테이블 크기가 바뀔 때 평균적으로 k/n개의 키만 옮기면 된다. (k = 키 개수, n = 서버 개수)

가상 노드

  • 서버가 몇 대 안 되면 링 위의 구간 크기가 제각각이라 데이터가 한쪽으로 쏠린다.
  • 서버 하나를 여러 개의 가상 노드로 링에 흩어 배치하면 표준편차가 줄어 데이터가 고르게 분포한다.
  • 다만 가상 노드 정보를 저장할 공간이 더 필요하니 개수는 적절히 조절해야 한다.

얻는 것

  • 서버 추가·삭제 시 재배치되는 키가 최소화된다.
  • 데이터가 균등하게 분포해 수평적 확장이 쉬워진다.
  • 핫스팟 키 문제를 완화한다. (유명인의 데이터가 한 샤드에 몰리는 것처럼, 특정 샤드에 접근이 집중되어 과부하가 생기는 문제)

아마존 다이나모DB, 아파치 카산드라, 디스코드 채팅 등이 이 방식을 씁니다. 세상에 똑똑한 사람들이 참 많다는 생각이 들었어요. 스터디에서 이야기했던 핫스팟 키 문제를 이렇게 풀 수 있구나 싶었습니다.

6장. 키-값 저장소 설계

1편에서 내용도 제일 많고 제일 어려웠던 장입니다. 앞 장에서 배운 개념들이 총동원됩니다.

CAP 정리

분산 시스템은 아래 세 가지를 동시에 모두 만족할 수 없습니다.

  • 일관성(Consistency): 어느 노드에 접속하든 같은 데이터를 본다.
  • 가용성(Availability): 일부 노드에 장애가 나도 항상 응답을 받는다.
  • 파티션 감내(Partition tolerance): 노드 간 통신이 끊겨도 시스템은 계속 동작한다.

실제 분산 환경에서 네트워크 장애는 피할 수 없으니, 사실상 일관성(CP)과 가용성(AP) 중 무엇을 포기할지 고르는 문제입니다. 은행처럼 잘못된 잔액을 보여주면 안 되는 시스템은 CP, 잠깐 오래된 데이터를 보여줘도 되는 SNS는 AP를 택합니다.

핵심 컴포넌트

  • 데이터 파티션: 5장의 안정 해시로 데이터를 여러 서버에 나눈다.
  • 데이터 다중화: 해시 링을 따라 N개의 서버에 복제본을 둔다. 여러 데이터센터에 걸쳐 두면 데이터센터 단위 장애에도 버틴다.
  • 정족수 합의(Quorum): N = 복제본 수, W = 쓰기 성공으로 인정할 응답 수, R = 읽기 성공으로 인정할 응답 수. W + R이 N보다 크면 강한 일관성이 보장된다. R이나 W를 1로 두면 빠르지만 일관성이 약해진다.
  • 일관성 모델: 강한 일관성, 약한 일관성, 최종 일관성(eventual consistency). 다이나모나 카산드라는 최종 일관성을 택한다.
  • 비일관성 해소 - 벡터 시계: 데이터 버전마다 [서버, 버전] 쌍을 달아서 어떤 버전이 이전 버전인지, 서로 충돌하는지 판단한다.

벡터 시계를 쓰면 "클라이언트"에 충돌 해소 로직이 들어가야 한다는데, 처음엔 웹·앱을 말하는 줄 알았어요. 여기서 클라이언트는 저장소를 호출하는 애플리케이션 계층을 뜻합니다.

장애 처리

  • 장애 감지 - 가십 프로토콜: 각 노드가 멤버들의 박동 카운터(heartbeat) 목록을 갖고, 주기적으로 자기 카운터를 올리며 무작위 노드들과 목록을 주고받는다. 일정 시간 카운터가 갱신되지 않은 노드는 장애(offline)로 간주한다.
  • 일시적 장애 - 느슨한 정족수와 단서 후 임시 위탁(hinted handoff): 장애 난 서버 대신 다른 서버가 잠시 요청을 처리하고, 복구되면 변경분을 일괄 반영한다.
  • 영구적 장애 - 반-엔트로피 프로토콜: 머클 트리로 복제본 간 차이 나는 부분만 빠르게 찾아 동기화한다.
  • 데이터센터 장애: 여러 데이터센터에 다중화한다.

쓰기·읽기 경로

  • 쓰기: 커밋 로그에 먼저 기록 → 메모리 캐시에 저장 → 캐시가 차면 디스크의 SSTable로 내린다.
  • 읽기: 메모리에 있으면 바로 반환, 없으면 블룸 필터로 어느 SSTable에 있을지 좁혀서 읽는다.

세 가지를 모두 만족할 수 없으니 어떤 시스템인지에 따라 무엇을 포기할지 정해야 한다는 게 가장 크게 남았습니다. 인프라 쪽 이야기가 많아서, SRE나 서버 개발자분들께 실제로 이런 걸 얼마나 직접 다루는지 물어보고 싶어졌어요.

이 장을 읽고 나서 EC2에 올려둔 개인 프로젝트에 CodeDeploy로 배포 파이프라인을 붙여, 실제 서비스처럼 배포 구조를 잡아보기도 했습니다.

7장. 분산 시스템을 위한 유일 ID 생성기 설계

요구사항은 ID가 유일하고, 숫자로만 구성되고, 64비트 안에 들어가고, 시간순으로 정렬 가능하며, 초당 1만 개 이상 만들 수 있어야 한다는 것입니다.

선택지 비교

방식장점단점
다중 마스터 복제 (auto_increment를 k씩 증가)DB 기능 그대로 사용데이터센터 여러 곳에 걸치기 어렵고, 서버 추가·삭제가 어려우며, 시간순 정렬이 안 됨
UUID서버 간 조율 없이 생성128비트로 길고, 숫자만이 아니며, 시간순 정렬 불가
티켓 서버구현이 쉽고 숫자 ID티켓 서버가 SPOF
트위터 스노플레이크모든 요구사항 충족서버 간 시계 동기화 필요

auto_increment는 DB 서버가 여러 대면 쓸 수 없고, 다중 마스터 복제로 해결할 수는 있지만 단점이 너무 큽니다.

스노플레이크 구조 (64비트)

  • 사인 비트 (1비트): 나중을 위해 남겨둠
  • 타임스탬프 (41비트): 기준 시각 이후 경과한 밀리초
  • 데이터센터 ID (5비트): 최대 32개
  • 서버 ID (5비트): 데이터센터당 최대 32대
  • 일련번호 (12비트): 같은 밀리초 안에서 1씩 증가, 밀리초가 바뀌면 0으로 초기화

타임스탬프가 앞에 있어서 ID를 정렬하면 곧 시간순 정렬이 됩니다. 41비트로 표현할 수 있는 최댓값은 약 69년이라, 그 뒤엔 기준 시각을 바꾸거나 ID 체계를 옮겨야 합니다.

상황에 맞게 각 절의 길이를 조정하는 게 효과적입니다. 동시성이 낮고 수명이 긴 애플리케이션이라면 일련번호 절을 줄이고 타임스탬프 절을 늘리는 식이죠. 여러 서버의 시계가 어긋나는 문제는 NTP로 동기화해 해결합니다.

8장. URL 단축기 설계

https://긴주소/...를 https://tinyurl.com/zn9edcu 같은 짧은 주소로 바꾸고, 짧은 주소로 접속하면 원래 주소로 리디렉션합니다.

301 vs 302 리디렉션

  • 301(영구 이동): 브라우저가 결과를 캐시해서 다음부터 단축 서버를 거치지 않는다. 서버 부하가 줄어든다.
  • 302(일시 이동): 매번 단축 서버를 거친다. 클릭 수 같은 트래픽 분석이 중요할 때 쓴다.

단축 URL 길이 정하기 단축 URL은 숫자 10개와 알파벳 대·소문자 52개, 총 62개 문자로 구성됩니다. 하루 1억 개씩 10년 운영하면 약 3,650억 개가 필요하고, 62^7 ≈ 3.5조이므로 7자리면 충분합니다. 몇 년을 운영할지도 대략 예상하고 설계해야 한다는 게 인상적이었어요.

방법 1. 해시 후 충돌 해소

  1. CRC32, MD5, SHA-1 등으로 해시를 만들면 7자리보다 길다.
  2. 앞의 7글자만 쓰면 충돌이 생긴다.
  3. 충돌이 해소될 때까지 미리 정한 문자열을 덧붙여 다시 해시한다.
  4. 충돌 여부를 매번 DB에 질의해야 해서 오버헤드가 크다.
  5. 블룸 필터(어떤 값이 집합에 "없다"는 걸 확실히 알려주는, 확률론 기반의 공간 효율적인 자료구조)로 DB 질의를 줄인다.

방법 2. base-62 변환

  1. 7장의 유일 ID 생성기로 ID를 발급한다.
  2. ID를 62진법으로 변환해 단축 URL로 쓴다.
  • 충돌이 원천적으로 없고 구현이 단순하다.
  • 다만 ID가 1씩 증가하면 다음 단축 URL을 쉽게 추측할 수 있다는 보안상 약점이 있다.

9장. 웹 크롤러 설계

웹 크롤러는 웹 페이지를 수집해서 검색 엔진 색인, 웹 아카이빙, 데이터 마이닝 등에 씁니다.

기본 흐름: 시작 URL 집합 → 미수집 URL 저장소 → HTML 다운로더(DNS 조회 포함) → 콘텐츠 파서 → 중복 콘텐츠 확인 → 링크 추출 → URL 필터 → 이미 방문한 URL인지 확인 → 다시 미수집 URL 저장소로

BFS를 쓰는 이유: 웹은 거대한 그래프라 DFS로 파고들면 끝없이 깊어질 수 있어서 BFS를 씁니다. 다만 한 페이지의 링크는 대부분 같은 서버를 가리켜서, 그냥 BFS를 하면 한 서버에 요청이 몰려 사실상 공격이 되어버립니다.

미수집 URL 저장소의 역할

  • 예의(politeness): 호스트별로 큐를 나누고, 한 큐는 하나의 작업 스레드가 적당한 간격을 두고 처리한다. 같은 서버에 동시에 많은 요청을 보내지 않는다.
  • 우선순위: 모든 웹페이지가 같은 가치를 갖는 건 아니다. 순위 결정 장치가 페이지랭크, 트래픽, 갱신 빈도 등을 보고 URL을 우선순위별 큐(상·중·하)에 나눠 넣고, 큐 선택기가 우선순위가 높은 큐에서 더 자주 꺼낸다.
  • 신선도: 변경 이력을 보고 자주 바뀌는 중요한 페이지를 더 자주 다시 수집한다.

robots.txt는 웹사이트가 크롤러와 소통하는 표준 방법으로, 크롤러가 수집해도 되는 페이지와 안 되는 페이지가 적혀 있습니다. 매번 받지 않고 캐시해둡니다.

성능과 안정성

  • DNS 조회는 보통 10~200ms가 걸리고 동기적으로 동작하는 경우가 많아 병목이 된다. DNS 결과를 캐시해둔다.
  • 응답이 느리거나 없는 서버는 타임아웃을 둬서 적당히 손절하고 넘어간다.
  • 무한히 깊은 디렉터리 구조를 만드는 거미 덫(spider trap) 을 피하기 위해 URL 최대 길이를 제한한다.
  • 같은 콘텐츠가 여러 URL에 있는 경우가 많으니 해시값으로 중복 콘텐츠를 걸러낸다.

10장. 알림 시스템 설계

모바일 푸시, SMS, 이메일 알림을 보내는 시스템입니다. iOS 개발을 하면서 늘 받는 쪽이었는데, 보내는 쪽 구조를 처음 제대로 봤어요.

  • 알림 유형별 제3자 서비스: iOS는 APNs, 안드로이드는 FCM, SMS는 트윌리오 같은 서비스, 이메일은 센드그리드 같은 서비스를 거친다. 나중에 서비스를 바꾸거나 추가하기 쉽게 확장성 있게 설계해야 한다.
  • 메시지 큐로 분리: 알림 서버가 알림 유형별 큐에 메시지를 넣고, 작업 서버가 꺼내서 제3자 서비스로 보낸다. APNs에 장애가 나도 SMS나 이메일은 영향을 받지 않고, 트래픽이 몰려도 큐가 버퍼 역할을 한다.
  • 안정성: 알림은 소실되면 안 되므로 알림 로그를 DB에 보관하고 재시도 메커니즘을 둔다.
  • 중복 전송: 분산 시스템 특성상 같은 알림이 중복 전송되는 걸 완전히 막을 순 없다. 이벤트 ID로 이미 보낸 알림인지 확인해 빈도를 줄인다.
  • 그 밖에: 사용자별 알림 수신 설정 확인, 알림 템플릿, 전송 빈도 제한(4장의 처리율 제한), 인증된 클라이언트만 알림을 보낼 수 있게 하는 보안, 전송률·클릭률 추적 등을 함께 고려한다.

11장. 뉴스 피드 시스템 설계

페이스북 뉴스 피드처럼, 친구들의 새 글을 모아 보여주는 시스템입니다. 크게 피드 발행과 피드 읽기 두 흐름으로 나뉩니다.

팬아웃(fanout): 어떤 사용자의 새 글을 친구 관계인 모든 사용자에게 전달하는 과정

방식동작장점단점
쓰기 시점 팬아웃 (push)글을 쓰는 순간 친구들의 피드 캐시에 미리 넣어둠피드 조회가 빠름친구가 많은 사용자는 쓰기 작업이 폭증(핫키 문제). 접속 안 하는 친구 피드까지 갱신하는 낭비
읽기 시점 팬아웃 (pull)피드를 여는 순간 친구들의 최근 글을 모음비활성 사용자에게 자원 낭비 없음, 핫키 문제 없음피드 조회가 느림

그래서 둘을 섞습니다. 대부분의 사용자는 push로 빠르게 조회하게 하고, 팔로워가 아주 많은 유명인의 글은 pull로 읽는 시점에 가져옵니다. 여기에 안정 해시로 요청과 데이터를 고르게 분산해 핫키 문제를 줄입니다.

피드 캐시에는 글 ID와 사용자 ID만 두고, 실제 글 내용과 사용자 정보는 각각의 캐시에서 조회해 조합합니다. 메모리를 아끼기 위해서예요.

12장. 채팅 시스템 설계

1:1 채팅, 그룹 채팅, 접속 상태 표시를 지원하는 채팅 시스템입니다.

서버와 클라이언트는 어떻게 실시간으로 통신할까?

  • 폴링: 클라이언트가 주기적으로 새 메시지가 있는지 묻는다. 대부분 "없음" 응답이라 자원 낭비가 크다.
  • 롱 폴링: 새 메시지가 올 때까지 연결을 열어두다가 응답한다. 메시지를 받을 서버와 연결된 서버가 다를 수 있고, 사용자가 연결을 끊었는지 알기 어렵다.
  • 웹소켓: 한 번 연결하면 양방향으로 계속 통신한다. 서버에서 클라이언트로 먼저 보낼 수 있어 채팅에 가장 적합하다.

구조

  • 채팅 서비스(웹소켓, 유상태)와 로그인·프로필 같은 일반 API 서비스(무상태)를 분리한다.
  • 접속 상태는 박동(heartbeat) 검사로 관리한다. 일정 시간 박동이 안 오면 오프라인으로 바꿔, 잠깐 끊겼다 붙는 경우마다 상태가 깜빡이지 않게 한다.

저장소는 왜 키-값 저장소(NoSQL)인가?

  • 채팅 데이터는 양이 엄청나게 많고, 대부분 최근 메시지만 조회한다.
  • 수평적 확장이 쉽고 데이터 접근 지연이 낮다.
  • 페이스북 메신저는 HBase, 디스코드는 카산드라를 쓴다.

메시지 ID는 순서를 보장해야 하는데, 7장의 스노플레이크 같은 전역 ID 생성기를 쓰거나, 채널 안에서만 순서가 맞으면 되니 채널별 지역 ID를 쓰는 방법도 있습니다.

13장. 검색어 자동완성 시스템

검색창에 글자를 입력할 때마다 인기 검색어 상위 5개를 보여주는 시스템입니다. 입력하는 속도보다 빨라야 하니 100ms 이내 응답이 목표입니다.

트라이(trie): 문자열을 꺼내는(retrieval) 연산에 초점을 맞춰 설계된 트리 자료구조입니다. 루트는 빈 문자열이고, 각 노드는 글자 하나를 나타내며, 경로를 따라가면 접두어가 됩니다.

최적화

  • 접두어 최대 길이 제한: 사람들이 검색창에 긴 문자열을 치는 경우는 드무니 접두어 길이를 제한해 탐색 비용을 줄인다.
  • 노드에 인기 검색어 캐시: 각 노드에 그 접두어로 시작하는 인기 검색어 상위 k개를 미리 저장해두면, 접두어 노드만 찾으면 바로 답을 줄 수 있다. 공간을 더 쓰는 대신 속도를 얻는 트레이드오프다.

데이터 수집: 검색할 때마다 트라이를 갱신하면 너무 비쌉니다. 검색 로그를 모아 주 단위 같은 주기로 트라이를 새로 만들고, 조회 쪽은 캐시에서 빠르게 응답합니다. 트라이가 너무 커지면 첫 글자 기준 등으로 샤딩합니다.

클라이언트에서도 결과를 브라우저 캐시에 저장해두면 같은 접두어 요청을 줄일 수 있습니다.

14장. 유튜브 설계

영상 업로드와 스트리밍을 지원하는 시스템입니다.

비디오 트랜스코딩 업로드된 원본 영상은 그대로 스트리밍하기엔 용량이 크고, 기기·네트워크마다 지원하는 포맷과 적절한 화질이 다릅니다. 그래서 여러 포맷과 해상도로 변환(트랜스코딩)해두고, 네트워크 상태에 맞춰 화질을 바꿔가며 보여줍니다.

  • 트랜스코딩 작업은 DAG(방향성 비순환 그래프) 로 정의해서, 영상·오디오·메타데이터 처리 같은 작업을 단계별로 병렬 실행한다.
  • 영상을 GOP 단위의 작은 조각으로 분할해서 병렬로 업로드·처리하면 속도가 빨라지고, 업로드가 중간에 실패해도 실패한 조각만 다시 보내면 된다.
  • 업로드 센터를 사용자와 가까운 곳에 두고, 단계 사이에 메시지 큐를 둬서 결합도를 낮춘다.
  • 클라우드 저장소에 올릴 수 있는 미리 사인된 URL(pre-signed URL) 을 발급해 인가된 사용자만 업로드하게 한다.

CDN 비용 최적화 CDN은 빠르지만 비쌉니다. 조회 수는 롱테일 분포라서 소수의 인기 영상에 트래픽이 몰립니다.

  • 인기 있는 비디오는 CDN으로, 조회 수가 적은 비디오는 자체 비디오 서버에서 재생해 비용을 아낀다.
  • 짧은 영상은 필요할 때 트랜스코딩하고, 지역에서만 인기 있는 영상은 다른 지역 CDN에 올리지 않는다.

15장. 구글 드라이브 설계

파일 업로드·다운로드, 여러 기기 간 동기화, 수정 알림, 파일 공유를 지원합니다.

블록 저장소

  • 파일을 블록(최대 4MB 정도) 단위로 나눠 저장하고, 블록마다 해시값을 붙인다.
  • 파일이 수정되면 바뀐 블록만 다시 올린다(델타 동기화). 대역폭을 크게 아낄 수 있다.
  • 블록은 파일 유형에 따라 다른 압축 알고리즘으로 압축하고, 암호화한 뒤 S3 같은 클라우드 저장소에 올린다.
  • S3는 여러 지역에 다중화해 데이터 손실을 막는다.

결국 이것도 쪼개서 저장하는 게 핵심이었어요. 14장 유튜브와도 닮았습니다.

메타데이터 DB 파일·블록·버전 정보는 일관성이 중요하므로 ACID를 보장하는 관계형 DB를 씁니다. 여러 기기에서 같은 파일을 보고 있을 때 서로 다른 버전을 보면 안 되니까요.

동기화 충돌 두 사용자가 같은 파일을 동시에 수정하면, 먼저 처리된 쪽이 이기고 나중 쪽은 충돌로 표시해서 사용자가 두 버전 중 선택하거나 병합하게 합니다.

알림 서비스 - 롱 폴링을 택한 이유 파일이 바뀌었다는 걸 다른 기기에 알려야 하는데, 웹소켓 대신 롱 폴링을 씁니다. 알림은 서버에서 클라이언트로 가는 단방향이면 충분하고, 자주 발생하지도 않으며, 파일이 수정됐다는 사실만 감지하면 되기 때문입니다.

저장 공간 절약

  • 같은 해시값의 블록은 중복 저장하지 않는다.
  • 보관할 버전 개수에 상한을 두고, 자주 바뀌는 파일의 버전이 무한히 쌓이지 않게 한다.
  • 자주 쓰지 않는 데이터는 S3 글래시어 같은 저렴한 아카이빙 저장소로 옮긴다.

마치며

챕터를 넘길수록 로드밸런서, 캐시, 안정 해시, 다중화, 메시지 큐 같은 개념이 반복해서 등장했습니다. 결국 모든 설계가 어떻게 나누고, 어떻게 복제하고, 무엇을 포기할 것인가의 조합이라는 게 가장 크게 남았어요.

스터디를 하면서 남긴 액션 아이템도 적어둡니다.

  • 사내 서비스에서 처리율 제한이 어떻게 구성되어 있는지 확인해보기
  • 책에 나온 개념을 실제로 누가 담당하는지, AWS 같은 관리형 서비스가 어디까지 해주는지 알아보기
  • 개인 프로젝트에 CodeDeploy 적용해서 실 서비스처럼 구조 만들기 (하나는 완료!)
  • 도커 공부

2편 정리는 다음 글에서 이어집니다.

© 2026 by sweepty. All rights reserved.
Theme by LekoArts