스터디 노트

Chapter 08

URL 단축기 설계

bit.ly 같은 단축 URL 서비스. 해시 vs base62 인코딩, 충돌 처리, 리다이렉트 방식(301/302), 캐시.

이 챕터의 답

URL 단축기는 3개 부품으로 만든다 — ID + base62 + 캐시

URL 단축기는 “긴 URL ↔ 짧은 키”의 매핑 문제다. 읽기:쓰기 = 10:1로 트래픽이 한쪽에 쏠리기 때문에, 모든 설계 결정이 읽기 경로 최적화로 흐른다. 아래 3개 부품을 조합하면 끝.

  1. 짧은 키 만들기 — 분산 ID 생성기(7장 Snowflake) + base62 인코딩. 충돌 검사 불필요.
  2. 매핑 저장 (쓰기 경로) — (id, shortKey, longUrl)을 DB에 INSERT.
  3. 빠른 조회 (읽기 경로) — Cache-aside 패턴: Hot path는 캐시에서 1ms, Cold path는 DB → 캐시 적재.

왜 어려운가 — 사용 패턴과 규모

단축은 단순해 보이지만 사용 패턴(누가 어떻게 쓰는가) 규모(36.5TB)가 모든 설계를 끌고 간다.

기본 기능

  • 단축 URL 생성 — 긴 URL을 입력하면 훨씬 짧은 URL을 반환
  • URL 리다이렉션 — 짧은 URL로 접속하면 원래 긴 URL로 보내준다
  • 짧은 URL은 숫자·영문 알파벳으로 구성되고, 가능한 짧고 읽기 쉬워야 한다

면접에서 확정할 질문

  • 트래픽 규모 (일일/초당 단축 요청 수, 읽기:쓰기 비율)
  • 짧은 URL의 길이 / 알파벳 종류 / 사용자 정의 별칭(custom alias) 지원?
  • URL 만료 정책 (영구? TTL?)
  • HTTPS 강제? 분석 데이터 수집?

누가 쓰는가 — 단축자와 클릭자

시스템의 부하를 가늠하려면 누가 어떻게 쓰는지부터 명확히 한다. 단축자(URL을 줄이는 사람)와 클릭자(짧은 URL을 클릭하는 사람)는 보통 다른 사람이고, 빈도도 한참 다르다.

단축자 (1번)
longURL 가짐 → 단축 요청 → shortURL 받음 → SNS 공유
두 URL을 모두 안다
클릭자 (N번)
트윗·문자에서 short.ly/xyz 봄 → 클릭만 함
shortURL만 안다

생성은 1번, 클릭은 N번. 그래서 클릭(읽기) 경로가 시스템의 90%를 차지하고, 아래의 “읽기:쓰기 = 10:1”이 자연스럽게 도출된다.

이 챕터의 표준 요구사항

항목계산
일일 단축 URL 생성 수1억 건전제
초당 쓰기 (write QPS)≈ 1,16010⁸ ÷ 86,400초
읽기:쓰기 비율10 : 1전제
초당 읽기 (read QPS)≈ 11,6001,160 × 10
10년간 누적 레코드3,650억10⁸ × 365 × 10
평균 URL 길이100 byte전제
10년 저장 용량≈ 36.5 TB3,650억 × 100B

3,650억 레코드 × 36.5TB는 단일 메모리/단일 DB로 감당이 불가능하다. 자연히 샤딩·캐시·고성능 ID 발급이 필요해진다.

큰 그림 — API · 정책 · 단축 흐름

부품 하나씩 들어가기 전에, 시스템의 외형(API)과 핵심 정책(301 vs 302)을 잡는다. 단축 흐름은 한 장 다이어그램으로.

API 엔드포인트

  • URL 단축POST /api/v1/data/shorten
    • 요청: { longUrl: string }
    • 응답: 단축된 shortUrl
  • URL 리다이렉션GET /api/v1/shortUrl
    • 응답: HTTP 301 또는 302로 longUrl로 redirect

URL 리다이렉션 — 301 vs 302

코드의미캐싱특징
301Permanent Redirect브라우저가 영구 캐싱서버 부담 ↓ · 클릭 분석 불가
302Found (Temporary)매번 서버 거침서버 부담 ↑ · 클릭 분석 가능

서버 부하 절감이 우선이면 301, 클릭 추적·A/B 테스트가 필요하면 302. bit.ly 같은 분석 위주 서비스는 기본적으로 302를 선호한다.

단축 흐름 한눈에

긴 URL이 들어가면 짧은 URL이 나온다. 그 사이의 “변환 로직”이 다음 섹션의 주제다.

입력 (longUrl)
https://example.com/some/very/long/path?with=params
Shortener Service
longUrl → shortKey 변환← 다음 섹션의 주제
DB에 (longUrl ↔ shortKey) 매핑 저장
출력 (shortUrl)
short.ly/2lkCB

부품 ① — 짧은 키 만들기 (ID + base62)

짧은 키는 결국 “긴 URL 또는 정수 ID를 짧은 문자열로 매핑”하는 문제다. 여기에 두 가지 큰 접근법이 있다 — 어떤 길로 갈지가 이 부품의 핵심 결정.

데이터 모델

1단계에서 본 36.5TB는 인메모리에 못 담는다. 관계형 DB의 단순한 테이블이 자연스러운 출발점:

컬럼타입설명
idBIGINT (PK)단조 증가 ID (Snowflake 등)
shortURLVARCHAR(7)base62 인코딩된 짧은 키 (인덱스)
longURLVARCHAR(2048)원래 URL
💡 여기서 자연스럽게 드는 질문

Q. 그럼 longURL은 어떻게 알아내?

A. 이 테이블이 진실의 원천(source of truth)이다. shortKey만 보고 longURL을 “계산”하는 게 아니라, 테이블에서 찾아오는 것(lookup)이다. base62는 정수 ↔ 짧은 문자열 변환만 담당하지, longURL과는 무관하다.

해시 값 길이 — 7자가 정답인 이유

  • 알파벳: [0-9] + [a-z] + [A-Z] = 62자 (= base62)
  • 7자 → 62⁷ ≈ 3.5조 가지
  • 10년치 트래픽(3,650억)을 흡수하고도 충분한 여유. 6자(62⁶ ≈ 568억)는 10년 안에 고갈

해시 함수 후보

해시출력특징
CRC3232 bit빠름, 충돌 잦음 (≈ 42억 가지)
MD5128 bit충돌 거의 없음, 길다 (32 hex)
SHA-1160 bit충돌 더 적음, 더 길다 (40 hex)

어느 쪽이든 출력의 일부(앞 7자)만 잘라 쓰면 충돌이 발생한다. 그래서 “충돌을 어떻게 다룰 것인가”가 핵심.

접근법 ① — 해시 후 충돌 해소

  1. longUrl을 해시 (CRC32/MD5/SHA-1 등)
  2. 앞 7자를 잘라 shortKey로 사용
  3. DB에 같은 shortKey가 있으면 충돌 → longUrl 뒤에 미리 정한 문자열을 붙여 다시 해시 → 반복
  • 장점: 같은 longUrl은 항상 같은 shortKey로 결정적
  • 단점: 매 요청마다 DB 조회로 충돌 검사 → 비쌈. 트래픽 증가에 따라 조회 비용도 증가

접근법 ② — base62 변환 (유일 ID 생성기 사용)

💡 여기서 자연스럽게 드는 질문

Q. 왜 굳이 새로운 ID를 만드나? longURL을 바로 해시하면 안 되나?

A. 접근법 ①처럼 longURL을 직접 해시하면 충돌 검사라는 비싼 단계를 피할 수 없다. 매 요청마다 DB를 뒤져야 한다. 그래서 longURL은 무시하고 유일성이 보장된 ID를 새로 발급받는 쪽이 훨씬 싸다.

  1. 분산 ID 생성기(7장 Snowflake 등)에서 정수 ID 발급
  2. 그 정수를 base62로 변환 → shortKey
  3. 충돌 검사 불필요 (ID 자체가 유일)
  • 장점: 충돌 없음, DB 조회 없음, 빠름
  • 단점: ID가 단조 증가해 다음 키 추측이 쉬움 (보안 우려). 같은 longUrl이라도 매번 다른 shortKey 발급 가능
🚫 흔한 오해 ①

base62는 “해시 결과를 줄이는 압축”이 아니다

base62 = 정수를 짧은 문자열로 바꾸는 진법 변환. 알파벳을 [0-9] 10개에서 [0-9a-zA-Z] 62개로 늘려, 같은 숫자를 더 적은 자릿수로 표현한다. 입력은 해시가 아니라 정수.

🚫 흔한 오해 ②

ID 생성기는 “해시”가 아니다

CRC32 같은 해시는 충돌이 가능. Snowflake는 타임스탬프 + 머신 ID + 일련번호로 결정적 유일성을 보장한다. 확률적이 아니라 설계적으로 충돌이 없으니 DB 검사 단계가 사라진다.

두 접근법 비교

차원① 해시 후 충돌 해소② base62 변환
충돌 처리DB 조회로 매번 검사 필요불필요 (ID 유일)
길이 보장고정 7자ID 크기에 따라 가변 (점점 길어짐)
결정성같은 longUrl → 같은 short호출마다 다른 short 가능
보안 (다음 키 추측)어려움 (해시 기반)쉬움 (단조 증가)
성능충돌 검사로 변동예측 가능, 빠름
필요한 인프라해시 함수만분산 ID 생성기 (7장)

책은 ② base62 변환을 권장한다. 7장에서 만든 ID 생성기를 그대로 재사용할 수 있어 챕터 간 자연스러운 연결.

인터랙티브 — base62 변환 직접 해보기

🔢 Base62 변환기

알파벳 [0-9a-zA-Z] 62자를 사용. 숫자 ↔ base62 양방향 변환. BigInt 기반이라 Snowflake ID 같은 큰 수도 처리한다.

base62 결과
2TX
길이: 3 · 이 길이로 표현 가능한 가짓수: 62^3 238,328
길이별 표현 가능 개수
길이62ⁿ대략
5916,132,832916.1백만
656,800,235,58456.8억
73,521,614,606,2083.5조← 책의 선택
8218,340,105,584,896218.3조
913,537,086,546,263,55213537.1조

부품 ② — 단축 파이프라인 (쓰기 경로)

Shortener 서비스 안에서 일어나는 일을 3단계 변환 파이프라인으로 본다. 각 단계가 받는 값과 내놓는 값이 또렷이 보이도록.

입력
POST /api/v1/data/shorten
{ longUrl: "https://example.com/..." }
분산 ID 생성기에서 unique ID 발급
7장 Snowflake — 64비트 정수
1541815603606036480
정수 ID를 base62로 인코딩
64비트 숫자 → 11자 내외의 짧은 문자열
“1mEpYx7Xy20”
URL DB에 매핑 저장
(id, shortKey, longUrl) 한 행 INSERT
INSERT INTO urls (id, shortKey, longUrl) VALUES (...)
출력 (응답)
200 OK
{ shortUrl: "short.ly/1mEpYx7Xy20" }

핵심: ① ID 생성기가 유일성을 보장 → ② base62로 짧게 만든 뒤 → ③ DB가 진실의 원천. 충돌 검사 불필요.

부품 ③ — Cache-aside 읽기 경로

읽기:쓰기 = 10:1이므로 읽기 경로 최적화가 핵심이다. 답은 “캐시 먼저, DB는 fallback”이라는 cache-aside 패턴. Hot path와 Cold path를 나란히 본다.

요청
GET short.ly/2lkCB
Hot path≈ 90% 요청 · ~1ms
1. Cache 조회
GET cache:2lkCB
✅ 적중
2. 즉시 응답
Cold path≈ 10% 요청 · ~10ms
1. Cache 조회
GET cache:2lkCB
❌ 미스
2. DB 조회
SELECT longUrl WHERE shortKey=...
3. Cache 적재
SET cache:2lkCB = longUrl
→ 다음 요청부터 Hot path 진입
응답
HTTP 302 Found
Location: https://example.com/...

핵심: 캐시는 처음엔 비어있지만, 미스가 한 번 나면 다음부턴 Hot path. 시간이 갈수록 Hot path 비중이 자연히 커지는 게 cache-aside의 장점.

두 방향의 비대칭 — 의도된 설계

단축기(쓰기)와 리다이렉션(읽기)이 동시에 빠를 필요는 없다. 트래픽이 한쪽으로 쏠리기 때문이다.

방향방법속도
shortURL → longURL (리다이렉션)shortKey로 인덱스 조회 + 캐시✅ 빠름 (~1ms)
longURL → shortURL (역검색)longURL 컬럼에 인덱스 없음 → 풀 스캔❌ 사실상 불가
💡 여기서 자연스럽게 드는 질문

Q. longURL을 알아도 shortURL을 찾기는 어렵겠네?

A. 맞다. 인덱스를 한 방향(shortKey)으로만 걸기 때문. 추가로 접근법 ②는 같은 longURL이라도 호출마다 다른 shortURL을 발급할 수 있어 유일한 답이 존재하지 않을 수도 있다. 의도된 비대칭이고, 읽기 패턴(10:1)에 맞춘 설계다.

마무리 — 운영에서 짚을 것들

핵심 설계가 끝났으니, 운영·확장 측면에서 추가로 다룰 주제들을 짚는다.

① 처리율 제한 장치 (Rate Limiter)

  • 악의적 사용자가 짧은 URL을 무한 발급해 ID 공간을 갉아먹는 것 방지
  • IP 기반 / 사용자 기반 처리율 제한 (4장과 연결)

② 웹 서버 규모 확장

  • Shortener·Redirect 서비스를 stateless로 설계 → 로드밸런서 뒤 다중 인스턴스로 수평 확장

③ DB 규모 확장

  • 샤딩: shortKey 또는 id 기준으로 파티셔닝 (5장 컨시스턴트 해싱 활용 가능)
  • 복제: 읽기 분산용 read replica

④ 데이터 분석 솔루션

  • 클릭 수, 출처(Referer), 지역, 디바이스 등 수집 → 데이터 파이프라인 (Kafka → 데이터 웨어하우스)
  • 302 리다이렉션 사용해야 분석 가능 — 트레이드오프 재확인

⑤ 가용성·일관성·안정성

  • 가용성: 멀티 AZ/멀티 리전 배포, 캐시·DB 복제
  • 일관성: 단축 URL 발급은 강한 일관성, 분석 데이터는 결과적 일관성으로 충분
  • 안정성: 단일 장애점 제거, 헬스 체크 + 자동 복구

핵심 한 줄

URL 단축기는 “긴 URL ↔ 짧은 키” 매핑 문제이고, 짧은 키는 분산 ID + base62 인코딩으로 충돌 없이 빠르게 만든다. 이후 모든 확장은 읽기 경로(캐시) 최적화에 집중된다.