Case Study: URL Shortener
প্রথম পূর্ণ design, ধাপে ধাপে
প্রথম পূর্ণ case study, আর interview এর সবচেয়ে পরিচিত প্রশ্ন: bit.ly এর মতো একটা URL shortener design করো। দেখতে সহজ (একটা লম্বা link কে ছোট করা), কিন্তু এর মধ্যে id তৈরি, cache, read-heavy scaling, analytics, আর অপব্যবহার ঠেকানোর মতো অনেক মৌলিক বিষয় আছে। এখানে আমরা System Design এর ৬ ধাপ পুরোপুরি মেনে চলবো, যাতে পরের case study গুলোর জন্য একটা ছাঁচ তৈরি হয়।
#ধাপ ১: Requirement
Functional:
- লম্বা URL দিলে ছোট link:
https://sh.rt/aB3xK9 - ছোট link এ গেলে আসল URL এ redirect
- ঐচ্ছিক: নিজের পছন্দের নাম (
sh.rt/eid-offer), মেয়াদ - Click এর সংখ্যা আর মোটামুটি পরিসংখ্যান (দেশ, device)
Non-functional:
- Redirect অনেক দ্রুত: p99 < ৫০ ms (server এর ভেতরে)
- Redirect খুব নির্ভরযোগ্য (link ভাঙলে সব জায়গায় ভাঙা)
- ছোট code অনুমান করা কঠিন হওয়া উচিত (অন্যের private link খুঁজে পাওয়া না যায়)
- Analytics কিছুটা দেরিতে হলেও চলে
বাদ: user এর dashboard এর জটিল feature, QR code (পরে)।
#ধাপ ২: Estimation
- দিনে ১ কোটি নতুন link, প্রতিটা গড়ে ১০০ বার click (read:write = ১০০:১)
- Write: ১ কোটি ÷ ১ লাখ = গড় ১০০/সেকেন্ড, peak ~৫০০
- Read (redirect): ১০০ কোটি/দিন = গড় ১০,০০০/সেকেন্ড, peak ~৫০,০০০
- Storage: প্রতি link ~৫০০ byte (URL, code, owner, সময়); ১ কোটি × ৫০০ B = দিনে ৫ GB, ৫ বছরে ~৯ TB
- Code এর দৈর্ঘ্য: ৫ বছরে ১,৮২৫ কোটি link। Base62 (a-z, A-Z, 0-9) এ ৭ অক্ষরে 62⁷ ≈ ৩.৫ লাখ কোটি সম্ভাবনা, যথেষ্টর চেয়ে অনেক বেশি।
সিদ্ধান্ত: এটা ভীষণ read-heavy। Redirect এর পথ cache আর CDN দিয়ে সাজাতে হবে; write এর চাপ সামান্য।
#ধাপ ৩: API
POST /v1/links
Authorization: Bearer ...
{"url": "https://example.com/very/long/path?x=1", "custom_code": "eid-offer", "expires_at": null}
→ 201 {"code": "eid-offer", "short_url": "https://sh.rt/eid-offer"}
GET /{code}
→ 301 / 302 Location: https://example.com/very/long/path?x=1
GET /v1/links/{code}/stats
→ 200 {"clicks": 18420, "by_country": {...}}301 নাকি 302?
301(স্থায়ী): browser মনে রাখে, পরের বার সরাসরি আসল site এ যায়। Server এর চাপ কমে, কিন্তু click গোনা যায় না, আর link বদলানো যায় না।302(অস্থায়ী): প্রতিবার আমাদের server হয়ে যায়। Analytics আর পরে বদলানো সম্ভব।
Analytics দরকার, তাই 302 (বা ছোট max-age দিয়ে cache)।
#ধাপ ৪: Data model
CREATE TABLE links (
code VARCHAR(16) PRIMARY KEY, -- lookup এর key
long_url TEXT NOT NULL,
owner_id BIGINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ,
is_active BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE INDEX ON links (owner_id, created_at DESC);Access pattern একদম সরল (code → URL), তাই key-value ধরনের store (DynamoDB, Cassandra) ও খুব মানানসই। শুরুতে PostgreSQL যথেষ্ট; বড় হলে code দিয়ে shard করা সহজ।
Click এর data আলাদা: এগুলো বিশাল পরিমাণ event, তাই সরাসরি database এ না, queue হয়ে analytics store এ (ClickHouse/BigQuery)।
#ধাপ ৫: High-level design
Redirect এর flow:
- CDN এ জনপ্রিয় link এর redirect অল্প সময়ের জন্য cache (কিছু analytics হারাবে, ঠিক করা যায় edge log দিয়ে)
- Redirect service আগে Redis এ খোঁজে (৮০/২০ নীতিতে জনপ্রিয় link প্রায় সবই cache এ)
- না পেলে database (replica) থেকে, তারপর cache এ রাখো
- Click event queue তে পাঠাও (non-blocking), redirect দাও
- Worker রা batch করে analytics store এ লেখে
#ধাপ ৬: Deep dive
#Short code কীভাবে তৈরি
| পদ্ধতি | কীভাবে | সুবিধা | অসুবিধা |
|---|---|---|---|
| URL এর hash (MD5 এর প্রথম ৭ অক্ষর) | একই URL এ একই code | সহজ | Collision সামলাতে হয়; একই URL বারবার দিলে একই code (কখনো চাই না) |
| Counter + Base62 | Database এর id ১২৫ → "21" |
Collision নেই, ছোট | ক্রমিক, অনুমান করা যায় (aB3xK9 এর পরেরটা আন্দাজ), কেন্দ্রীয় counter |
| Counter এর range ভাগ | প্রতিটা server আগে থেকে ১০,০০০ id এর একটা range নেয় | দ্রুত, কেন্দ্রীয় নির্ভরতা কম | Server মরলে range এর কিছু অপচয় (সমস্যা না) |
| Random ৭-৮ অক্ষর | secrets দিয়ে random Base62, unique constraint, collision হলে আবার |
অনুমান করা যায় না, সহজ | খুব বিরল collision এ retry |
বাছাই: random ৭-৮ অক্ষর + database এর unique constraint। ৩.৫ লাখ কোটি সম্ভাবনায় ১,৮০০ কোটি link থাকলেও collision এর সম্ভাবনা নগণ্য, আর হলে শুধু আবার চেষ্টা। অনুমান করা যায় না বলে private link নিরাপদ। (ক্রমিক id চাইলে সেটাকে একটা গোপন permutation দিয়ে এলোমেলো করা যায়।)
import secrets
import string
ALPHABET = string.ascii_letters + string.digits # Base62
def new_code(length: int = 7) -> str:
return "".join(secrets.choice(ALPHABET) for _ in range(length))#Custom code
User এর পছন্দের নাম একই links table এ, unique constraint দিয়ে। কিছু নাম সংরক্ষিত রাখো (api, login, admin), আর অশালীন শব্দের তালিকা।
#Cache
- Key:
link:{code}→ long_url, TTL ২৪ ঘণ্টা + jitter - Link বন্ধ বা মেয়াদ শেষ হলে cache মোছা
- না থাকা code এর জন্য "নেই" ও অল্প সময়ের জন্য cache (penetration ঠেকাতে)
- Viral link এর জন্য hot key: CDN আর app এর local cache (১-২ সেকেন্ড)
#অপব্যবহার ঠেকানো
URL shortener phishing আর malware ছড়ানোর প্রিয় হাতিয়ার:
- নতুন link এ rate limit (user/IP প্রতি)
- Long URL কে Google Safe Browsing ধরনের তালিকায় যাচাই (async worker দিয়ে), খারাপ হলে link বন্ধ
- Report করার ব্যবস্থা
- নিজের domain এর redirect loop আটকানো (
sh.rt/x→sh.rt/y→ ...)
#Scale
- Redirect service stateless, horizontal scale
- Read এর বেশিরভাগ cache/CDN এ শেষ; database এ read replica
- Data বড় হলে code দিয়ে hash sharding (consistent hashing)
- Analytics আলাদা pipeline; redirect এর গতিতে কোনো প্রভাব নেই
#Interview এর শেষে বলার মতো trade-off
- 302 বেছে নিয়েছি analytics এর জন্য, দাম: প্রতিটা click আমাদের server হয়ে যায় (cache আর CDN দিয়ে সামলাচ্ছি)
- Random code বেছে নিয়েছি নিরাপত্তার জন্য, দাম: বিরল collision এ retry
- Click analytics eventual consistent, দাম: stats কয়েক সেকেন্ড থেকে মিনিট পিছিয়ে
#অনুশীলন
#নিজেকে যাচাই করো
এই system এর read:write অনুপাত design কে কীভাবে প্রভাবিত করে?
Read প্রায় ১০০ গুণ বেশি, তাই redirect এর পথ cache, CDN আর read replica দিয়ে সাজাতে হয়, আর write এর অংশ সহজ রাখা যায়।
ক্রমিক id থেকে code বানালে কী সমস্যা?
Code অনুমান করা যায়, ফলে অন্যের private link খুঁজে বের করা সম্ভব, আর মোট কত link আছে সেটাও বোঝা যায়।
Click গোনা redirect এর সাথে synchronous ভাবে database এ লিখলে কী সমস্যা?
প্রতিটা redirect ধীর হবে আর database এ বিশাল write এর চাপ পড়বে। Queue তে event রেখে async ভাবে batch করে লিখলে redirect দ্রুত থাকে।
চিহ্ন দিলে sidebar আর রোডম্যাপে এই অধ্যায়ের station ভরে যাবে।