🔗 Trận 1: TinyURL – Chuyện chàng lùn gánh gã khổng lồ (URL Shortener) - EP 1
Thiết kế TinyURL như thế nào? Cùng xây dựng một hệ thống rút gọn link từ con số 0
Nếu Facebook, YouTube hay Shopee đều có những đường link dài ngoằng như thế này
https://example.com/product/category/detail?id=182937182937&ref=campaign_summer_2026
thì việc chia sẻ chúng sẽ vô cùng bất tiện.
Đó là lý do những dịch vụ như TinyURL hay Bitly ra đời.
Chúng biến một đường link dài thành một đường link ngắn gọn như:
Nhìn thì có vẻ đơn giản.
Nhưng nếu mỗi ngày có 100 triệu URL mới được tạo, làm thế nào để hệ thống vẫn hoạt động nhanh, không bị trùng mã và có thể mở rộng trong nhiều năm?
Hãy cùng thiết kế hệ thống này như một senior nhé.
Nhưng trước tiên hãy nhớ 4 bước sau:
- 1. Understand the problem and design scope: Hiểu rõ bài toán và thiết lập phạm vi thiết kế
- 2. Propose high-level design: Đề xuất thiết kế cấp độ tổng quan và đạt sự đồng thuận
- 3. Design deep dive: Đi sâu vào thiết kế chi tiết và phân tích các thành phần quan trọng
- 4. Wrap up: Thảo luận các trade-off và cách mở rộng
Bước 1. Trước khi đi vào thiết kế, hãy hiểu mình đang xây cái gì
Một sai lầm rất phổ biến của người mới học System Design là lao ngay vào nghĩ về Database hay Redis.
Thực tế, điều đầu tiên các pháp sư làm luôn là làm rõ bài toán.
Ví dụ, nếu người phỏng vấn nói:
"Hãy thiết kế TinyURL."
Thì sẽ có hàng loạt câu hỏi cần được đặt ra.
- Hệ thống cần làm được những gì?
- Có bao nhiêu người sử dụng?
- Một ngày tạo bao nhiêu URL?
- Có cần thống kê lượt click không?
- URL ngắn có được tùy chỉnh không?
Sau khi trao đổi, giả sử ta thống nhất được yêu cầu như sau:
- Có thể tạo URL ngắn từ URL dài.
- Khi người dùng mở URL ngắn sẽ tự động chuyển về URL gốc.
- Mỗi ngày có khoảng 100 triệu URL mới.
Ước lượng quy mô
Một kỹ năng cực kỳ quan trọng trong System Design là Back-of-the-envelope estimation (ước lượng nhanh).
Thay vì nói "rất nhiều request", ta sẽ thử tính và đưa ra số liệu cụ thể.
Số request ghi
- Một ngày có 100 000 000 URL
- Một ngày = 86 400 giây
Vậy thì → 100 000 000 / 86 400 ≈ 1160 request/giây
Đây gọi là Write QPS.
Số request đọc
Giả sử số người mở link nhiều gấp 10 lần số lần tạo link.
Ta có → Read QPS ≈ 11 600 request/giây
Ngay lập tức ta nhận ra một điều.
Hệ thống chủ yếu là đọc dữ liệu, chứ không phải ghi dữ liệu.
Chi tiết nhỏ này sẽ ảnh hưởng đến toàn bộ thiết kế phía sau.
Bước 2. Hệ thống sẽ hoạt động như thế nào?
Thực ra TinyURL chỉ có hai chức năng chính.
Chức năng đầu tiên: Tạo URL ngắn
Client gửi:
POST /api/v1/data/shorten
# Request Body
{
"longUrl": "https://example.com/very-long-url"
}
Server trả về:
# Response Body
{
"shortUrl": "https://tinyurl.com/2TX8AbC"
}
Đơn giản.
Chức năng thứ hai: Điều hướng
Người dùng mở:
https://tinyurl.com/2TX8AbC
Server tìm URL gốc rồi trả về:
302 Redirect
hoặc
301 Redirect
301 hay 302?
Đây là điểm rất nhiều người mới bỏ qua.
Hai mã trạng thái này đều dùng để chuyển hướng nhưng hành vi lại khác nhau. Điều này khá quan trọng.
301 Moved Permanently
Trình duyệt sẽ nhớ kết quả.
Lần sau người dùng mở cùng một link, trình duyệt sẽ đi thẳng đến URL gốc.
Không cần hỏi TinyURL nữa.
Ưu điểm:
- nhanh
- giảm tải server
302 Found
Trình duyệt không nhớ.
Mỗi lần click đều phải đi qua server. Nghe có vẻ tệ hơn nhưng lại cực kỳ hữu ích.
Vì mỗi lần người dùng mở link, server đều biết.
Ta có thể thống kê:
- bao nhiêu lượt click
- click từ quốc gia nào
- dùng điện thoại hay máy tính
- click lúc mấy giờ
Đó là lý do Bitly thường dùng kiểu này.
Bước 3. URL dài sẽ biến thành URL ngắn bằng cách nào?
Đây mới là phần thú vị nhất.
Ta cần một thuật toán sinh ra chuỗi như:
2TX8AbC
Ý tưởng đầu tiên: Hash URL
Ví dụ dùng:
- MD5
- SHA1
Hash URL thành chuỗi rất dài. Sau đó lấy 7 ký tự đầu.
Nghe hợp lý nhưng có một vấn đề lớn.
Hai URL khác nhau hoàn toàn có thể sinh ra cùng 7 ký tự đầu.
Ví dụ:
abc123X...
và
abc123X...
Nếu chuyện này xảy ra thì hệ thống không biết phải mở URL nào.
Đây gọi là Hash Collision.
Cách xử lý
Server phải hỏi Database:
Chuỗi này đã tồn tại chưa?
Nếu đã có rồi. Hash lại. Nếu vẫn trùng. Hash tiếp.
Có thể phải lặp nhiều lần. Điều đó đồng nghĩa với rất nhiều lần truy cập Database.
Không hiệu quả.
Ý tưởng thứ hai: Đừng hash URL nữa
Đây là cách Alex Xu đánh giá là tối ưu hơn.
Thay vì hash URL.
Ta sinh một ID duy nhất.
Ví dụ:
1
2
3
4
...
11157
Sau đó đổi ID sang hệ Base62.
Ví dụ:
11157 → 2TX
Thế là xong.
Base62 là gì?
Có tất cả:
26 chữ thường + 26 chữ hoa + 10 chữ số = 62 ký tự
Ví dụ:
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
Mỗi ID sẽ được đổi sang bảng ký tự này.
Giống như việc đổi:
255 → FF
trong hệ Hex.
Chỉ khác là bây giờ ta đổi sang Base62.
Vì sao cách này tốt hơn?
Vì ID luôn là duy nhất, không bao giờ trùng nhau.
Nên
- Không cần kiểm tra collision.
- Không cần hash lại.
- Không cần truy vấn Database nhiều lần.
Bước 4. Làm sao để mở link thật nhanh?
Hãy nhớ lúc đầu.
Read QPS khoảng: 11 600 request/giây
Nếu mỗi request đều đọc Database, sẽ rất nhanh bị quá tải.
Giải pháp gần như mọi hệ thống lớn đều dùng là: Redis
Luồng xử lý sẽ trở thành:

Nếu Redis có dữ liệu. Trả về ngay. Nếu Redis không có mới đọc Database. Sau đó lưu ngược lại Redis.
Lần sau sẽ nhanh hơn rất nhiều.
Đây chính là mô hình Cache Aside Pattern rất phổ biến.
Kiến trúc tổng thể
Khi ghép tất cả lại, hệ thống sẽ trông như sau:

Mỗi thành phần đều có một nhiệm vụ rõ ràng:
- Web Server nhận request từ người dùng.
- ID Generator tạo ID không trùng lặp.
- Base62 Encoder biến ID thành chuỗi ngắn.
- Redis giúp truy cập cực nhanh.
- Database lưu dữ liệu lâu dài.
Nếu hệ thống có hàng tỷ người dùng thì sao?
Một kỹ sư giỏi sẽ không dừng lại ở việc "hệ thống chạy được".
Họ sẽ nghĩ tiếp:
Nếu ngày mai lượng người dùng tăng gấp 100 lần thì sao?
Khi đó chúng ta có thể bổ sung thêm nhiều thành phần:
- Rate Limiter để ngăn bot tạo hàng triệu URL giả.
- Database Sharding để chia dữ liệu sang nhiều máy chủ.
- Replication để tăng khả năng đọc.
- Auto Scaling để tự động thêm Web Server khi lưu lượng tăng.
- Analytics Pipeline để thống kê lượt click, quốc gia, thiết bị... mà không làm chậm luồng chính.
Đây là những ý tưởng thường được nhắc đến ở cuối các buổi phỏng vấn System Design. Và đây là thực sự là điểm khác biệt giữa senior và phần còn lại. Tôi sẽ viết chi tiết ở 1 bài khác.
Tổng kết
Thoạt nhìn, TinyURL chỉ là một ứng dụng "biến link dài thành link ngắn". Nhưng phía sau nó là rất nhiều bài toán quen thuộc trong thiết kế hệ thống:
- Làm sao tạo ID duy nhất?
- Làm sao tránh trùng lặp?
- Làm sao xử lý hàng chục nghìn request mỗi giây?
- Làm sao giảm tải Database?
- Làm sao mở rộng khi số lượng người dùng tăng lên hàng trăm lần?
Điều thú vị là những kỹ thuật xuất hiện trong TinyURL như Cache, Base62, Distributed ID Generator, Sharding hay Rate Limiter cũng chính là những mảnh ghép được sử dụng trong hầu hết các hệ thống lớn như Facebook, YouTube, TikTok hay Shopee.
Vì vậy, dù TinyURL chỉ là một ứng dụng nhỏ, nó lại là một trong những bài tập System Design kinh điển nhất dành cho người mới bắt đầu. Chỉ cần hiểu thật kỹ bài toán này, bạn sẽ có nền tảng rất tốt để tiếp cận những hệ thống phức tạp hơn trong tương lai.
All Rights Reserved