43강
설계 과제 — URL 단축 서비스
6단계 방법을 처음으로 끝까지 적용한다. 그리고 인기 링크가 왜 문제가 아니라 해법인지 본다
먼저 보면 좋은 강의: 웹 요청의 해부, 지연 시간과 처리량, 시스템 설계 면접 6단계 방법
단계별로 보기
스페이스바로 재생/정지, 좌우 화살표로 단계 이동, R로 처음으로 돌아갑니다.
대기 중1 / 4 단계 — 가장 단순한 구조로 시작한다
서버 하나, 데이터베이스 하나. 여기서 시작하는 게 맞습니다. 부품을 미리 넣어두고 시작하는 게 아니라, 숫자가 강제할 때만 더하는 것입니다. 그런데 초당 150건이 들어옵니다. 서버 하나는 40건, 데이터베이스는 30건입니다. 숫자가 이미 두 곳에서 안 맞습니다.
이 강의를 마치면
- 6단계 방법을 실제 문제에 처음부터 끝까지 적용한다.
- 읽기와 쓰기의 비율이 설계 전체를 어떻게 지배하는지 설명한다.
- 요청이 소수의 인기 링크에 몰리는 성질(쏠림)이 캐시를 성립시키는 이유를 설명한다.
- 로드밸런서·서버 여러 대·캐시를 각각 어떤 숫자가 강제했는지 근거를 대며 설명한다.
왜 이 문제부터인가
URL 단축 서비스는 설계 연습의 고전입니다. 이유가 있습니다. 기능은 초등학생도 설명할 수 있을 만큼 단순한데, 규모를 붙이는 순간 진짜 설계 문제가 되기 때문입니다.
기능은 이게 전부입니다.
- 긴 주소를 주면 짧은 주소를 만들어 준다
- 짧은 주소를 누르면 원래 주소로 보내준다
끝입니다. 그런데 이걸 하루 1억 번 하려면? 그때부터 이야기가 달라집니다.
앞 강의의 6단계를 그대로 돌려 봅시다.
---
1단계 — 무엇을 만드는가
- 기능: 주소 등록, 짧은 주소로 이동. 이 둘뿐입니다.
- 품질: 이동은 200밀리초 안에 끝나야 합니다. 사용자가 링크를 눌렀는데
한참 하얀 화면이면 안 됩니다. 그리고 짧은 주소가 절대 엉뚱한 곳으로 가면 안 됩니다. 이건 타협 불가입니다.
- 범위 밖: 클릭 통계, 사용자 계정, 원하는 문구로 주소 만들기. 안 합니다.
범위를 잘라내는 이 한 줄이 나중에 "그럼 통계는요?"라는 질문을 통째로 없애줍니다.
2단계 — 얼마나 큰가
숫자를 셉니다.
| 값 | 계산 | |
|---|---|---|
| 새 링크 | 하루 100만 개 | → 초당 약 12건 (쓰기) |
| 클릭 | 링크당 평균 100번 | → 하루 1억 번 → 초당 약 1,200건 (읽기) |
| 비율 | 읽기 : 쓰기 = 100 : 1 | |
| 저장 | 5년치 18억 개 × 500바이트 | ≈ 1TB |
두 줄이 결정적입니다.
첫째, 이건 읽기 시스템입니다. 쓰기를 최적화하는 데 시간을 쓰면 안 됩니다. 초당 12건은 낡은 노트북도 처리합니다.
둘째, 1TB는 서버 한 대에 들어갑니다. 그러니 샤딩(데이터를 여러 대에 쪼개 나누는 것)은 필요 없습니다. 숫자를 세지 않았다면 "18억 개면 크니까 샤딩해야지"라고 했을 것이고, 아무 이유 없이 시스템을 열 배 복잡하게 만들었을 것입니다.
규모 추정은 부품을 더하기 위해서만 하는 게 아닙니다. 안 넣어도 된다는 걸 증명하기 위해서도 합니다.
3단계 — 계약
POST /urls {longUrl} → {shortCode}
GET /{shortCode} → 원래 주소로 이동urls: shortCode(키) → longUrl, createdAt이 데이터 모델을 보세요. 키 하나를 주면 값 하나가 나옵니다. 조인도, 검색도, 정렬도, 범위 질의도 없습니다.
이건 대단히 좋은 소식입니다. 이렇게 단순한 접근 패턴은 캐시가 완벽하게 들어맞습니다. 캐시는 원래 "키 → 값" 상자니까요. 만약 매번 복잡한 검색을 해야 했다면 캐시로는 어림도 없었을 것입니다.
4단계 — 큰 그림
가장 단순한 것부터 그리고, 숫자가 강제할 때만 부품을 더합니다.
사용자 → 서버 → 데이터베이스숫자를 대봅시다.
"읽기가 초당 1,200건인데 서버 하나는 40건이다." → 서버를 여러 대 두고, 앞에서 나눠줄 로드밸런서가 필요합니다.
"데이터베이스는 초당 30건이다." → 1,200건은 커녕 40배 부족합니다. 서버를 아무리 늘려도 뒤에서 데이터베이스가 막습니다. 데이터베이스 앞에 캐시가 반드시 있어야 합니다.
사용자 → 로드밸런서 → 서버 여러 대 → 캐시 → 데이터베이스선 하나하나에 그것을 강제한 숫자가 붙어 있습니다. 이게 4단계의 목표입니다.
5단계 — 병목: 그런데 캐시가 정말 될까?
여기가 이 강의의 핵심입니다.
데이터베이스는 초당 30건입니다. 읽기는 1,200건입니다. 캐시가 적어도 97.5%를 받아내야 나머지 30건만 데이터베이스로 갑니다.
97.5%라니, 가능한 이야기일까요?
링크는 18억 개입니다. 캐시에 18억 개를 다 넣을 수는 없습니다. 그러면 그냥 데이터베이스를 하나 더 산 것이나 마찬가지입니다.
쏠림이라는 선물
실제 클릭이 18억 개 링크에 고르게 퍼질까요? 전혀 아닙니다.
오늘의 클릭은 방금 SNS에 올라간 링크 몇천 개에 몰립니다. 3년 전에 만든 링크는 오늘 아무도 안 누릅니다. 요청 분포가 지독하게 불공평합니다.
이걸 쏠림이라고 합니다. 그리고 여기서 관점의 전환이 일어납니다.
불공평함은 문제처럼 들리지만, 캐시에게는 최고의 선물입니다.
왜냐하면 인기 링크 몇천 개만 캐시에 넣으면 요청의 95% 이상이 거기서 끝나기 때문입니다. 18억 개 중 0.0001%만 들고 있으면 되는 것입니다.
캐시가 작아도 되는 이유가 이것입니다. 만약 요청이 정말로 18억 개에 고르게 퍼진다면 캐시는 무용지물입니다. 매번 처음 보는 키니까요.
그래도 숫자를 다시 댑시다
- 적중률 95% → 데이터베이스로 가는 건 초당 60건
- 데이터베이스는 30건 → 아직 두 배 부족합니다
그럼 어떻게 할까요? 세 가지 길이 있습니다.
- 캐시를 키워 적중률을 98%로 → 데이터베이스로 24건
- 캐시 유효시간을 늘려 적중률을 올린다 (대신 오래된 값이 더 오래 남습니다)
- 읽기 전용 복제본을 하나 둔다
이게 병목 심화입니다. 숫자를 대고, 안 맞으면 부품을 더하거나 설정을 바꾸고, 다시 셉니다. 감으로 하는 게 아닙니다.
6단계 — 무엇을 포기했는가
- 캐시 유효시간 60초 → 링크를 지워도 최대 60초는 열립니다. 스팸 링크를
즉시 차단해야 한다면 삭제 시 캐시를 지우는 경로를 추가해야 하고, 그만큼 복잡해집니다. 이 서비스에서는 60초를 받아들입니다. 받아들인다는 것을 말하는 것이 중요합니다. 모르는 것과 받아들인 것은 다릅니다.
- 샤딩 안 함 → 1TB는 한 대에 들어갑니다. 10배가 되면 그때 나눕니다.
지금 안 넣는 것이 옳은 선택입니다.
- 짧은 코드를 순번으로 생성 → 단순하고 충돌이 없습니다. 대신 다음 코드를
추측할 수 있습니다. 비밀 링크 서비스가 아니므로 괜찮다고 판단합니다.
---
직접 해보세요
아래 과제에는 가장 단순한 구조가 놓여 있습니다. 서버 한 대, 데이터베이스 한 대. 그리고 초당 150건의 클릭이 쏟아집니다. 클릭은 고르게 오지 않고 인기 링크에 몰립니다.
실행해서 무엇이 먼저 무너지는지 보세요. 그리고 6단계에서 세운 숫자대로 로드밸런서, 서버 여러 대, 캐시를 놓아 보세요.
부품을 놓을 때마다 스스로 물어보세요. "이걸 강제한 숫자가 뭐지?" 답할 수 없는 부품은 빼면 됩니다.
이 강의가 단순화한 것
교육용 모형은 언제나 무언가를 생략합니다. 무엇을 생략했는지 아는 것도 학습의 일부입니다.
전제한 것
- 본문의 초당 1200건은 실제 규모 추정 예시이고, 과제 시뮬레이션은 관찰하기 쉽도록 초당 150건으로 축소했다. 비율 관계(서버 40건, DB 30건)는 그대로 유지된다.
- 짧은 코드를 만드는 방법(순번/해시/충돌 처리)은 본문에서만 다루고 시뮬레이션하지 않는다.
- 인기 링크 쏠림은 지프(Zipf) 분포로 근사한다. 실제 클릭 분포는 시간에 따라 인기 항목 자체가 바뀌지만 여기서는 고정된 것으로 본다.
- 캐시는 유효시간 안에서는 항상 정확한 값을 준다고 본다. 캐시와 데이터베이스가 어긋나는 상황은 다루지 않는다.
- 리다이렉트를 받은 브라우저가 실제 목적지로 다시 요청하는 두 번째 왕복은 모델에 포함하지 않는다.
다루지 않은 것
- 짧은 코드 생성 알고리즘과 충돌 처리, 코드 길이 산정
- 301(영구)과 302(임시) 리다이렉트의 차이와 그것이 클릭 통계에 미치는 영향
- 스팸·악성 링크 차단과 그에 따른 캐시 무효화
- 여러 지역에 걸친 배치와 지역 간 지연
- 쓰기 경로(링크 등록)의 최적화 — 초당 12건이라 문제가 되지 않는다
더 읽을거리: Design a URL Shortener · Zipf's law
이제 직접 만들어 보세요
읽어서 아는 것과 만들어서 아는 것은 다릅니다. 컴포넌트를 배치하고 트래픽을 흘려서 실제로 동작하는지 확인해 보세요.
이 강의에 물어보기
하루 5번이 강의 본문만 근거로 답합니다. 강의에 없는 내용은 지어내지 않습니다.