URL Shortener (TinyURL) ডিজাইন
- ●URL shortener একটা লম্বা URL-কে ছোট, ইউনিক code-এ রূপান্তর করে এবং পরে সেই code থেকে আসল URL-এ redirect করে।
- ●এটা প্রচণ্ড read-heavy system — লেখার চেয়ে পড়া (redirect) প্রায় ১০০ গুণ বেশি, তাই cache আর KV store মূল ভরসা।
- ●ছোট code বানানোর দুটো পথ — hash করে নেওয়া, অথবা একটা counter/range থেকে ID নিয়ে base62 encode করা।
ধরো তুমি একটা বড় product page-এর লিংক WhatsApp-এ পাঠাতে চাও, কিন্তু লিংকটা ৩০০ অক্ষর লম্বা। দেখতে বিশ্রী, copy-paste-এ ভাঙে। URL shortener এই সমস্যা সমাধান করে — bit.ly/3xKp9 মতো ছোট লিংক বানিয়ে দেয়, যা click করলে আসল লম্বা URL-এ নিয়ে যায়। চলো একে interview framework অনুযায়ী ধাপে ধাপে ডিজাইন করি।
১. সমস্যা বোঝা (Requirements)
প্রথম কাজ — কী বানাচ্ছি তা পরিষ্কার করা। দুই ধরনের requirement থাকে।
Functional requirements (system কী করবে):
- একটা লম্বা URL দিলে একটা ছোট, ইউনিক short URL ফেরত দেবে।
- short URL-এ গেলে আসল লম্বা URL-এ redirect করবে।
- (ঐচ্ছিক) ব্যবহারকারী নিজের পছন্দের custom alias দিতে পারবে — যেমন
bit.ly/my-shop। - (ঐচ্ছিক) লিংকের expiry/মেয়াদ সেট করা যাবে।
- (ঐচ্ছিক) কতবার click হলো — সেই analytics দেখা যাবে।
Non-functional requirements (system কেমন হবে):
- High availability — service বন্ধ থাকলে কোটি কোটি লিংক ভেঙে যাবে। তাই প্রায় সবসময় চালু থাকতে হবে।
- Low latency — redirect হতে হবে চোখের পলকে (১০০ms-এর নিচে)।
- Scalability — শত কোটি লিংক, দিনে শত কোটি redirect সামলাতে হবে।
- ইউনিকনেস — দুটো ভিন্ন লিংক যেন কখনো একই short code না পায়।
URL shortener অনেকটা ক্লোকরুমের টোকেনের মতো। তুমি একটা বড় ব্যাগ (লম্বা URL) জমা দাও, তারা একটা ছোট নম্বরওয়ালা টোকেন (short code) দেয়। পরে টোকেন দেখালেই তোমার ব্যাগ ফেরত — মানে আসল URL।
২. স্কেল আন্দাজ (Estimation)
ডিজাইন করার আগে আন্দাজ করা জরুরি, যাতে বুঝি কোথায় চাপ পড়বে। ধরি প্রতিদিন ১ কোটি (10M) নতুন URL তৈরি হয়, আর read:write ratio = 100:1।
নতুন URL/দিন (write) = 10,000,000
Write QPS = 10,000,000 / 86,400 sec ≈ 115 writes/sec
Read (redirect) QPS = 115 × 100 ≈ 11,500 reads/sec → peak ধরি ~25,000/sec
Storage (per URL):
short code + long URL + metadata ≈ 500 bytes
প্রতি বছর URL = 10M × 365 ≈ 3.65 বিলিয়ন
প্রতি বছর storage = 3.65B × 500 bytes ≈ 1.8 TB/বছর
৫ বছরে ≈ 9 TB
Bandwidth:
Read 25,000/sec × 500 bytes ≈ 12.5 MB/sec outgoing
মূল উপলব্ধি — এটা read-heavy। তাই আমাদের সব মনোযোগ read fast করার দিকে: cache আর read replica।
৩. API ডিজাইন
মাত্র দুটো প্রধান endpoint দরকার।
POST /api/v1/shorten
body: { "longUrl": "https://example.com/very/long/path", "customAlias": "my-shop" (optional), "expireAt": "2027-01-01" (optional) }
resp: 201 { "shortUrl": "https://sho.rt/3xKp9", "code": "3xKp9" }
GET /{code}
e.g. GET /3xKp9
resp: 301 (বা 302) Redirect → Location: https://example.com/very/long/path
404 যদি code না থাকে, 410 Gone যদি expire হয়ে যায়
খেয়াল করো — POST দিয়ে নতুন বানাই, আর redirect-এর জন্য simple GET. এই GET-টাই সবচেয়ে বেশি call হবে, তাই একে যত হালকা ও দ্রুত রাখা যায় তত ভালো।
৪. ডেটা মডেল
মূল table খুবই সরল — একটা mapping রাখি code → long URL।
| Field | Type | বর্ণনা |
|---|---|---|
| short_code | string PK | ৬-৭ character-এর ইউনিক code (key) |
| long_url | string | আসল লম্বা URL |
| created_at | timestamp | কখন তৈরি হলো |
| expire_at | timestamp | মেয়াদ (null হলে চিরস্থায়ী) |
| user_id | string | কে বানিয়েছে (ঐচ্ছিক) |
| click_count | integer | কতবার click হয়েছে (analytics) |
SQL না NoSQL? এখানে relation প্রায় নেই — আমরা শুধু একটা key (short_code) দিয়ে একটা value (long_url) খুঁজি। এটা ঠিক Key-Value Store-এর কাজ — যেমন DynamoDB, Cassandra বা Redis। এরা horizontally scale করে, একটা key দিয়ে lookup অসম্ভব দ্রুত, আর শত কোটি row সামলায়। তাই NoSQL KV store বেছে নিই। (ছোট স্কেলে PostgreSQL-ও দিব্যি চলত — index করা primary key lookup-ও দ্রুত।)
৫. হাই-লেভেল ডিজাইন
মূল component আর request flow এমন:
- Client — browser বা mobile app।
- Load Balancer — request গুলোকে অনেকগুলো app server-এ ভাগ করে দেয়।
- App Servers (stateless) — shorten করা ও redirect-এর logic চালায়। stateless বলে যত খুশি বাড়ানো যায়।
- Cache (Redis) — সবচেয়ে জনপ্রিয় (hot) code → long URL এখানে রাখা হয়, যাতে DB-তে যেতে না হয়।
- Database (KV store) — সব mapping-এর স্থায়ী ঘর।
- ID Generation Service — নতুন ইউনিক code বানিয়ে দেয় (নিচে deep dive-এ)।
Write flow (shorten):
- Client
POST /shortenপাঠায়। - App server ID generation service থেকে একটা নতুন ইউনিক number নেয়, সেটাকে base62 encode করে code বানায়।
{code → long_url}DB-তে save করে।- পূর্ণ short URL ফেরত দেয়।
Read flow (redirect) — এটাই বেশি ঘটে:
- Client
GET /3xKp9পাঠায়। - App server আগে cache-এ খোঁজে। পেলে (cache hit) সঙ্গে সঙ্গে redirect।
- না পেলে (cache miss) DB থেকে নেয়, cache-এ বসিয়ে দেয়, তারপর redirect।
- HTTP redirect (301/302) দিয়ে browser-কে আসল URL-এ পাঠিয়ে দেয়।
৬. গভীরে (Deep Dive)
৬.১ Code কীভাবে বানাবো — Hash vs Counter
পথ ১: Hashing। long URL-কে MD5/SHA হ্যাশ করে প্রথম ৭ character নিই। সমস্যা — দুটো ভিন্ন URL একই ৭ character পেতে পারে (collision)। তখন আবার চেষ্টা করতে হয় (rehash বা salt যোগ), যা slow আর জটিল।
পথ ২: Counter + base62 (recommended)। একটা global counter রাখি যা শুধু বাড়ে: 1, 2, 3...। প্রতিটা সংখ্যাকে Base62 (0-9, a-z, A-Z = ৬২টা অক্ষর) দিয়ে encode করি। যেমন number 125 → base62-তে "21"। এতে collision-এর প্রশ্নই নেই, কারণ প্রতিটা number আলাদা। আর ৭ character দিয়ে 62⁷ ≈ ৩.৫ ট্রিলিয়ন code পাওয়া যায় — যথেষ্ট।
কিন্তু একটাই counter মানে bottleneck। সমাধান — range allocation: প্রতিটা app server একসাথে ১০০০টা number-এর একটা block (যেমন 1–1000, পরের জন 1001–2000) নিয়ে রাখে এবং নিজে নিজে বিতরণ করে। (Twitter-এর Snowflake-ও একই ধারণা।)
Counter কখনোই in-memory একটা variable-এ রেখো না — server restart হলে আবার 1 থেকে শুরু হয়ে আগের code overwrite হয়ে যাবে! Counter/range রাখো একটা persistent জায়গায় (যেমন ZooKeeper, বা DB-র atomic increment) যাতে কোনো দুই server একই number না পায়।
৬.২ Redirect — 301 না 302?
- 301 (Permanent) — browser এটা cache করে রাখে। পরের বার একই short link click করলে আমাদের server-এ আর হিট না করেই সরাসরি destination-এ যায়। দ্রুত, server-এ কম load। কিন্তু তখন আমরা click count করতে পারি না (server তো হিট-ই হলো না)।
- 302 (Temporary) — browser cache করে না, প্রতিবার আমাদের server-এ হিট করে। এতে আমরা প্রতিটা click count/analytics করতে পারি, এবং পরে চাইলে destination বদলাতে পারি।
মূল trade-off: 301 = দ্রুত কিন্তু analytics নেই; 302 = analytics আছে কিন্তু প্রতিবার load। analytics দরকার হলে 302 বেছে নাও।
৬.৩ Cache strategy
read-heavy বলে cache-ই হিরো। আমরা Redis-এ hot URL রাখি (LRU eviction — কম ব্যবহৃতগুলো বের করে দেয়)। 80/20 নিয়মে অল্প কিছু লিংকই বেশিরভাগ traffic পায়, তাই অল্প RAM-এই বিশাল hit rate পাওয়া যায়। cache miss হলে DB থেকে এনে cache-এ বসাই (cache-aside pattern)।
৭. বটলনেক ও স্কেলিং
- Read load — cache (Redis) + DB read replica দিয়ে সামলাও। এটাই সবচেয়ে বড় চাপ, আর এটাই সবচেয়ে সহজে scale হয় কারণ data immutable (একবার বানালে বদলায় না, তাই caching নিরাপদ)।
- Database size — শত কোটি row একটা machine-এ ধরবে না। তাই
short_code-এর ওপর sharding করো (consistent hashing দিয়ে), যাতে data অনেক machine-এ ছড়িয়ে যায়। - ID generation bottleneck — একটা counter সবার জন্য চাপ। range allocation দিয়ে প্রতিটা server-কে আগেই number-এর block দিয়ে রাখো।
- High availability — DB-র multiple replica, একাধিক region-এ deploy, আর সামনে একটা CDN/edge layer দিলে latency আরও কমে।
৮. সারসংক্ষেপ
URL shortener দেখতে সহজ, কিন্তু এর মধ্যে system design-এর সব মূল ধারণা আছে — ID generation, base62 encoding, read-heavy caching, KV store, sharding, আর redirect-এর trade-off। মনে রেখো: code বানাতে counter+base62 নাও (collision-free), read fast করতে cache, আর data scale করতে sharding।
Interviewer দেখতে চায় তুমি বুঝেছ কিনা যে এটা read-heavy — তাই caching-এর উপর জোর দাও। আর hash vs counter-এর trade-off স্পষ্টভাবে ব্যাখ্যা করতে পারলে, এবং 301 vs 302-এর পার্থক্য (analytics বনাম speed) বলতে পারলে তুমি এগিয়ে থাকবে।
মিনি কুইজ
1. URL shortener system সাধারণত কেমন?
2. Counter থেকে নেওয়া ID-কে কেন base62-তে encode করা হয়?
3. Permanent short link-এর জন্য কোন redirect ভালো, এবং কেন?