Rút gọn link
Hiểu đề
Yêu cầu chức năng
- Người dùng có thể dán URL dài và nhận về link ngắn
- Người bấm link ngắn được chuyển hướng tới URL gốc
- Người dùng có thể tự đặt alias (ví dụ sho.rt/sale-t11) và đặt ngày hết hạn
- Người tạo link có thể xem số lượt click theo ngày
Yêu cầu phi chức năng
- Chuyển hướng p95 < 100 ms tính từ lúc request tới server
- Đường redirect sẵn sàng 99,99% (tối đa khoảng 52 phút gián đoạn mỗi năm)
- Mã sinh tự động dài tối đa 7 ký tự và không đoán được link kế tiếp
- Tỷ lệ đọc:ghi khoảng 100:1, thiết kế phải ưu tiên đường đọc
- Số liệu click được phép trễ tối đa 5 phút
Ngoài phạm vi: Quét mã độc và phishing chuyên sâu (chỉ kiểm tra blocklist đơn giản); Sửa URL đích sau khi tạo; Tên miền riêng cho từng khách hàng; Đăng ký, đăng nhập và phân quyền chi tiết.
Thực thể và API
| Method | Endpoint | Ghi chú |
|---|---|---|
| POST | /v1/urls | Body: longUrl, customAlias?, expiresAt?. Trả về code và shortUrl. 409 nếu alias đã có người dùng. |
| GET | /{code} | Trả 302 kèm header Location. 404 nếu không có, 410 nếu đã hết hạn. |
| GET | /v1/urls/{code}/stats | Số click theo ngày, chỉ chủ link được xem. |
| DELETE | /v1/urls/{code} | Chủ link xóa link. Xóa cả bản ghi trong cache. |
Thiết kế tổng thể
Đi lần lượt từng yêu cầu chức năng. Mục tiêu ở đây là chạy đúng, việc tối ưu để dành cho phần đào sâu.
1) Người dùng có thể dán URL dài và nhận về link ngắn
API kiểm tra URL rồi lấy một ID số từ khoảng ID mà server đang giữ. ID được xáo trộn rồi mã hóa base62 thành chuỗi 7 ký tự. Vì mỗi ID chỉ cấp một lần nên không cần kiểm tra trùng. Grokking dùng base-58 (bỏ 0, O, I, l và ký tự +, /) để dễ đọc; khoá học dùng base62 cho mã ngắn hơn một chút.
- 1Client gửi POST /v1/urls với longUrl
- 2API kiểm tra định dạng URL và blocklist tên miền
- 3API lấy ID kế tiếp trong IdRange đang giữ trong bộ nhớ, hết thì xin khoảng mới
- 4Xáo trộn ID bằng một hoán vị cố định rồi mã hóa base62 thành code
- 5INSERT bản ghi ShortUrl vào DB và trả shortUrl cho client
Nguồn: Grokking Modern System Design · ch.34 Design TinyURL ↗ · Design and Deployment of TinyURL · mục "Workflow" (Shortening) và "Encoder for TinyURL" (sequencer 64-bit + bộ mã hóa base-58) ; Grokking Modern System Design · ch.12 Sequencer ↗ · mục "Third solution: using a range handler" ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Evaluation of TinyURL's Design · mục "Unpredictability" ; Chưa có nguồn trong thư viện: hoán vị cố định (xáo trộn ID) và việc dùng base62 thay cho base-58 là lựa chọn của khoá học
2) Người bấm link ngắn được chuyển hướng tới URL gốc
Đây là đường nóng nhất, chiếm khoảng 99% traffic. Server đọc cache trước, chỉ hỏi DB khi cache trượt. Ghi click đi qua queue để không làm chậm redirect.
- 1Trình duyệt gọi GET /{code}
- 2API đọc Redis theo code, trượt thì đọc DB rồi ghi lại vào cache
- 3Kiểm tra expiresAt, đã quá hạn thì trả 410
- 4Trả 302 với Location là longUrl
- 5Đẩy ClickEvent vào queue sau khi đã trả response
Nguồn: Grokking Modern System Design · ch.34 Design TinyURL ↗ · Design and Deployment of TinyURL · mục "Workflow" (Redirection: kiểm tra cache rồi tới database) ; Grokking Modern System Design · ch.16 Distributed Cache ↗ · Background of Distributed Cache · mục "Writing policies" (write-around: ghi vào cache sau lần trượt) ; Node.js in Practice ↗ (.zip) · ch.7 Networking · Technique 51 "Following redirects" (mã 301/302, tr. 158–159) ; Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · mục "Motivation" (Improved performance, Granular scalability)
3) Người dùng có thể tự đặt alias và đặt ngày hết hạn
Alias tự đặt được ghi bằng lệnh insert có điều kiện, DB tự từ chối nếu trùng. Hết hạn được kiểm tra lúc đọc, còn việc xóa hẳn do job chạy nền.
- 1Client gửi customAlias và expiresAt
- 2API kiểm tra alias hợp lệ (4-30 ký tự, có dấu gạch ngang để không trùng không gian mã tự sinh)
- 3INSERT với ràng buộc unique trên code, lỗi trùng thì trả 409
- 4Cache đặt TTL không vượt quá expiresAt
- 5Job hằng đêm xóa các link đã hết hạn quá 30 ngày
Nguồn: Grokking Modern System Design · ch.34 Design TinyURL ↗ · Requirements of TinyURL's Design · mục "Functional requirements" (Custom short links, Expiry time; không tái sử dụng mã hết hạn) ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Design and Deployment of TinyURL · mục "Workflow" (Custom short links) ; MongoDB: The Definitive Guide 2e ↗ (.zip) · ch.5 Indexing · mục "Unique Indexes" (tr. 104) ; Chưa có nguồn trong thư viện: độ dài alias 4-30 ký tự và mốc xóa sau 30 ngày là giả định của khoá học
4) Người tạo link có thể xem số lượt click theo ngày
Click được gom theo lô từ queue và ghi vào kho phân tích. API thống kê chỉ đọc bảng đã cộng sẵn theo ngày nên rất nhẹ.
- 1Worker đọc ClickEvent từ queue theo lô vài nghìn sự kiện
- 2Ghi lô vào kho phân tích và cộng dồn bảng click theo (code, ngày)
- 3GET /v1/urls/{code}/stats đọc bảng cộng dồn
Nguồn: Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · mục "Motivation" (Improved performance, Granular scalability) ; Grokking Modern System Design · ch.24 Sharded Counters ↗ · mục "Burst of writes requests" ; Chưa có nguồn trong thư viện: kho phân tích (BigQuery/ClickHouse) không có sách trong thư viện, chỉ nêu ở mức khái niệm
Hạ tầng theo quy mô
Cùng một sản phẩm, ba mức người dùng. Chọn từng mức để xem sơ đồ, phép tính và cấu hình server thay đổi thế nào.
Hai instance Node.js trên Cloud Run và một Postgres managed. Mã sinh từ sequence của Postgres, click đếm ngay trong DB. Traffic nhỏ nên mọi thứ nằm gọn trong một database.
Ước lượng
- Tạo link: 10.000 link/ngày / 86.400 ≈ 0,12 req/s
- Redirect (đọc:ghi 100:1): 10.000 × 100 = 1.000.000 lượt/ngày / 86.400 ≈ 11,6 req/s, đỉnh ×10 ≈ 116 req/s
- API: ceil(116 / (1.000 × 0,6)) = 1, lấy tối thiểu 2 instance để có dự phòng
- Lưu trữ: 10.000 × 500 B × 365 ≈ 1,8 GB/năm
- Không gian mã: 62^7 ≈ 3.500 tỷ; 5 năm × 3,65 triệu link ≈ 18 triệu, dùng chưa tới 0,001%
Nguồn con số: Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · mục "Examples of Resource Estimation" (Number of servers required, Storage requirements, Bandwidth requirements) ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Requirements of TinyURL's Design · mục "Resource estimation" (tỷ lệ 1:100, 500 B mỗi bản ghi, cache theo 80-20) ; Quy tắc ước lượng của khoá học · /hoc/chon-server (1.000 req/s mỗi máy 2 vCPU, chạy tối đa 60% CPU, Redis ×1,3)
Bảng hạ tầng
| Thành phần | Chọn | Số lượng | Cấu hình | Vì sao |
|---|---|---|---|---|
| Load balancer | LB có sẵn của Cloud Run + TLS managed | 1 (managed) | Mặc định | Không phải tự vận hành, đủ cho vài trăm req/s. |
| API | Node.js (Fastify), Docker, Cloud Run | 2 | 2 vCPU · 4 GB RAM | 116 req/s đỉnh chỉ dùng khoảng 12% sức một instance, cái thứ hai để dự phòng. |
| Database | PostgreSQL managed (Cloud SQL / RDS) | 1 | 2 vCPU · 8 GB · 20 GB SSD | Vài GB dữ liệu và 116 QPS đọc là rất nhẹ với một node có index trên code. |
| Sinh mã | Sequence Postgres + xáo trộn + base62 | Nằm trong DB | BIGSERIAL | Ghi 0,12 req/s nên gọi nextval mỗi lần vẫn thoải mái. |
| Đếm click | Bảng click_daily trong Postgres, UPDATE gộp mỗi 10 s | Nằm trong DB | Gộp trong bộ nhớ API | Gộp lại để không ghi 1 lần cho mỗi lượt bấm. |
| Job dọn link | Cloud Scheduler gọi endpoint nội bộ | 1 job/đêm | Xóa theo lô 1.000 dòng | Lượng link hết hạn mỗi ngày rất nhỏ. |
- Điểm xuất phát
- Không có cache vì DB đủ nhanh ở 116 QPS
- Không có queue, click được gộp trong bộ nhớ rồi UPDATE định kỳ
Khi redirect lên vài nghìn req/s, mỗi lượt đều đọc DB và UPDATE click nên Postgres là thứ quá tải đầu tiên.
Cấu hình và chi phí là ước lượng để học và trình bày trong phỏng vấn. Xem cách tính ở bài Chọn server.
So sánh nhanh ba mức
| Thành phần | Mức 1 | Mức 2 | Mức 3 |
|---|---|---|---|
| Người dùng | 10.000 người tạo link/ngày | 1 triệu người tạo link/ngày | 50 triệu DAU · 10 triệu link mới/ngày |
| Chi phí | ≈ 40–90 USD/tháng | ≈ 1.500–3.500 USD/tháng | ≈ 50.000–100.000 USD/tháng (CDN và ScyllaDB chiếm phần lớn) |
| Load balancer | LB có sẵn của Cloud Run + TLS managed 1 (managed) × Mặc định | L7 LB managed (ALB / Cloud LB) 1 (managed) × Mặc định | Chưa cần |
| API | Node.js (Fastify), Docker, Cloud Run 2 × 2 vCPU · 4 GB RAM | Node.js, container autoscale theo CPU 10 lúc đỉnh × 2 vCPU · 4 GB RAM | Node.js trên Kubernetes, HPA theo CPU 40 pod (20/region) × 2 vCPU · 4 GB |
| Database | PostgreSQL managed (Cloud SQL / RDS) 1 × 2 vCPU · 8 GB · 20 GB SSD | Chưa cần | ScyllaDB, phân mảnh theo hash(code), TTL theo expiresAt 12 node/region, tổng 24 × 16 vCPU · 128 GB · 3,75 TB NVMe |
| Sinh mã | Sequence Postgres + xáo trộn + base62 Nằm trong DB × BIGSERIAL | Sequence tăng 1.000 mỗi lần, API giữ khoảng trong bộ nhớ Nằm trong DB × INCREMENT BY 1000 | Chưa cần |
| Đếm click | Bảng click_daily trong Postgres, UPDATE gộp mỗi 10 s Nằm trong DB × Gộp trong bộ nhớ API | Chưa cần | Chưa cần |
| Job dọn link | Cloud Scheduler gọi endpoint nội bộ 1 job/đêm × Xóa theo lô 1.000 dòng | Chưa cần | Chưa cần |
| Cache | Chưa cần | Redis managed (ElastiCache / Memorystore) 1 primary + 1 replica × 13 GB RAM | Redis Cluster 3 shard × 2 = 6 node/region, tổng 12 × 16 GB RAM mỗi node |
| DB primary | Chưa cần | PostgreSQL managed, HA standby 1 (+1 standby) × 4 vCPU · 16 GB · 1 TB SSD | Chưa cần |
| DB replica | Chưa cần | Postgres read replica 1 × 4 vCPU · 16 GB · 1 TB SSD | Chưa cần |
| Queue click | Chưa cần | Pub/Sub hoặc SQS 1 topic × Managed | Chưa cần |
| Worker | Chưa cần | Node.js consumer ghi theo lô 2 × 1 vCPU · 2 GB | Chưa cần |
| Kho phân tích | Chưa cần | BigQuery hoặc ClickHouse Cloud 1 (managed) × ≈ 10 GB/ngày thô | ClickHouse 3 shard × 2 replica = 6 node × 8 vCPU · 32 GB · 2 TB SSD |
| CDN | Chưa cần | Chưa cần | Cloudflare hoặc CloudFront, cache 302 với max-age 300 s Toàn cầu (managed) × Log stream bật |
| DNS + LB | Chưa cần | Chưa cần | GeoDNS + L7 LB mỗi region 2 region × Active-active |
| Cấp khoảng ID | Chưa cần | Chưa cần | Service nhỏ lưu con trỏ khoảng trong etcd 3 pod/region × 1 vCPU · 1 GB |
| Kafka | Chưa cần | Chưa cần | Kafka managed (MSK / Confluent) 3 broker/region × 12 partition topic clicks |
| Stream worker | Chưa cần | Chưa cần | Consumer Node.js hoặc Flink 6 pod × 2 vCPU · 4 GB |
Đào sâu
Mỗi câu hỏi đi từ cách đơn giản đến cách người phỏng vấn mong đợi ở cấp senior.
1) Làm sao sinh mã ngắn không trùng khi có nhiều server cùng tạo link?
Lấy MD5 của longUrl, mã hóa base62 và giữ 7 ký tự đầu. Khi số link lên hàng tỷ, hai URL khác nhau sẽ cho cùng 7 ký tự. Phải đọc DB kiểm tra trùng rồi thử lại với salt.
Đánh đổi: Đơn giản và cùng URL cho cùng mã, nhưng mỗi lần tạo tốn thêm một lần đọc DB và tỷ lệ va chạm tăng dần theo dữ liệu.
⚠ Chưa có nguồn trong thư viện: sách không phân tích cách hash URL rồi cắt; Grokking Modern System Design · ch.34 Design TinyURL · Encoder for TinyURL dùng ID từ sequencer + base-58 để tránh va chạm thay vì hash
Sinh 7 ký tự ngẫu nhiên từ 62^7 ≈ 3.500 tỷ khả năng. INSERT với unique constraint, trùng thì sinh lại. Với vài tỷ link, xác suất trùng mỗi lần dưới 0,1% nên hiếm khi phải thử lại.
Đánh đổi: Không đoán được và không cần điều phối, nhưng vẫn phụ thuộc DB báo trùng và tỷ lệ thử lại tăng khi không gian mã đầy dần.
Nguồn: Grokking Modern System Design · ch.12 Sequencer ↗ · mục "First solution: UUID" (sinh ngẫu nhiên, vẫn có khả năng trùng) ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Design and Deployment of TinyURL · mục "Design" (MongoDB trả lỗi duplicate-key khi trùng) ; MongoDB: The Definitive Guide 2e ↗ (.zip) · ch.5 Indexing · mục "Unique Indexes" (tr. 104)
Một dịch vụ nhỏ cấp cho mỗi server một khoảng ID, ví dụ 1.000 hoặc 10.000 số. Server tự tăng ID trong bộ nhớ, không gọi mạng. ID đi qua một hoán vị cố định (ví dụ Feistel vài vòng) rồi mã hóa base62, nên mã không trùng và không lộ thứ tự. Ở nhiều region, mỗi region nhận các khoảng rời nhau.
Đánh đổi: Không bao giờ trùng và không cần đọc DB, đổi lại phải vận hành dịch vụ cấp khoảng có HA và chấp nhận mất vài khoảng ID khi server chết.
Nguồn: Grokking Modern System Design · ch.12 Sequencer ↗ · mục "Third solution: using a range handler" (Pros/Cons: mất một khoảng khi server chết) ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Encoder for TinyURL · mục "The scope of the short URL generator" ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Evaluation of TinyURL's Design · mục "Unpredictability" (chọn ngẫu nhiên ID trong khoảng) ; Chưa có nguồn trong thư viện: hoán vị Feistel không có trong sách, sách chỉ đề xuất chọn ngẫu nhiên ID và thêm salt
2) Làm sao redirect nhanh mà vẫn đếm được click? Dùng 301 hay 302?
301 nghĩa là chuyển hẳn, trình duyệt sẽ lưu và lần sau tự đi tới URL gốc mà không gọi lại server. Tải giảm, nhưng các lần bấm sau không còn qua server nên mất số liệu click. Link cũng không thể hết hạn hay bị xóa với người đã từng bấm.
Đánh đổi: Ít tải nhất cho server nhưng mất analytics và mất quyền kiểm soát link.
Nguồn: Node.js in Practice ↗ (.zip) · ch.7 Networking · Technique 51 "Following redirects" (301 Moved permanently: client nên cập nhật URL vĩnh viễn, tr. 158–159) ; Chưa có nguồn trong thư viện: hành vi trình duyệt lưu 301 và hệ quả mất số liệu click
302 là chuyển tạm thời nên mỗi lần bấm đều quay về server, đếm được đủ. Redis giữ các link nóng, theo giả định 80-20 của Grokking, khoảng 20% link tạo ra 80% lượt bấm nên cache trúng phần lớn. Ghi click đẩy vào queue để redirect không phải đợi DB.
Đánh đổi: Đếm đủ và kiểm soát được link, nhưng mọi lượt bấm đều chạy tới server gốc nên chi phí tăng tuyến tính theo traffic.
Nguồn: Node.js in Practice ↗ (.zip) · ch.7 Networking · Technique 51 "Following redirects" (302 Found, tr. 158–159) ; Grokking Modern System Design · ch.16 Distributed Cache ↗ · Background of Distributed Cache · mục "Writing policies" (write-around: ghi vào cache sau lần trượt) ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Requirements of TinyURL's Design · mục "Memory estimation" (giả định 80-20) ; Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · mục "Motivation" (Improved performance, Granular scalability)
Trả 302 với Cache-Control: max-age khoảng 300 s để CDN giữ redirect gần người dùng, còn trình duyệt không cache lâu. Lượt bấm trúng CDN vẫn được đếm qua log stream của CDN đẩy vào Kafka. Khi xóa link hoặc hết hạn thì gọi purge CDN theo code.
Đánh đổi: Server gốc chỉ nhận phần trượt CDN, đổi lại số liệu click trễ vài phút và xóa link có độ trễ tối đa bằng max-age nếu purge lỗi.
Nguồn: Grokking Modern System Design · ch.11 Content Delivery Network (CDN) ↗ · Content Consistency in CDN · mục "Time-to-live (TTL)" ; Grokking Modern System Design · ch.11 Content Delivery Network (CDN) ↗ · mục "Pull CDN" ; Chưa có nguồn trong thư viện: log stream từ CDN vào Kafka và API purge là tính năng của nhà cung cấp CDN
3) Khi số link lên hàng tỷ thì mở rộng đường đọc và lưu trữ thế nào?
Tăng CPU và RAM cho một node mỗi khi chậm. Ổn tới vài trăm GB, nhưng 10 tỷ bản ghi × 500 B là 5 TB, index không còn nằm trong RAM. Node đó cũng là điểm chết duy nhất của toàn bộ redirect.
Đánh đổi: Đơn giản nhất nhưng có trần cứng về dung lượng và độ sẵn sàng.
Nguồn: Grokking Modern System Design · ch.4 Non-functional System Characteristics ↗ · Scalability · mục "Vertical scalability--scaling up" và "Horizontal scalability--scaling out" ; Grokking Modern System Design · ch.9 Databases ↗ · Data Partitioning · mục "Why do we partition data?"
Redis đỡ phần lớn lượt đọc, replica đỡ phần cache trượt, primary chỉ nhận ghi. Với tỷ lệ 100:1 thì primary gần như rảnh. Đủ cho tới khoảng 1 TB dữ liệu.
Đánh đổi: Dễ vận hành và vẫn dùng SQL, nhưng dung lượng vẫn bị giới hạn bởi một node primary.
Nguồn: Grokking Modern System Design · ch.9 Databases ↗ · Data Replication · mục "Single leader/primary-secondary replication" và "Synchronous versus asynchronous replication" ; Grokking Modern System Design · ch.16 Distributed Cache ↗ · Background of Distributed Cache · mục "Writing policies" (write-around: ghi vào cache sau lần trượt) ; Chưa có nguồn trong thư viện: mốc "khoảng 1 TB" là ước lượng của khoá học
Truy cập luôn là lấy theo code nên hợp với kho NoSQL phân mảnh. Grokking chọn MongoDB (có leader, đọc nhanh, báo trùng khóa) thay vì Cassandra/DynamoDB vốn cần đọc theo quorum; ScyllaDB hoặc DynamoDB cũng dùng được nếu chấp nhận nhất quán yếu hơn. Dữ liệu phân mảnh bằng hash của code và tự cân bằng khi thêm node. Bật TTL có sẵn để tự xóa link hết hạn. Mỗi region giữ một bản sao đầy đủ để redirect đọc tại chỗ, Redis Cluster phía trước giữ link nóng.
Đánh đổi: Mở rộng gần như tuyến tính và chịu được mất một region, đổi lại mất truy vấn SQL linh hoạt và replication giữa region là eventual consistency.
Nguồn: Grokking Modern System Design · ch.34 Design TinyURL ↗ · Evaluation of TinyURL's Design · mục "Scalability" (sharding ngang + consistent hashing) ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Design and Deployment of TinyURL · mục "Design" (Why are NoSQL databases like Cassandra or Riak not good choices instead of MongoDB?) ; Grokking Modern System Design · ch.10 Key-value Store ↗ · Design of a Key-value Store · mục "Consistent hashing" và "Use virtual nodes" ; Grokking Modern System Design · ch.9 Databases ↗ · Data Replication · mục "Single leader/primary-secondary replication" và "Synchronous versus asynchronous replication" ; MongoDB: The Definitive Guide 2e ↗ (.zip) · ch.6 Special Index and Collection Types · mục "Time-To-Live Indexes" (tr. 114)
4) Alias tự đặt và hết hạn xử lý thế nào cho đúng?
Đọc xem alias đã có chưa, chưa có thì insert. Hai người gửi cùng alias trong vài ms sẽ cùng thấy trống và người sau ghi đè người trước. Link hết hạn được xóa bằng một lệnh DELETE lớn mỗi đêm, làm DB khựng.
Đánh đổi: Dễ viết nhưng có race condition và job xóa gây tải đột biến.
Nguồn: MongoDB: The Definitive Guide 2e ↗ (.zip) · ch.5 Indexing · mục "Unique Indexes" (tr. 104) ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Design and Deployment of TinyURL · mục "Design" (dựa vào lỗi duplicate-key thay vì đọc trước)
Dùng unique constraint hoặc INSERT ... ON CONFLICT DO NOTHING, DB đảm bảo chỉ một người thắng. Lúc redirect so sánh expiresAt, quá hạn thì trả 410 ngay cả khi bản ghi chưa bị xóa. Job xóa chạy theo lô nhỏ.
Đánh đổi: Đúng và đơn giản, nhưng alias có thể đụng mã tự sinh nếu hai không gian mã dùng chung định dạng.
Nguồn: MongoDB: The Definitive Guide 2e ↗ (.zip) · ch.5 Indexing · mục "Unique Indexes" (tr. 104) ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Requirements of TinyURL's Design · mục "Functional requirements" (Expiry time) ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Design and Deployment of TinyURL · mục "Workflow" (Deletion khi hết hạn)
Mã tự sinh luôn đúng 7 ký tự base62, alias bắt buộc có dấu gạch ngang hoặc độ dài khác, nên hai nhóm không bao giờ va nhau. Ghi bằng conditional write. TTL của bản ghi đặt theo expiresAt để DB tự dọn, TTL cache bằng min(1 giờ, thời gian còn lại). Không tái sử dụng mã đã hết hạn để link cũ trên mạng không trỏ sang nội dung lạ.
Đánh đổi: Không race và không cần job dọn lớn, đổi lại có quy tắc định dạng alias mà người dùng phải theo.
Nguồn: Grokking Modern System Design · ch.34 Design TinyURL ↗ · Requirements of TinyURL's Design · Quiz "we don't reuse the expired short URLs" ; Grokking Modern System Design · ch.34 Design TinyURL ↗ · Design and Deployment of TinyURL · mục "Workflow" (Custom short links, tối đa 11 ký tự) ; MongoDB: The Definitive Guide 2e ↗ (.zip) · ch.6 Special Index and Collection Types · mục "Time-To-Live Indexes" (tr. 114) ; Grokking Modern System Design · ch.16 Distributed Cache ↗ · Background of Distributed Cache · mục "Eviction policies" và "Cache invalidation"
Trong phỏng vấn
- “Tôi chọn 302 để đếm được click và còn thu hồi được link, sau đó bù hiệu năng bằng Redis và CDN cache ngắn hạn.”
- “Mã sinh từ bộ đếm cấp theo khoảng nên không bao giờ trùng, tôi xáo trộn ID trước khi base62 để không ai đoán được link kế tiếp.”
- “Tỷ lệ đọc:ghi khoảng 100:1, nên mọi quyết định đều tối ưu đường redirect và đẩy ghi click ra khỏi đường đó.”
- “Truy cập luôn theo code, nên khi lớn tôi chuyển sang key-value store phân mảnh theo hash(code).”
- Chọn 301 vì nhanh hơn mà quên rằng sẽ mất số liệu click và không thể xóa link
- Dùng bộ đếm tăng dần rồi base62 trực tiếp, lộ tổng số link và cho phép duyệt toàn bộ link
- Ghi click đồng bộ vào DB trên đường redirect
- Kiểm tra alias bằng SELECT rồi INSERT, bị race khi hai người đặt cùng lúc
Đọc thêm
Sách trong thư viện của bạn. Số chương có thể lệch nhẹ so với bản PDF.