๐Ÿ”— Bit.ly ์„ค๊ณ„

Gunhoยท2026๋…„ 1์›” 12์ผ

System Design

๋ชฉ๋ก ๋ณด๊ธฐ
2/4

๐Ÿ”— Bit.ly ์‹œ์Šคํ…œ ์„ค๊ณ„

๊ธด URL์„ ์งง์€ ๋งํฌ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” URL ๋‹จ์ถ• ์„œ๋น„์Šค

๐Ÿค” ์™œ URL ๋‹จ์ถ• ์„œ๋น„์Šค๊ฐ€ ํ•„์š”ํ•œ๊ฐ€?

๋ฌธ์ž ์ˆ˜ ์ œํ•œ

  • ํŠธ์œ„ํ„ฐ, SMS ๋“ฑ ๊ธ€์ž ์ˆ˜ ์ œํ•œ์ด ์žˆ๋Š” ํ”Œ๋žซํผ์—์„œ ์œ ์šฉ
  • ๊ธด URL์ด ๊ณต๊ฐ„์„ ๋งŽ์ด ์ฐจ์ง€ํ•˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐ

๊ฐ€๋…์„ฑ ํ–ฅ์ƒ

  • ์งง๊ณ  ๊ฐ„๊ฒฐํ•œ ๋งํฌ๋กœ ๊ณต์œ ๊ฐ€ ์‰ฌ์›€
  • ๋ณด๊ธฐ ์ข‹๊ณ  ๊ธฐ์–ตํ•˜๊ธฐ ์‰ฌ์šด URL ์ƒ์„ฑ

๋งํฌ ๊ด€๋ฆฌ

  • URL ํด๋ฆญ ์ˆ˜, ์ง€์—ญ, ์‹œ๊ฐ„ ๋“ฑ ๋ถ„์„ ๊ฐ€๋Šฅ
  • ๋งŒ๋ฃŒ ๊ธฐํ•œ ์„ค์ •์œผ๋กœ ์ž„์‹œ ๋งํฌ ๊ด€๋ฆฌ
  • ์ปค์Šคํ…€ ๋ณ„์นญ์œผ๋กœ ๋ธŒ๋žœ๋”ฉ ๊ฐ€๋Šฅ

๋ณด์•ˆ ๋ฐ ์‹ ๋ขฐ

  • ์›๋ณธ URL์„ ์ˆจ๊ฒจ ์ŠคํŒธ์ด๋‚˜ ์•…์„ฑ ๋งํฌ ์ฐจ๋‹จ
  • ๋งํฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๊ธฐ๋Šฅ์œผ๋กœ ์•ˆ์ „์„ฑ ํ™•์ธ

๐Ÿ“‹ ๊ธฐ๋Šฅ ์š”๊ตฌ์‚ฌํ•ญ

URL ๋‹จ์ถ•

  • ๊ธด URL ์ž…๋ ฅ โ†’ ์งง์€ URL ์ƒ์„ฑ
  • ์˜ˆ: https://www.example.com/very/long/url/path โ†’ bit.ly/abc123

์ปค์Šคํ…€ ์˜ต์…˜

  • ์‚ฌ์šฉ์ž ์ง€์ • ๋ณ„์นญ ์„ค์ • ๊ฐ€๋Šฅ
  • URL ๋งŒ๋ฃŒ ๊ธฐํ•œ ์„ค์ • ๊ฐ€๋Šฅ

๋ฆฌ๋””๋ ‰์…˜

  • ์งง์€ URL ์ ‘์† ์‹œ ์›๋ณธ URL๋กœ ์ž๋™ ์ด๋™

๐Ÿ“Š ์‹œ์Šคํ…œ ๊ทœ๋ชจ

  • ์ด ๋‹จ์ถ• URL: 10์–ต ๊ฐœ (10B)
  • ์ผ์ผ ํ™œ์„ฑ ์‚ฌ์šฉ์ž: 1์–ต ๋ช… (100M)

๐Ÿ” ๊ณ„๋žต์น˜ ์ถ”์ •

TPS (Transactions Per Second)

๊ฐ€์ •

  • 1์–ต ๋ช…์˜ ์œ ์ € (10^8)
  • ํ•˜๋ฃจ์— 10๋ฒˆ request
  • Read:Write ๋น„์œจ = 9:1
  • ํ•˜๋ฃจ โ‰ˆ 10^5 ์ดˆ

๊ณ„์‚ฐ

์ด request = 10^8 ร— 10 = 10^9 request/์ผ

์“ฐ๊ธฐ TPS = (10^9 ร— 0.1) / 10^5 = 1,000 TPS
์ฝ๊ธฐ TPS = (10^9 ร— 0.9) / 10^5 = 9,000 TPS

์ €์žฅ์šฉ๋Ÿ‰

๐Ÿ’ก Base64์˜ +์™€ /๋Š” URL์—์„œ ํŠน์ˆ˜ ๋ฌธ์ž๋กœ ์‚ฌ์šฉ๋˜๋ฏ€๋กœ ์ œ์™ธํ•œ 62๊ฐœ ๋ฌธ์ž(0-9, a-z, A-Z) Base62๋กœ ์ธ์ฝ”๋”ฉํ•ฉ๋‹ˆ๋‹ค.

๋‹จ์ถ• URL ์ €์žฅ

  • Base62 ์ธ์ฝ”๋”ฉ: 6์ž ํ•„์š”
  • 10^9 ๊ฐœ URL ร— 6 bytes = 6GB

๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํฌํ•จ

  • ์›๋ณธ URL, ์ƒ์„ฑ์ผ, ๋งŒ๋ฃŒ์ผ, user_id ๋“ฑ
  • ๋ ˆ์ฝ”๋“œ๋‹น ์•ฝ 200 bytes
  • 10^9 ๊ฐœ ร— 200 bytes = 200GB

์š”์•ฝ

ํ•ญ๋ชฉ๊ฐ’
์“ฐ๊ธฐ TPS1,000 TPS
์ฝ๊ธฐ TPS9,000 TPS
๋‹จ์ถ• URL ์ €์žฅ6GB
์ด ์ €์žฅ์šฉ๋Ÿ‰200GB

๐Ÿ“‹ ๋น„๊ธฐ๋Šฅ ์š”๊ตฌ์‚ฌํ•ญ

Scalability & CAP

๋ถ„์‚ฐ ์ฒ˜๋ฆฌ

  • ์ˆ˜ํ‰์  ํ™•์žฅ(Horizontal Scaling) ์ง€์›
  • ๋‹ค์ค‘ ์„œ๋ฒ„ ๊ตฌ์„ฑ์œผ๋กœ ํŠธ๋ž˜ํ”ฝ ๋ถ„์‚ฐ

CAP ์ •๋ฆฌ

  • AP ์šฐ์„  (Availability + Partition Tolerance)
  • Eventual Consistency ํ—ˆ์šฉ
  • ๊ณ ๊ฐ€์šฉ์„ฑ์„ ํ†ตํ•œ ์„œ๋น„์Šค ์•ˆ์ •์„ฑ ํ™•๋ณด

์‘๋‹ต ์‹œ๊ฐ„ ๋ชฉํ‘œ

์ž‘์—… ์œ ํ˜•๋ชฉํ‘œ ์‘๋‹ต ์‹œ๊ฐ„
๋ฆฌ๋””๋ ‰์…˜ (์ฝ๊ธฐ)< 100ms
URL ์ƒ์„ฑ (์“ฐ๊ธฐ)< 200ms

P99 Latency

  • ๋ฆฌ๋””๋ ‰์…˜: < 200ms
  • URL ์ƒ์„ฑ: < 500ms

๐Ÿ—„๏ธ ๋„๋ฉ”์ธ ๋ชจ๋ธ

URL

ColumnTypeDescriptionConstraints
idBIGINTPrimary key, auto-incrementPRIMARY KEY
short_urlVARCHAR(10)Shortened URL code (e.g., "abc123")UNIQUE, NOT NULL, INDEX
original_urlTEXTOriginal long URLNOT NULL
user_idBIGINTUser who created the URLNULLABLE, INDEX
created_atTIMESTAMPCreation timestampNOT NULL, DEFAULT NOW()
expires_atTIMESTAMPExpiration timestampNULLABLE, INDEX
click_countINTNumber of times accessedDEFAULT 0
is_deletedBOOLEANWhether URL is deletedDEFAULT FALSE
is_customBOOLEANWhether short_code is customDEFAULT FALSE

๐Ÿ”Œ API ์„ค๊ณ„

1. URL ์ƒ์„ฑ

POST /api/urls
Content-Type: application/json

Request

{
  "original_url": "https://www.example.com/very/long/url/path",
  "alias": "my-custom-link",  // optional
  "expires_at": "2025-12-31"  // optional
}

Response

201 Created

{
  "short_url": "https://bit.ly/abc123",
  "original_url": "https://www.example.com/very/long/url/path",
  "expires_at": "2025-12-31T23:59:59Z"
}

2. URL ๋ฆฌ๋””๋ ‰์…˜

GET /{short_code}

Response

302 Found
Location: https://www.example.com/very/long/url/path

์š”์•ฝ

MethodEndpointDescription
POST/api/urlsCreate shortened URL
GET/{short_code}Redirect to original URL

๐Ÿ—๏ธ ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜

์ „์ฒด ๊ตฌ์กฐ

์ฒ˜์Œ ์„ค๊ณ„ํ•œ ์‹œ์Šคํ…œ ๊ตฌ์กฐ๋Š” ์œ„์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์ธ Load Balancer, API Servers, Redis Cache, ๊ทธ๋ฆฌ๊ณ  Database๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ตฌ์กฐ์—์„œ๋Š”:

  • Load Balancer๊ฐ€ ํŠธ๋ž˜ํ”ฝ์„ API Server๋“ค๋กœ ๋ถ„์‚ฐ,
  • API Server๊ฐ€ Redis Cache์™€ Database๋ฅผ ์ง์ ‘ ์ฐธ์กฐ,
  • ๋‹จ์ˆœํ•˜๊ณ  ๋ช…ํ™•ํ•œ ๊ตฌ์กฐ๋กœ ๋น ๋ฅธ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์œ„ ๊ตฌ์กฐ์—์„œ๋„ ์ถฉ๋ถ„ํžˆ ํŠธ๋ž˜ํ”ฝ์„ ๊ฐ๋‹นํ•  ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ ๋ณด๋‹ค ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ์ฝ๊ธฐ/์“ฐ๊ธฐ๋ผ๋Š” ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ถ„๋ฆฌ ๋˜ ๋…๋ฆฝ์ ์œผ๋กœ ํ™•์žฅํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ฐœ์„  ๋  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค:

๋ฐœ์ „๋œ ๊ตฌ์กฐ์˜ ํ•ต์‹ฌ ๊ฐœ์„ ์‚ฌํ•ญ:

  • Write Servers์™€ Read Servers์˜ ๋ช…ํ™•ํ•œ ๋ถ„๋ฆฌ: ์“ฐ๊ธฐ์™€ ์ฝ๊ธฐ ์›Œํฌ๋กœ๋“œ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ์Šค์ผ€์ผ ๊ฐ€๋Šฅ
  • Database Replication ๋„์ž…: Primary DB(์“ฐ๊ธฐ)์™€ Read Replicas(์ฝ๊ธฐ) ๋ถ„๋ฆฌ๋กœ DB ๋ถ€ํ•˜ ๋ถ„์‚ฐ
  • ๋” ์ •๊ตํ•œ ์บ์‹ฑ ์ „๋žต: Read Server๊ฐ€ Redis์™€ Read Replicas๋ฅผ ํšจ์œจ์ ์œผ๋กœ ํ™œ์šฉ

์ฃผ์š” ์ปดํฌ๋„ŒํŠธ

1. Load Balancer (๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ)

์—ญํ• 

  • ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์„ ์—ฌ๋Ÿฌ API ์„œ๋ฒ„๋กœ ๋ถ„์‚ฐ
  • ์„œ๋ฒ„ ํ—ฌ์Šค ์ฒดํฌ ๋ฐ ์žฅ์•  ๊ฐ์ง€
  • SSL ์ข…๋ฃŒ ์ฒ˜๋ฆฌ

๋ผ์šฐํŒ…

POST /api/urls โ†’ Write Servers
GET /{shortCode} โ†’ Read Servers

2. API Servers

Write Servers (์“ฐ๊ธฐ ์„œ๋ฒ„)

  • URL ์ƒ์„ฑ ์š”์ฒญ ์ฒ˜๋ฆฌ
  • Primary DB์— ๋ฐ์ดํ„ฐ ์ €์žฅ
  • ๋ชฉํ‘œ: 1,000 TPS

Read Servers (์ฝ๊ธฐ ์„œ๋ฒ„)

  • ๋ฆฌ๋””๋ ‰์…˜ ์š”์ฒญ ์ฒ˜๋ฆฌ
  • Redis ์บ์‹œ ์šฐ์„  ์กฐํšŒ
  • ๋ชฉํ‘œ: 9,000 TPS

3. Redis Cache (์บ์‹ฑ ๊ณ„์ธต)

์—ญํ• 

  • Hot URLs (์ž์ฃผ ์ ‘๊ทผ๋˜๋Š” URL) ์บ์‹ฑ
  • 90%+ ์š”์ฒญ์„ 1-5ms์— ์ฒ˜๋ฆฌ
  • DB ๋ถ€ํ•˜ ๋Œ€ํญ ๊ฐ์†Œ

์บ์‹œ ์ „๋žต

Key: url:{short_code}
Value: {original_url}
TTL: 24์‹œ๊ฐ„

4. Database

Primary DB (Master)

  • ๋ชจ๋“  ์“ฐ๊ธฐ ์ž‘์—… ์ฒ˜๋ฆฌ
  • ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›

Read Replicas (์ฝ๊ธฐ ๋ณต์ œ๋ณธ)

  • Cache miss ์‹œ ์ฝ๊ธฐ ์ฒ˜๋ฆฌ
  • Primary DB ๋ถ€ํ•˜ ๋ถ„์‚ฐ
  • ๋น„๋™๊ธฐ ๋ณต์ œ๋กœ ๋™๊ธฐํ™”

๋ฐ์ดํ„ฐ ํ๋ฆ„

์ฝ๊ธฐ ์š”์ฒญ (๋ฆฌ๋””๋ ‰์…˜)

1. Client โ†’ Load Balancer
2. Load Balancer โ†’ Read Server
3. Read Server โ†’ Redis Cache
   โ”œโ”€ Cache Hit โ†’ ์ฆ‰์‹œ ์‘๋‹ต (1-5ms)
   โ””โ”€ Cache Miss โ†’ Read Replica ์กฐํšŒ (50ms)
4. ์›๋ณธ URL๋กœ 302 ๋ฆฌ๋””๋ ‰์…˜

์“ฐ๊ธฐ ์š”์ฒญ (URL ์ƒ์„ฑ)

1. Client โ†’ Load Balancer
2. Load Balancer โ†’ Write Server
3. Write Server โ†’ Primary DB ์ €์žฅ
4. Write Server โ†’ Redis Cache ์ €์žฅ (์„ ํƒ)
5. Primary DB โ†’ Read Replicas ๋ณต์ œ (๋น„๋™๊ธฐ)
6. ๋‹จ์ถ• URL ์‘๋‹ต

์„ฑ๋Šฅ ์ตœ์ ํ™”

์บ์‹ฑ

  • 90%+ ์š”์ฒญ์„ Redis์—์„œ ์ฒ˜๋ฆฌ
  • ์‘๋‹ต ์‹œ๊ฐ„ < 5ms

DB Replication

  • ์ฝ๊ธฐ ๋ถ€ํ•˜๋ฅผ ์—ฌ๋Ÿฌ Replica๋กœ ๋ถ„์‚ฐ
  • ์ˆ˜ํ‰ ํ™•์žฅ ๊ฐ€๋Šฅ

๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ

  • ํŠธ๋ž˜ํ”ฝ์„ ์—ฌ๋Ÿฌ ์„œ๋ฒ„๋กœ ๋ถ„์‚ฐ
  • ๊ณ ๊ฐ€์šฉ์„ฑ ๋ณด์žฅ

ํ™•์žฅ์„ฑ

์ˆ˜ํ‰ ํ™•์žฅ

  • API ์„œ๋ฒ„ ์ถ”๊ฐ€๋กœ TPS ์ฆ๊ฐ€
  • Read Replica ์ถ”๊ฐ€๋กœ ์ฝ๊ธฐ ์„ฑ๋Šฅ ํ–ฅ์ƒ

๊ณ ๊ฐ€์šฉ์„ฑ

  • ๋‹ค์ค‘ ์„œ๋ฒ„๋กœ ๋‹จ์ผ ์žฅ์• ์  ์ œ๊ฑฐ
  • ์žฅ์•  ์‹œ ์ž๋™ ํŽ˜์ผ์˜ค๋ฒ„

์„ฑ๋Šฅ

  • ์บ์‹ฑ์œผ๋กœ ๋น ๋ฅธ ์‘๋‹ต
  • DB ๋ถ€ํ•˜ ์ตœ์†Œํ™”

๐Ÿค– ๋‹จ์ถ• URL ์•Œ๊ณ ๋ฆฌ์ฆ˜

๋ฐฉ๋ฒ• 1: Hash ํ•จ์ˆ˜

ํ”„๋กœ์„ธ์Šค

์›๋ณธ URL
  โ†“
Hash ํ•จ์ˆ˜ (MD5/SHA256)
  โ†“
์•ž X์ž ์ถ”์ถœ
  โ†“
16์ง„์ˆ˜ โ†’ 10์ง„์ˆ˜ ๋ณ€ํ™˜
  โ†“
Base62 ์ธ์ฝ”๋”ฉ
  โ†“
์ถฉ๋Œ ๊ฒ€์‚ฌ
  โ”œโ”€ ์ถฉ๋Œ ์—†์Œ โ†’ ์ €์žฅ
  โ””โ”€ ์ถฉ๋Œ ๋ฐœ์ƒ โ†’ ๋žœ๋ค ๊ฐ’ ์ถ”๊ฐ€ ํ›„ ์žฌ์‹œ๋„
  

์žฅ์ 

โœ… ๊ฐ™์€ URL์€ ํ•ญ์ƒ ๊ฐ™์€ ๋‹จ์ถ• ์ฝ”๋“œ
โœ… ๋ถ„์‚ฐ ํ™˜๊ฒฝ์— ์ ํ•ฉ
โœ… ๊ตฌํ˜„ ๊ฐ„๋‹จ

๋‹จ์ 

โŒ ์ถฉ๋Œ ๊ฐ€๋Šฅ์„ฑ ์กด์žฌ
โŒ ์ถฉ๋Œ ์‹œ ์žฌ์‹œ๋„ ํ•„์š”
โŒ ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ์‹œ๊ฐ„

๋ฐฉ๋ฒ•2 counter + BASE62 Encoding

ํ”„๋กœ์„ธ์Šค

์š”์ฒญ
โ†“
Counter์—์„œ ๊ณ ์œ  ID ๋ฐœ๊ธ‰
โ†“
Base62 ์ธ์ฝ”๋”ฉ
โ†“
์ €์žฅ (์ถฉ๋Œ ์—†์Œ!)

์„ฑ๋Šฅ

Redis INCR ์„ฑ๋Šฅ: 100,000+ ops/sec
10,000 TPS ์š”๊ตฌ์‚ฌํ•ญ: โœ… ์ถฉ๋ถ„ํžˆ ๊ฐ€๋Šฅ

์žฅ์ 

โœ… ์ถฉ๋Œ ์ ˆ๋Œ€ ์—†์Œ
โœ… ๋น ๋ฅธ ์„ฑ๋Šฅ
โœ… ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ์‹œ๊ฐ„

๋‹จ์ 

โš ๏ธ ์ˆœ์ฐจ์ ์ด๋ผ ์˜ˆ์ธก ๊ฐ€๋Šฅ
โš ๏ธ Redis๊ฐ€ ๋‹จ์ผ ์žฅ์• ์  (Redis Cluster๋กœ ํ•ด๊ฒฐ๊ฐ€๋Šฅ)

ID ๋ฐฐ์น˜๋กœ ๋ฐ›์•„์˜ฌ ๋•Œ์˜ Trade-off

๐Ÿ‘ ์„ฑ๋Šฅ ๊ฐœ์„ 

Redis ํ˜ธ์ถœ 1/N ๊ฐ์†Œ
๋กœ์ปฌ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ฆ‰์‹œ ํ• ๋‹น

๐Ÿ‘Ž ๋น„์šฉ

ID ๋‚ญ๋น„ (์„œ๋ฒ„ ์žฅ์•  ์‹œ ๋ฏธ์‚ฌ์šฉ ID ์†์‹ค)
๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€
ID ์ˆœ์„œ์— Gap ๋ฐœ์ƒ

โš–๏ธ ๋ฐฐ์น˜ ํฌ๊ธฐ๊ฐ€ ํ•ต์‹ฌ: 100~1000๊ฐœ ๊ถŒ์žฅ

๐Ÿ“š ์ฐธ์กฐ

๊ฐ€์ƒ ๋ฉด์ ‘ ์‚ฌ๋ก€๋กœ ๋ฐฐ์šฐ๋Š” ๋Œ€๊ทœ๋ชจ ์‹œ์Šคํ…œ ์„ค๊ณ„ ๊ธฐ์ดˆ
Hello Interview

profile
Hello

0๊ฐœ์˜ ๋Œ“๊ธ€