সেশন ২৩

URL Shortener / Pastebin Design - ছোট link, পুরো একটা design

এই সেশনের লক্ষ্য: একটা URL shortener শুরু থেকে শেষ পর্যন্ত design করা: requirements, back-of-envelope estimation, API, data model আর high-level design। তারপর short code বানানোর তিনটা উপায় (base62 counter, hash, key generation service), 301 বনাম 302 redirect, caching, expiry আর custom alias এর ভেতরে ঢোকা। শেষে দেখা, Pastebin এ ঠিক কোন জায়গাটা বদলায়।

০০ যে প্রশ্নগুলোর উত্তর খুঁজবো

এতদিন আমরা টুকরো টুকরো শিখেছি। Cache, replica, shard, CDN, queue: প্রতিটা আলাদা সেশনে, আলাদা সমস্যার সমাধান হিসেবে। এবার সেই টুকরোগুলো জোড়া লাগিয়ে একটা আস্ত system বানানোর পালা।

শুরুর জন্য URL shortener সবচেয়ে ভালো বন্ধু। Idea টা এক লাইনের: লম্বা link দিলে ছোট link পাবেন। কিন্তু একটু ভেতরে ঢুকলেই প্রায় সব বড় প্রশ্ন সামনে আসে: কত traffic, কোথায় data রাখবো, কীভাবে দুইজনকে একই code দেওয়া ঠেকাবো। Interview তেও এটা সবচেয়ে বেশি আসা প্রশ্নগুলোর একটা। তাই এই সেশনে আমরা ধীরে ধীরে, প্রতিটা ধাপ হাতে কলমে করে এগোবো।

এটা স্টেজ ০৯ এর প্রথম case study। তাই আগে কাঠামোটা দেখে নিন। এই স্টেজের প্রতিটা সেশন, আর একটা ভালো system design interview, এই সাতটা ধাপ মেনে এগোয়:

1 Requirements কী বানাবো, আর কতটা ভালো হতে হবে 2 Back-of-envelope estimation storage, bandwidth, QPS এর মোটামুটি হিসাব 3 API অ্যাপ বাইরে থেকে কোন কোন call করবে 4 Data model কোন data কোথায় থাকবে 5 High-level design বড় বড় box আর তাদের মাঝের arrow 6 Deep dives সবচেয়ে কঠিন দুই-তিনটা অংশ খুলে দেখা 7 Bottlenecks ও trade-offs কোথায় চাপ পড়বে, আর কী ছাড় দিলাম
স্টেজ ০৯ এর প্রতিটা case study এই সাতটা ধাপ মেনে চলে। Interview তেও এই ক্রমেই এগোনো ভালো। দুই নম্বর ধাপের হিসাবটাই ঠিক করে দেয়, পরের ধাপগুলোতে কোন অংশে সবচেয়ে বেশি মনোযোগ দিতে হবে।

অনেকে প্রশ্ন শুনেই box আঁকতে শুরু করেন। আমরা তা করবো না। আগে জানবো কী বানাতে হবে, তারপর হিসাব করবো কত বড় বানাতে হবে। দেখবেন, হিসাবটা নিজেই বলে দেয় design এর কঠিন অংশ কোথায়।

এই স্টেজের design গুলো সহজ থেকে কঠিনের দিকে সাজানো। আজ একটা key আর একটা cache দিয়ে শুরু। তারপর rate limiter আর search, তারপর feed এর fan-out, chat এর real-time connection, YouTube এর বিশাল data, আর শেষে Uber, যেখানে আগের প্রায় সবকিছু একসাথে লাগে। প্রতিটা সেশন আগেরটার উপর দাঁড়িয়ে থাকবে।

  • ১ URL shortener এর functional requirement আর non-functional requirement কী কী?
  • ২ Back-of-envelope estimation: দিনে কত link, সেকেন্ডে কত QPS, কত storage?
  • ৩ API, data model আর high-level design কেমন হবে?
  • ৪ Short code কীভাবে বানাবো? (base62 আর counter)
  • ৫ Hash দিয়ে বানালে hash collision এর কী হবে, আর key generation service কেন?
  • ৬ 301 redirect নাকি 302 redirect? Click analytics কোথায় জমা হবে?
  • ৭ Read-heavy traffic এ cache, replica আর shard কোথায় বসবে?
  • ৮ Expiry, custom alias আর abuse কীভাবে সামলাবো?
  • ৯ Pastebin এ কী বদলায় (object storage), আর পুরো design এর bottleneck কোথায়?

০১ URL shortener এর functional requirement আর non-functional requirement কী কী?

বৃহস্পতিবার বিকেল। অর্ক “কোড ক্যাফে” club এর general secretary। Cafeteria-র সামনের notice board এ সে এইমাত্র hackathon এর poster টা লাগিয়ে একটু দূরে দাঁড়িয়েছে। Poster এর নিচে registration এর Google Form link, পুরো ৮০ অক্ষরের। একজন first-year student phone বের করে link টা টাইপ করতে শুরু করলো। 1FAIpQLSd এ এসে সে থমকে গেলো: এটা বড় হাতের I, নাকি ছোট হাতের l? দুইবার ভুল হলো। তারপর phone টা পকেটে রেখে সে চলে গেলো।

অর্কের মনটা খারাপ হয়ে গেলো। এমন কতজন যে এভাবে ফিরে যাচ্ছে, সে জানেও না। সেদিন রাতেই সে ঠিক করলো, club এর নিজের একটা ছোট link লাগবে: cc.lnk/hack26। মাত্র ১৩ অক্ষর, ভুল হওয়ার সুযোগই নেই।

Idea টা সহজ। কেউ cc.lnk/hack26 খুললে আমাদের server একটা table এ hack26 খোঁজে। সেখানে লম্বা link টা পায়। তারপর browser কে বলে দেয়: “আসল ঠিকানা ওইটা, ওখানে যান।” Browser কে এভাবে অন্য ঠিকানায় পাঠিয়ে দেওয়ার নাম redirect।

Student খোলে: cc.lnk/hack26 phone এর browser থেকে একটা GET request যায় ১ Server table এ hack26 খোঁজে hack26 → docs.google.com/forms/d/e/1FAIp... ২ Server উত্তর দেয়: 302 Found Location header এ আসল লম্বা URL ৩ Browser নিজেই Google Form খোলে student এর চোখে পুরোটা এক ঝলকে হয়ে যায় ৪
আমাদের server Google Form টা নিজে দেখায় না। সে শুধু browser কে আসল ঠিকানাটা বলে দেয় (ধাপ ৩)। বাকি কাজ browser নিজেই করে।

Requirement: আগে ঠিক করুন কী বানাচ্ছেন

Design শুরু করার আগে প্রথম কাজ হলো দুটো প্রশ্নের উত্তর লিখে ফেলা। System টা কী কী করবে? আর কতটা ভালোভাবে করবে? প্রথম প্রশ্নের উত্তরগুলোকে বলে functional requirement, মানে feature এর তালিকা। দ্বিতীয়টার উত্তরগুলোকে বলে non-functional requirement, মানে গতি, নির্ভরযোগ্যতা আর আকারের শর্ত।

অর্কের system এর functional requirement:

  1. ছোট link বানানো: লম্বা URL দিলে একটা ছোট, unique link পাওয়া যাবে।
  2. Redirect: ছোট link খুললে আসল URL এ পৌঁছে যাবে।
  3. Custom alias: চাইলে নিজের পছন্দের নাম দেওয়া যাবে, যেমন hack26।
  4. Expiry: চাইলে link এর একটা মেয়াদ দেওয়া যাবে। Registration শেষ হলে link আর কাজ করবে না।
  5. Analytics: কোন link এ কতবার click পড়লো, দেখা যাবে। অর্ক জানতে চায়, poster থেকে আসলে কতজন এলো।

আর non-functional requirement:

একটা জিনিস এখনই চোখে পড়ে। একটা link বানানো হয় একবার, কিন্তু click পড়ে শত শত বার। অর্থাৎ এই system এ write কম, read অনেক বেশি। এমন system কে বলে read-heavy। এই একটা কথাই পুরো design এর দিক ঠিক করে দেবে।

Requirement আগে, box পরে

Interview এ প্রথম পাঁচ মিনিট প্রশ্ন করুন: custom alias লাগবে কি? Expiry? Analytics? দিনে কত link? Requirement পরিষ্কার না করে box আঁকা শুরু করলে পরে পুরো design আবার বদলাতে হয়।

০২ Back-of-envelope estimation: দিনে কত link, সেকেন্ডে কত QPS, কত storage?

এক মাসের মধ্যে আরো তিরিশটা club অর্কের কাছে এলো: “ভাই, আমাদেরও একটা দিন না।” তারপর পাশের university, তারপর অনলাইন দোকান আর কোচিং সেন্টার। ধরুন কয়েক বছরে service টা সারা দেশে ছড়িয়ে গেলো। তখন কত বড় machine লাগবে? কয়টা? এই প্রশ্নের উত্তর পেতে নিখুঁত হিসাবের দরকার নেই। শুধু মাত্রাটা বুঝলেই চলে: সংখ্যাটা কি হাজারে, লাখে, নাকি কোটিতে?

এই মোটা দাগের হিসাবকে বলে back-of-envelope estimation: খামের পেছনে পাঁচ মিনিটে করা যায় এমন হিসাব। পুরোনো দিনের engineer রা এমন হিসাব সত্যিই খামের উল্টো পিঠে করতেন, নামটা সেখান থেকেই এসেছে। এই স্টেজের প্রতিটা design এ আমরা এই হিসাবটা করবো, তাই এখানে প্রতিটা ধাপ হাতে করে দেখবো। হিসাবে একটা শব্দ বারবার আসবে: QPS (queries per second), মানে সেকেন্ডে কতগুলো request আসে। আর peak মানে দিনের সবচেয়ে ব্যস্ত সময়, যখন traffic গড়ের চেয়ে কয়েক গুণ বেশি থাকে।

ধরে নিই (assumption)
  নতুন short link প্রতিদিন   : 1 কোটি = 10,000,000
  read : write              : 100 : 1   (link বানানো হয় একবার, click হয় বহুবার)
  peak                      : গড়ের 3 গুণ

Write QPS (নতুন link)
  10,000,000 / 86,400 sec    ≈ 116 write/sec
  peak এ                     ≈ 350 write/sec

Read QPS (redirect)
  116 × 100                  ≈ 11,600 read/sec
  peak এ                     ≈ 35,000 read/sec

Storage (প্রতিটা row ~500 byte: URL ~200, বাকি column আর index ~300)
  10,000,000 × 500 B         = 5 GB / দিন
  5 GB × 365                 ≈ 1.8 TB / বছর
  5 বছরে                      ≈ 9 TB
  5 বছরে মোট link             = 10,000,000 × 365 × 5 = 18,250,000,000 (18.25 billion)

Cache (সব নয়, শুধু hot link)
  দিনে আলাদা আলাদা link এ click   ≈ 5 কোটি (ধরে নিলাম)
  সবচেয়ে hot 20%                ≈ 1 কোটি link × 500 B ≈ 5 GB  → একটা Redis এই এঁটে যায়

Bandwidth
  35,000 redirect/sec × ~500 B  ≈ 17.5 MB/sec  → খুবই সামান্য

সংখ্যাগুলো এবার design এর ভাষায় পড়া যাক:

সংখ্যাDesign এর জন্য মানে
সেকেন্ডে ৩৫০টা writeখুবই কম। একটা ভালো database একাই এটা সামলাতে পারে।
সেকেন্ডে ৩৫,০০০টা readআসল চাপ এখানেই। Database কে প্রতিটা click এ জিজ্ঞেস করা যাবে না, cache লাগবে।
৫ বছরে ৯ TBএকটা machine এ রাখা কষ্টকর। একসময় data ভাগ (shard) করতে হবে।
৫ বছরে ১,৮২৫ কোটি linkCode কত অক্ষরের হবে, সেটা এই সংখ্যাই ঠিক করবে (সেকশন ০৪)।
hot data ৫ GBএকটা Redis এর memory তেই এঁটে যায়।
১৭.৫ MB/sec bandwidthচিন্তার কিছু নেই। Redirect এর উত্তর খুব ছোট।
সংখ্যা থেকে সিদ্ধান্ত

Estimation এর উদ্দেশ্য সংখ্যা মুখস্থ করা নয়। উদ্দেশ্য হলো কোথায় চাপ পড়বে সেটা আগে দেখা। এখানে হিসাবটা পরিষ্কার বলছে: write নিয়ে চিন্তা কম, read নিয়ে চিন্তা বেশি।

০৩ API, data model আর high-level design কেমন হবে?

API: বাইরের দুনিয়ার সাথে চুক্তি

API হলো বাইরের দুনিয়ার সাথে আমাদের চুক্তি: কোন ঠিকানায় কী পাঠালে কী ফেরত আসবে। অর্কের system এ চারটা কাজ দরকার:

# ১) নতুন short link বানানো
POST /api/v1/urls
Authorization: Bearer <token>
{
  "long_url":     "https://docs.google.com/forms/d/e/1FAIpQLSd.../viewform",
  "custom_alias": "hack26",                 // না দিলেও চলে
  "expires_at":   "2026-12-31T23:59:59Z"    // না দিলেও চলে
}
→ 201 Created
{ "short_url": "https://cc.lnk/hack26" }

# ২) Redirect (system এর আসল কাজ)
GET /hack26
→ 302 Found
Location: https://docs.google.com/forms/d/e/1FAIpQLSd.../viewform

# ৩) Click এর হিসাব দেখা
GET /api/v1/urls/hack26/stats
→ 200 OK
{ "total_clicks": 1240, "today": 312 }

# ৪) Link মুছে ফেলা
DELETE /api/v1/urls/hack26
→ 204 No Content

খেয়াল করুন, redirect এর ঠিকানাটা /api/v1/ এর নিচে নেই। ওটা সরাসরি cc.lnk/hack26, কারণ এই ঠিকানাটাই poster এ ছাপা হয়। যত ছোট, তত ভালো।

Data model: কী store করবো

আমাদের data খুবই সরল। একটা code এর বিপরীতে একটা লম্বা URL, আর তার সাথে কয়েকটা তথ্য:

CREATE TABLE urls (
    short_code   VARCHAR(16)  PRIMARY KEY,   -- "1005ban" বা custom "hack26"
    long_url     TEXT         NOT NULL,      -- গড়ে ~200 byte, সর্বোচ্চ 2,048
    user_id      BIGINT,                     -- কে বানিয়েছে
    created_at   TIMESTAMP    NOT NULL,
    expires_at   TIMESTAMP                   -- NULL মানে মেয়াদ নেই
);

CREATE TABLE users (
    user_id      BIGINT       PRIMARY KEY,
    email        VARCHAR(255) UNIQUE,
    created_at   TIMESTAMP    NOT NULL
);

-- click এর হিসাব এখানে নেই; সেটা আলাদা analytics store এ থাকে

এখানে short_code হলো primary key। ফলে একই code দুইবার INSERT হতে পারে না, database নিজেই সেটা আটকায়। এই সুবিধাটা আমরা সেকশন ০৮ এ কাজে লাগাবো।

SQL নাকি NoSQL? আমাদের প্রায় সব query একই রকম: একটা short_code দিয়ে একটা row খোঁজা। কোনো join নেই, জটিল query নেই। শুরুতে PostgreSQL বা MySQL একদম যথেষ্ট। Data অনেক বড় হলে DynamoDB বা Cassandra এর মতো key-value store ও ভালো চলে, কারণ এদের ভাগ করে অনেক machine এ ছড়িয়ে দেওয়া সহজ।

High-level design: প্রথম খসড়া

এবার box আঁকার পালা। প্রথম খসড়াটা ইচ্ছা করেই সরল রাখা হলো:

CLIENT LOAD BALANCER APPLICATION DATA অর্ক student student Load Balancer path দেখে পাঠায় POST /api/v1/urls GET /hack26 Write API নতুন short link বানায় Redirect Service code খুঁজে 302 পাঠায় INSERT SELECT Database urls table
প্রথম খসড়া। Link বানানোর পথ (বাঁয়ে) আর click এর পথ (ডানে, highlighted) দুটো আলাদা service এ থাকে। Click এর পথে traffic প্রায় ১০০ গুণ বেশি, তাই ওটাকে আলাদা করে বড় করা যায়।

দুটো আলাদা service কেন? কারণ দুটোর চাপ একদম আলাদা। Write API সেকেন্ডে ৩৫০টা request পায়, Redirect Service পায় ৩৫,০০০টা। আলাদা থাকলে শুধু redirect server এর সংখ্যা বাড়ানো যায়, write এর গায়ে হাত না দিয়েই। Load balancer request এর path দেখে ঠিক service এ পাঠায় (load balancer এর কথা সেশন ০২ এ দেখেছেন)।

এই খসড়ায় একটা বড় প্রশ্নের উত্তর এখনো নেই। Write API নতুন link এর জন্য 1005ban বা 2LrsIyw এর মতো code টা পাবে কোথা থেকে? পরের দুই সেকশন এই প্রশ্ন নিয়েই।

০৪ Short code কীভাবে বানাবো? (base62 আর counter)

ভালো short code এর চারটা গুণ থাকা দরকার। Code ছোট হবে। Unique হবে, মানে দুইটা link কখনো একই code পাবে না। URL এ লেখা নিরাপদ হবে। আর আন্দাজ করা কঠিন হবে।

Base62: অল্প অক্ষরে বড় সংখ্যা

আমরা সাধারণত দশটা অঙ্ক দিয়ে গুনি: 0 থেকে 9। একে বলে base 10। Base62 মানে ৬২টা “অঙ্ক” দিয়ে গোনা: দশটা সংখ্যা, ২৬টা ছোট হাতের অক্ষর আর ২৬টা বড় হাতের অক্ষর। প্রতিটা ঘরে ১০টার বদলে ৬২টা সম্ভাবনা থাকে, তাই অল্প কয়েকটা অক্ষরেই বিশাল সংখ্যা লেখা যায়।

তাহলে base64 কেন নয়? Base64 এ আরো দুটো চিহ্ন থাকে: + আর /। URL এ / মানে নতুন path, আর + অনেক জায়গায় space হয়ে যায়। Base62 এ এমন কোনো ঝামেলা নেই।

Alphabet: 62টা অক্ষর
  0-9   → 10টা
  a-z   → 26টা
  A-Z   → 26টা
  মোট   → 62টা   "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

কত অক্ষরে কতগুলো আলাদা code
  62^5  =           916,132,832   (প্রায় 92 কোটি)
  62^6  =        56,800,235,584   (প্রায় 5,680 কোটি)
  62^7  =     3,521,614,606,208   (প্রায় 3.5 লাখ কোটি)

5 বছরে দরকার 18.25 billion
  62^6 এ ঢোকে, কিন্তু এক-তৃতীয়াংশ ভরে যায়
  62^7 এ জায়গা প্রায় 190 গুণ বেশি   → তাই 7 অক্ষর

উদাহরণ: 1,000,000 সংখ্যাটাকে base62 তে লেখা (বারবার 62 দিয়ে ভাগ)
  1,000,000 ÷ 62 = 16,129   ভাগশেষ  2   → '2'
     16,129 ÷ 62 =    260   ভাগশেষ  9   → '9'
        260 ÷ 62 =      4   ভাগশেষ 12   → 'c'
          4 ÷ 62 =      0   ভাগশেষ  4   → '4'
  ভাগশেষগুলো নিচ থেকে উপরে পড়ুন      → "4c92"

মিলিয়ে দেখা
  4×62³ + 12×62² + 9×62 + 2
  = 953,312 + 46,128 + 558 + 2
  = 1,000,000  ✓

হিসাবটা বলছে, ৭ অক্ষরেই প্রায় সাড়ে তিন লাখ কোটি আলাদা code বানানো যায়। আমাদের ৫ বছরে দরকার ১,৮২৫ কোটি। তাই ৭ অক্ষর নিশ্চিন্তে বহু বছর চলবে।

সবচেয়ে সরল উপায় হলো একটা counter রাখা। নতুন link এলে counter এক বাড়ে, আর সেই সংখ্যাটা base62 তে লিখে দেওয়া হয়। দুইটা link কখনো একই সংখ্যা পায় না, তাই collision এর প্রশ্নই ওঠে না।

একটা ছোট কৌশল আছে। Counter টা 1 থেকে শুরু করলে প্রথম দিকের code হবে 1, 2, 3। তাই counter শুরু করা হয় 62^6 থেকে, মানে 56,800,235,584 থেকে। এই সংখ্যার base62 রূপ 1000000, পুরো ৭ অক্ষরের। এরপরের প্রতিটা code ও ৭ অক্ষরের হয়। অর্কের hackathon link যেমন পেলো 56,801,470,151 নম্বর, যার base62 রূপ 1005ban।

কিন্তু counter এর দুটো সমস্যা আছে।

  1. একটা counter মানে একটা SPOF: সব write server কে প্রতিটা link এর জন্য একই counter এর কাছে যেতে হয়। Counter down হলে কেউ নতুন link বানাতে পারে না (SPOF এর কথা সেশন ০১ এ দেখেছেন)।
  2. Code আন্দাজ করা যায়: 1005ban এর পরের code 1005bao। কেউ একটা ছোট script লিখে পরপর সব code খুলে দেখতে পারে। তার মধ্যে কারো private Google Doc এর link থাকতেই পারে।

প্রথম সমস্যার সমাধান হলো counter থেকে একটা একটা সংখ্যা না নিয়ে একবারে একটা range নেওয়া:

Counter Service পরের খালি range: 3001 থেকে 1 - 1000 1001 - 2000 2001 - 3000 Server A এখন দিচ্ছে: 7 Server B এখন দিচ্ছে: 1042 Server C এখন দিচ্ছে: 2913 ১,০০০টা link এ মাত্র একবার Counter এর কাছে যেতে হয় Counter কিছুক্ষণ down থাকলেও হাতের range শেষ না হওয়া পর্যন্ত কাজ চলে
প্রতিটা server একবারে ১,০০০ সংখ্যার একটা range নিয়ে নেয়, তারপর নিজের memory থেকে একটা একটা করে দেয়। দুইটা range কখনো মেলে না, তাই দুইটা server কখনো একই সংখ্যা পায় না।

দ্বিতীয় সমস্যার সমাধান হলো সংখ্যাটাকে base62 তে লেখার আগে একটা গোপন নিয়মে এলোমেলো করে নেওয়া। নিয়মটা এমন হতে হবে যাতে দুইটা আলাদা সংখ্যা কখনো একই ফল না দেয়। তাহলে uniqueness ও থাকে, আবার পাশাপাশি দুইটা link এর code ও একদম আলাদা দেখায়।

০৫ Hash দিয়ে বানালে hash collision এর কী হবে, আর key generation service কেন?

দ্বিতীয় উপায়: URL এর hash

Hash function যেকোনো লম্বা লেখা থেকে একটা নির্দিষ্ট মাপের “আঙুলের ছাপ” বানায়। একই লেখা দিলে সবসময় একই ছাপ আসে। MD5 এমন একটা hash function, যেটা সবসময় ১২৮ bit এর ফল দেয়। অর্কের Google Form link এর MD5 নিয়ে base62 তে লিখলে ২২ অক্ষর হয়। আমরা শুধু প্রথম ৭টা অক্ষর রাখি:

long_url = "https://docs.google.com/forms/d/e/1FAIpQLSd-hackathon.../viewform"   (80 অক্ষর)

MD5(long_url)        = 5adb277d61f7bf90c664c4722f3962bb   (128 bit, 32টা hex অক্ষর)
সংখ্যাটা base62 তে    = 2LrsIyw...                          (মোট 22 অক্ষর)
প্রথম 7 অক্ষর         = 2LrsIyw                             ← short code

5 বছর পরে collision এর সম্ভাবনা
  database এ আগে থেকেই আছে   = 18,250,000,000 code
  মোট জায়গা (62^7)           = 3,521,614,606,208
  নতুন code আগেরটার সাথে মিলে যাওয়ার সম্ভাবনা
                              ≈ 18.25 billion / 3.52 trillion
                              ≈ 0.52%
  মানে প্রায় প্রতি 190টা নতুন link এ 1টা collision

সমস্যাটা ঐ কেটে ফেলার মধ্যেই। ১২৮ bit এর পুরো hash এ দুইটা আলাদা URL এর ফল মিলে যাওয়া প্রায় অসম্ভব। কিন্তু ৭ অক্ষরে কাটার পর জায়গা অনেক ছোট হয়ে যায়। তখন দুইটা আলাদা URL একই ৭ অক্ষরের code পেতেই পারে। একে বলে hash collision। উপরের হিসাবে ৫ বছর পরে প্রায় প্রতি ১৯০টা নতুন link এ একটা collision হবে। এটা উপেক্ষা করার মতো সংখ্যা নয়।

তাই hash পদ্ধতিতে প্রতিবার database এ খোঁজ নিতে হয়:

আবার Long URL docs.google.com/forms/d/e/1FAIpQLSd-hackathon... MD5 hash, তারপর base62 5adb277d61f7... → 2LrsIyw... (22 অক্ষর) প্রথম ৭টা অক্ষর: 2LrsIyw বাকি ১৫টা অক্ষর ফেলে দেওয়া হয় Database এ 2LrsIyw আগেই আছে? অন্য কোনো URL এর code হিসেবে হ্যাঁ না হ্যাঁ: hash collision URL এর শেষে একটা salt জুড়ে আবার hash করে (চেষ্টা ২, ৩ ...) না: code টা খালি নতুন row INSERT হয় অর্ক পায় cc.lnk/2LrsIyw
৭টা অক্ষরে কেটে ফেলার পর দুইটা আলাদা URL এর code মিলে যেতে পারে। তাই প্রতিবার database এ খোঁজ নিতে হয়। মিলে গেলে URL এর সাথে একটু বাড়তি লেখা (salt) জুড়ে আবার hash করা হয় (বাঁয়ের dashed পথ)।

Collision হলে URL এর শেষে একটা ছোট বাড়তি লেখা জুড়ে দেওয়া হয়, যাকে বলে salt। Salt বদলালে hash ও পুরোপুরি বদলে যায়, তাই নতুন code পাওয়া যায়। এই পদ্ধতির আরো দুটো downside আছে। প্রতিটা নতুন link এর জন্য একটা বাড়তি database read লাগে। আর একই URL দুইজন আলাদা মানুষ দিলে দুজনেই একই code পায়। তখন দুজনের জন্য আলাদা expiry বা আলাদা click এর হিসাব রাখা যায় না।

তৃতীয় উপায়: key আগে থেকেই বানিয়ে রাখা

বাসের টিকিট কাউন্টারের কথা ভাবুন। কাউন্টারের লোকটা প্রতিটা যাত্রীর জন্য ছাপাখানায় দৌড়ায় না। তার হাতে আগে থেকে ছাপানো টিকিটের একটা বান্ডিল থাকে। যাত্রী এলে সে একটা টিকিট ছিঁড়ে দেয়। বান্ডিল শেষ হলে অফিস থেকে আরেকটা বান্ডিল নিয়ে আসে।

Key generation service (KGS) ঠিক এই কাজটাই করে। একটা offline job আগে থেকেই কোটি কোটি random ৭ অক্ষরের key বানিয়ে একটা table এ রেখে দেয়। App server গুলো KGS এর কাছ থেকে একবারে ১,০০০টা key এর একটা বান্ডিল নিয়ে নিজের memory তে রাখে। নতুন link এলে শুধু একটা key তুলে দেয়:

Offline job আগে থেকেই random ৭ অক্ষরের key বানিয়ে রাখে aZ3kP9q, 7bQx2Lm, Kp04sTe, ... (কোনোটা দুইবার নয়) unused_keys তৈরি key এর stock ১,০০০টা তোলে KGS batch করে key দেয় Server A memory তে 1,000 key Server B memory তে 1,000 key key তোলা আর used হিসেবে mark করা একই transaction এ হয় তাই একই key দুইজন পায় না। Server crash করলে তার হাতের key গুলো শুধু নষ্ট হয়, আর কোনো ক্ষতি নেই।
Key গুলো আগে থেকেই বানানো থাকে। নতুন link এলে server নিজের memory থেকে একটা key তুলে দেয়। তখন hash করতে হয় না, database এ collision খুঁজতেও হয় না।

KGS এর চারটা খুঁটিনাটি মনে রাখা দরকার:

তিনটা পদ্ধতি পাশাপাশি রাখলে:

পদ্ধতিসুবিধাDownside
Counter + base62Collision কখনো হয় না, হিসাব সরলCounter একটা SPOF; code আন্দাজ করা যায় (range আর এলোমেলো করা দিয়ে ঠিক করা যায়)
Hash + প্রথম ৭ অক্ষরCounter লাগে না; একই URL সবসময় একই code পায়Collision হয়, প্রতিবার database এ খুঁজতে হয়; data বাড়লে retry বাড়ে
Key generation serviceWrite এর সময় কোনো হিসাব বা খোঁজ নেই, code randomআলাদা একটা service চালাতে হয়; KGS এর জন্য standby লাগে
অর্ক কোনটা বেছে নিলো

অর্ক KGS বেছে নিলো। নতুন link বানানোর সময় database এ খোঁজাখুঁজি নেই, আর code গুলো random বলে আন্দাজ করার উপায়ও নেই। Interview এ counter range আর KGS দুটোই ভালো উত্তর। শুধু কেন বেছে নিলেন, সেটা trade-off দিয়ে বলতে পারলেই হলো।

০৬ 301 redirect নাকি 302 redirect? Click analytics কোথায় জমা হবে?

Hackathon এর registration চলছে। অর্ক প্রতিদিন সন্ধ্যায় একবার দেখে, poster থেকে কতজন এলো। এই সংখ্যাটা পাওয়া যাবে কিনা, তা নির্ভর করে একটা ছোট্ট সিদ্ধান্তের উপর: server কোন redirect code পাঠাবে।

HTTP তে 3xx দিয়ে শুরু হওয়া status code গুলো browser কে বলে, “যা খুঁজছেন, সেটা অন্য ঠিকানায় আছে।” নতুন ঠিকানাটা থাকে response এর Location header এ। এদের মধ্যে দুটো আমাদের কাজের:

301 MOVED PERMANENTLY Browser cc.lnk server Google Form ১ম click: GET /hack26 301 + Location Form খোলে ২য় click: সোজা Form এ server কিছুই জানে না 302 FOUND Browser cc.lnk server Google Form ১ম click: GET /hack26 302 + Location Form খোলে ২য় click: আবার GET /hack26 আবার server এ আসে click গোনা হয়: +1 302 + Location
301 পেলে browser ঠিকানাটা মনে রেখে দেয়। দ্বিতীয়বার সে আর আমাদের server কে জিজ্ঞেস করে না, তাই সেই click গোনা যায় না। 302 পেলে browser প্রতিবার আগে আমাদের server এ আসে।
বিষয়301 redirect302 redirect
দ্বিতীয় click কোথায় যায়সোজা আসল ঠিকানায়আবার আমাদের server এ
Server এর উপর চাপকমবেশি, প্রতিটা click আসে
Click গোনা যায়?না, বেশিরভাগ click আমাদের চোখের আড়ালেহ্যাঁ, প্রতিটা
পরে destination বদলানো যায়?কঠিন, পুরোনো browser পুরোনো ঠিকানাই মনে রাখেহ্যাঁ, সাথে সাথে

অর্কের দুটো জিনিসই দরকার। Click গুনতে হবে। আর registration শেষ হলে link টাকে “registration বন্ধ” page এ ঘুরিয়ে দিতে হবে। দুটোই শুধু 302 দিয়ে সম্ভব। তাই অর্ক 302 বেছে নিলো, যদিও এতে server এ চাপ বেশি পড়ে।

Click এর হিসাব কোথায় লিখবো

প্রথম ভাবনা হতে পারে: প্রতিটা click এ database এর একটা counter এক বাড়িয়ে দিই। কিন্তু peak এ সেকেন্ডে ৩৫,০০০টা click আসে। মানে সেকেন্ডে ৩৫,০০০টা write, যেখানে link বানানোর write মাত্র ৩৫০টা! তার উপর প্রতিটা redirect কে ঐ write শেষ হওয়ার জন্য অপেক্ষা করতে হতো।

এর বদলে click এর খবরটা একটা queue তে রেখে দেওয়া হয় (queue এর কথা সেশন ০৮ এ দেখেছেন)। Redirect সাথে সাথে ফেরত যায়:

GET /hack26 আসে Redirect Service code টা খুঁজে পায় student এর পথ হিসাবের পথ 302 সাথে সাথে ফেরত যায় ~5ms, কাউকে অপেক্ষা করতে হয় না Student Form দেখে হিসাবের কাজটা তার চোখে পড়ে না Click event যায় queue তে code, সময়, device, কোথা থেকে এলো Worker ১ মিনিটের click একসাথে যোগ করে Analytics DB hack26: আজ 1,240 click
Student এর redirect কখনো হিসাবের জন্য আটকে থাকে না। Click এর খবর queue তে জমা হয় (সেশন ০৮)। Worker পরে এক মিনিটের সব click একসাথে যোগ করে একবারে লেখে।

Worker queue থেকে event গুলো তুলে এক মিনিটের সব click একসাথে যোগ করে। তারপর hack26 এর জন্য একটাই write করে: “এই মিনিটে আরো ৪২টা click।” ফলে ৩৫,০০০টা ছোট write কমে গিয়ে প্রতি মিনিটে কয়েক হাজার write এ নেমে আসে।

Redirect এর পথ পরিষ্কার রাখুন

Redirect এর পথে শুধু সেই কাজটুকু থাকবে, যেটা ছাড়া redirect হয় না: code থেকে URL খুঁজে বের করা। বাকি সব কাজ (গোনা, device চেনা, দেশ বের করা) queue তে পাঠিয়ে পরে করুন। অর্কের dashboard এক মিনিট পিছিয়ে থাকলে কারো ক্ষতি নেই।

০৭ Read-heavy traffic এ cache, replica আর shard কোথায় বসবে?

Cache: সবচেয়ে বড় লাভ

সেকেন্ডে ৩৫,০০০টা read এর প্রতিটা database এ পাঠালে database হাঁপিয়ে উঠবে। কিন্তু এখানে একটা সুখবর আছে। সব link সমান জনপ্রিয় নয়। আজ সন্ধ্যায় hack26 এ হাজার click পড়ছে, অথচ গত বছরের একটা পিকনিকের link এ কেউ click করছে না। অল্প কিছু link ই বেশিরভাগ click পায়।

তাই সবচেয়ে জনপ্রিয় link গুলো Redis এ রাখা হয়। সেশন ০৪ এর cache-aside নিয়মটাই এখানে খাটে:

Redirect Service hack26 এর আসল URL টা দরকার Redis এ hack26 আছে? cache-aside (সেশন ০৪) HIT MISS HIT: ~1ms Redis থেকেই URL, সাথে সাথে 302 বেশিরভাগ click এখানেই শেষ হয় MISS: replica তে খোঁজে ~5-10ms, database replica (সেশন ০৩) পেলে Redis এ রেখে দেয়, TTL 24h
একটা code এর URL প্রায় কখনো বদলায় না, তাই cache এ রাখার জন্য এটা একদম উপযুক্ত। প্রথম click এ MISS হয়। তারপর ২৪ ঘণ্টা ধরে সেই link এর সব click Redis থেকেই উত্তর পায়।

URL shortener এ cache রাখা অন্য system এর চেয়ে সহজ। একটা code এর URL প্রায় কখনো বদলায় না। তাই cache এর data পুরোনো হয়ে যাওয়ার (cache invalidation) ঝামেলা প্রায় নেই। শুধু link ডিলিট হলে বা মেয়াদ শেষ হলে Redis থেকে key টা মুছে দিলেই চলে। Memory ভরে গেলে LRU নিয়মে সবচেয়ে কম ব্যবহার হওয়া link গুলো বাদ পড়ে। Estimation বলছে, hot link গুলো রাখতে মাত্র ৫ GB লাগে।

Replica: database এর read ভাগ করা

Cache miss গুলো যায় database এর read replica তে (replica এর কথা সেশন ০৩ এ দেখেছেন)। Write যায় primary তে, read ছড়িয়ে পড়ে কয়েকটা replica তে।

একটা ছোট ফাঁদ আছে। অর্ক link বানিয়েই সাথে সাথে WhatsApp group এ পাঠালো। কেউ এক সেকেন্ডের মধ্যে click করলো। Replica তে তখনো row টা পৌঁছায়নি (replication lag)। ফলে সে পেলো “link খুঁজে পাওয়া যায়নি”! এর সহজ সমাধান হলো, নতুন link বানানোর সময়েই সেটা Redis এ লিখে দেওয়া। তাহলে প্রথম click টা replica তে যাওয়ার আগেই cache এ উত্তর পায়।

Shard: ৯ TB ভাগ করে রাখা

৫ বছরে ৯ TB data একটা machine এ রাখা কষ্টকর। তাই data কয়েকটা shard এ ভাগ করা হয় (সেশন ০৫)। Shard key হিসেবে short_code সবচেয়ে ভালো, কারণ প্রতিটা redirect এর সাথে code টা থাকেই। hash(short_code) দেখে বোঝা যায় row টা কোন shard এ আছে। ফলে কোনো query কে সব shard ঘুরে আসতে হয় না।

ধরুন কোনো জনপ্রিয় গায়ক তার নতুন গানের link cc.lnk/gaan share করলেন। এক মিনিটে দশ লাখ click! সব click একই key তে, তাই একটা Redis node এর উপরেই সব চাপ পড়ে। এটাই সেশন ০৫ এর hot key সমস্যা। সমাধান হলো প্রতিটা redirect server এর নিজের memory তে কয়েক সেকেন্ডের জন্য একটা ছোট cache রাখা। তাহলে বেশিরভাগ click Redis পর্যন্ত যায়ই না। চাইলে CDN এর edge server ও এমন redirect এর উত্তর অল্প সময়ের জন্য রেখে দিতে পারে (CDN এর কথা সেশন ০৬ এ দেখেছেন)।

০৮ Expiry, custom alias আর abuse কীভাবে সামলাবো?

Expiry: মেয়াদ শেষ হলে কী হবে

Hackathon এর registration ৩১ ডিসেম্বর রাতে শেষ। তারপর কেউ poster দেখে link খুললে কী দেখবে? অর্ক চায়, সে যেন একটা ভদ্র বার্তা দেখে: “Registration শেষ, পরের বার দেখা হবে।”

মেয়াদ শেষ হওয়া row গুলো দুই ভাবে সামলানো হয়। প্রথমত, প্রতিটা click এর সময় expires_at দেখে নেওয়া হয়। একে বলে lazy expiry, কারণ মেয়াদ যাচাই হয় কেবল তখনই, যখন কেউ link টা চায়। দ্বিতীয়ত, প্রতি রাতে একটা cleanup job মেয়াদ শেষ হওয়া row গুলো মুছে দেয় (cron job এর কথা সেশন ০৯ এ দেখেছেন):

-- ১) প্রতিটা click এর সময় (lazy check)
SELECT long_url, expires_at FROM urls WHERE short_code = 'hack26';
-- expires_at < now() হলে: redirect নয়, "410 Gone" আর একটা সুন্দর page

-- ২) প্রতি রাত ৩টায় cleanup job (cron, সেশন ০৯)
DELETE FROM urls
WHERE  expires_at < now()
LIMIT  10000;
-- একবারে ১০,০০০ row; কিছু না পাওয়া পর্যন্ত বারবার চলে,
-- যাতে বড় একটা DELETE database কে অনেকক্ষণ আটকে না রাখে

মেয়াদ শেষ হলে 410 Gone ফেরত দেওয়া হয়। এর মানে “জিনিসটা ছিলো, কিন্তু এখন আর নেই।” 404 এর চেয়ে এটা বেশি সৎ উত্তর।

মেয়াদ শেষ হওয়া code কি আবার অন্য কাউকে দেওয়া উচিত? না, অন্তত তাড়াতাড়ি নয়। অর্কের poster হয়তো আরো ছয় মাস দেয়ালে থাকবে। ততদিনে hack26 অন্য কারো link হয়ে গেলে student রা অচেনা কোনো page এ পৌঁছাবে। Code এর জায়গা তো সাড়ে তিন লাখ কোটি, তাই পুরোনো code ফেরত নেওয়ার কোনো দরকারই নেই।

Custom alias: নিজের পছন্দের নাম

cc.lnk/hack26 মনে রাখা cc.lnk/1005ban এর চেয়ে অনেক সহজ। এই নিজের দেওয়া নামকে বলে custom alias। তবে কয়েকটা নিয়ম লাগে। নাম ৪ থেকে ১৬ অক্ষরের হবে, শুধু base62 এর অক্ষর আর - চলবে। api, admin, login এর মতো কিছু শব্দ আগে থেকেই সংরক্ষিত থাকবে, কারণ ওগুলো আমাদের নিজের path।

আসল বিপদটা লুকিয়ে আছে সময়ের মধ্যে। ধরুন একই মুহূর্তে দুইটা club hack26 নামটা চাইলো:

অর্ক: "hack26" চাই কোড ক্যাফের hackathon অন্য club: "hack26" চাই একই নামের আরেকটা hackathon 10:00:00.001 10:00:00.002 INSERT short_code = 'hack26' short_code হলো PRIMARY KEY, তাই unique যেটা আগে পৌঁছায় যেটা পরে পৌঁছায় INSERT সফল 201 Created: cc.lnk/hack26 duplicate key error 409 Conflict: অন্য একটা নাম বেছে নিন
দুইটা request এর মধ্যে মাত্র ১ millisecond এর তফাত। "আগে খুঁজে দেখি, খালি থাকলে INSERT করি" নিয়মে দুজনেই খালি দেখতো। তখন দুজনই ভাবতো নামটা তার। তাই সিদ্ধান্তটা দিতে হয় database এর unique constraint কে।

দুইটা server যদি আগে খুঁজে দেখে “নামটা খালি কিনা”, দুজনেই খালি দেখবে। তারপর দুজনেই INSERT করতে যাবে। তাই “খুঁজে দেখি, তারপর লিখি” নিয়মে কাজ হয় না। সিদ্ধান্তটা দিতে হয় database এর primary key কে। যার INSERT আগে পৌঁছায়, সে নামটা পায়। অন্যজন 409 Conflict পায়, মানে “এই নামটা ইতিমধ্যে নেওয়া হয়ে গেছে।”

আরেকটা সূক্ষ্ম জায়গা আছে। KGS এর random key আর কারো custom alias একই হয়ে যেতে পারে। দুটো একই table এ থাকে, আর short_code primary key। তাই এমন হলে KGS এর key দিয়ে INSERT fail করে, আর server পরের key টা নিয়ে আবার চেষ্টা করে। কোনো link হারায় না।

ছোট link এর একটা অন্ধকার দিক আছে। Link দেখে বোঝা যায় না সেটা কোথায় নিয়ে যাবে। কেউ একদিন cc.lnk/bonus500 বানিয়ে ছড়িয়ে দিলো, যার পেছনে একটা নকল মোবাইল ব্যাংকিং page। মানুষ সেখানে PIN দিয়ে টাকা হারালো। এমন প্রতারণাকে বলে phishing। তিনটা সহজ ব্যবস্থা অনেকটা আটকায়:

০৯ Pastebin এ কী বদলায় (object storage), আর পুরো design এর bottleneck কোথায়?

Hackathon এর দিন অর্ক আরেকটা জিনিস চাইলো। Contest এর problem statement আর sample input গুলো একটা ছোট link এ সবাইকে দেওয়া। এমন service কে বলে Pastebin: আপনি একটা লেখা paste করেন, একটা ছোট link পান, আর link খুললে সেই লেখাটাই দেখা যায়।

এখানে বেশিরভাগ design একই থাকে। Short code, KGS, expiry, cache সব আগের মতোই কাজ করে। পার্থক্য একটাই: আগে একটা row তে ২০০ byte এর URL থাকতো, এখন থাকবে ১০ KB থেকে ১০ MB এর লেখা। হিসাবটা দেখুন:

ধরে নিই
  নতুন paste প্রতিদিন    : 10 লাখ = 1,000,000
  গড় paste এর size      : 10 KB   (সর্বোচ্চ 10 MB)
  read : write           : 10 : 1

Storage
  1,000,000 × 10 KB      = 10 GB / দিন
  10 GB × 365            ≈ 3.6 TB / বছর
  → URL shortener এর 10 ভাগের 1 ভাগ item, অথচ storage প্রায় 2 গুণ

Read
  1,000,000 × 10 / 86,400   ≈ 116 read/sec
  116 × 10 KB               ≈ 1.2 MB/sec   (একটা viral paste এ এটা অনেক গুণ বাড়ে)

এত বড় লেখা database এর row তে রাখা ভালো নয়। Database এর row ছোট রাখার জন্যই বানানো। বড় row থাকলে backup ধীর হয়, replica তে copy হতেও বেশি সময় লাগে। তাই আসল লেখাটা রাখা হয় object storage এ। এটা এমন একটা storage, যেখানে একটা নাম (key) দিয়ে পুরো একটা file রাখা আর তোলা যায়। Amazon S3 এর মতো। পরের সেশনগুলোতে ছবি আর video রাখতেও আমরা এই object storage ই ব্যবহার করবো। Database এ থাকে শুধু ছোট তথ্য: paste এর id, file এর নাম, size আর মেয়াদ। এই ছোট তথ্যকে বলে metadata।

CLIENT SERVICE STORAGE লেখক পাঠক POST /api/v1/pastes GET /p/x7Kp2Qa Paste API লেখা জমা নেয় CDN Edge কাছের copy (সেশন ০৬) metadata আসল লেখা miss হলে Metadata DB id, size, expiry Object Storage pastes/x7Kp2Qa.txt (10 KB)
Database এ শুধু ছোট তথ্য থাকে: কোন paste, কত বড়, কবে মেয়াদ শেষ। আসল লেখাটা object storage এ file হিসেবে থাকে। Paste একবার লেখার পর আর বদলায় না, তাই CDN সেটা নিশ্চিন্তে cache করে রাখতে পারে।
বিষয়URL ShortenerPastebin
প্রতিটা item এর আকার~৫০০ byte~১০ KB, সর্বোচ্চ ১০ MB
আসল data কোথায়Database এর row তেObject storage এ file হিসেবে
Read এর উত্তরছোট একটা 302পুরো লেখাটা
CDN এর ভূমিকাঐচ্ছিকখুব কাজের, কারণ paste কখনো বদলায় না
চাপ কোথায়Request এর সংখ্যায়Storage আর bandwidth এ

পুরো ছবি

সব টুকরো জোড়া লাগালে অর্কের system দাঁড়ায় এমন:

CLIENT LOAD BALANCER APPLICATION CACHE + QUEUE DATA ইউজার ইউজার ইউজার Load Balancer path দেখে পাঠায় KGS key এর stock Write API নতুন link বানায় Redirect GET /:code, তারপর 302 click event Redis Cache code → URL, ~5 GB Click Queue event জমা থাকে hash(code) → shard miss worker Shard 1 + replica Shard 2 + replica Analytics DB click এর হিসাব
Highlighted পথটাই সবচেয়ে ব্যস্ত: click থেকে Redirect হয়ে Redis। Write API নতুন link এর key নেয় KGS থেকে, আর code hash করে ঠিক shard এ লেখে। Click এর হিসাব queue হয়ে আলাদা database এ যায়।

এই design যা দিলো

  • ছোট আর unique code: ৭ অক্ষরের base62 code, আর KGS থাকায় collision খোঁজার দরকার হয় না।
  • দ্রুত redirect: বেশিরভাগ click Redis থেকে ~1ms এ উত্তর পায়।
  • Click এর হিসাব: 302 এর কারণে প্রতিটা click গোনা যায়, আর গোনার কাজটা queue তে হয় বলে redirect ধীর হয় না।
  • বড় হওয়ার জায়গা: ৯ TB data shard এ ভাগ হয়ে থাকে, আর redirect server বাড়ানো যায় আলাদা করে।

যেখানে এখনো নজর রাখতে হবে

  • KGS একা থাকলে SPOF: তাই একটা standby KGS রাখা হয়। Server গুলোর হাতে ১,০০০টা করে key থাকায় কয়েক মিনিটের বিরতিতেও নতুন link বানানো থামে না।
  • Viral link একটা hot key: একটা link এ সেকেন্ডে হাজার হাজার click এলে একটা Redis node এর উপর চাপ পড়ে (সেশন ০৫)। প্রতিটা redirect server এর memory তে কয়েক সেকেন্ডের ছোট একটা cache রাখলে চাপ কমে।
  • 302 এর খরচ: প্রতিটা click আমাদের server এ আসে। হিসাব পাওয়া যায়, কিন্তু server আর bandwidth এর খরচও বাড়ে।
  • হিসাব একটু পিছিয়ে থাকে: Click count এক মিনিট পরে আপডেট হয়। Dashboard এর জন্য এটা চলে, টাকার হিসাবের জন্য চলতো না।
প্রথম design থেকে যা শেখার

অর্ক একটা box দিয়ে শুরু করেনি, শুরু করেছিলো দুটো প্রশ্ন দিয়ে: কী লাগবে, আর কতটা লাগবে। সেখান থেকে বেরোলো ১০০:১ read-write অনুপাত। আর সেই অনুপাতই ঠিক করে দিলো cache, আলাদা redirect service, আর queue তে পাঠানো click এর হিসাব। ভালো design এভাবেই requirement থেকে ধাপে ধাপে বেরিয়ে আসে।

১০ মগজে প্রেশার দিন

আগে নিজে ভাবুন। তারপর উত্তর খুলে দেখুন: বেশিরভাগ মানুষ কীভাবে ভাবে, সেই চিন্তায় কী বাদ পড়ে, আর আসল কথাটা কোথায়।

প্রশ্ন ১: URL এর MD5 hash নিয়ে প্রথম ৭টা অক্ষর রাখলেই তো short code হয়ে গেলো। MD5 এ collision তো প্রায় হয়ই না, তাই না?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“MD5 এর ফল ১২৮ bit এর। এত বড় জায়গায় দুইটা URL এর hash মিলে যাওয়ার সম্ভাবনা প্রায় শূন্য। তাই collision নিয়ে চিন্তা করার দরকার নেই।”

১২৮ bit এর কথাটা ঠিক। কিন্তু আমরা তো পুরো ১২৮ bit রাখছি না।

যে জায়গাটা যোগ করা দরকার

৭টা base62 অক্ষরে জায়গা মাত্র সাড়ে তিন লাখ কোটি, মানে প্রায় ৪২ bit। ৫ বছরে database এ ১,৮২৫ কোটি code জমবে। তখন প্রতিটা নতুন code এর আগেরগুলোর কোনো একটার সাথে মিলে যাওয়ার সম্ভাবনা প্রায় ০.৫%, মানে প্রতি ১৯০টা link এ একটা। তাই hash পদ্ধতিতে প্রতিবার database এ খুঁজতে হয়, আর মিলে গেলে salt জুড়ে আবার চেষ্টা করতে হয়। এই খোঁজাখুঁজি এড়াতেই KGS বা counter range বেছে নেওয়া হয়।

মনে রাখার কৌশল

Hash এর collision এর হিসাব হয় যত bit রাখছেন তার উপর, hash function যত bit দেয় তার উপর নয়। কেটে ছোট করলে collision ফিরে আসে।

প্রশ্ন ২: 301 এ browser ঠিকানা মনে রাখে, server এ চাপ কমে। তাহলে সবসময় 301 দেওয়াই কি ভালো?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“301 দিলে দ্বিতীয় click থেকে আর server এ আসে না। ফলে খরচ কমে, redirect ও দ্রুত হয়। তাই 301 ই ভালো।”

খরচের হিসাবটা ঠিক। কিন্তু এর বদলে কী হারাচ্ছেন, সেটা বাদ পড়ে গেছে।

যে জায়গাটা যোগ করা দরকার

301 দিলে বেশিরভাগ click আপনার চোখের আড়ালে চলে যায়। অর্ক তখন জানতেই পারতো না poster থেকে কতজন এলো। আরেকটা বড় সমস্যা হলো, পরে destination বদলানো প্রায় অসম্ভব হয়ে যায়। যে browser একবার 301 দেখেছে, সে পুরোনো ঠিকানাটাই মনে রাখে। তাই সঠিক উত্তর হলো: requirement দেখে বেছে নিন। Analytics বা destination বদলানো লাগলে 302। শুধু খরচ কমানোই উদ্দেশ্য হলে, আর link কখনো বদলাবে না জানা থাকলে, 301।

Interview এর ভাষায়

“301 না 302” প্রশ্নের এক শব্দের উত্তর নেই। উত্তরটা শুরু করুন এভাবে: “Analytics কি requirement এ আছে?” এতে interviewer বোঝেন, আপনি requirement থেকে সিদ্ধান্ত নিচ্ছেন।

প্রশ্ন ৩: Peak এ সেকেন্ডে ৩৫,০০০টা redirect। আরো কয়েকটা database replica যোগ করলেই তো সমস্যা মিটে যায়?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“Read বেশি হলে read replica বাড়ালেই হয়। দশটা replica হলে প্রত্যেকে সেকেন্ডে ৩,৫০০টা read পাবে, যেটা একটা database সহজেই সামলায়।”

Replica দিয়ে কাজ চলবে ঠিকই। কিন্তু data এর একটা বৈশিষ্ট্য এখানে কাজে লাগানো হয়নি।

যে জায়গাটা যোগ করা দরকার

সব link সমান জনপ্রিয় নয়। অল্প কিছু hot link ই বেশিরভাগ click পায়, আর তাদের মোট আকার মাত্র ৫ GB। এটা একটা Redis এর memory তেই এঁটে যায়। Redis থেকে উত্তর আসে ~1ms এ, যেখানে database এর লাগে ~5-10ms। তাই প্রথম ধাপ cache, আর replica থাকে শুধু miss গুলো সামলানোর জন্য। এতে দশটার জায়গায় দুই-তিনটা replica ই যথেষ্ট হয়। আর একটা link viral হলে local cache লাগে, কারণ তখন একটা key তেই সব চাপ পড়ে।

মূল pattern

Read-heavy system এ প্রথম প্রশ্নটা হলো: কোন data বারবার পড়া হয়, আর সেটা কত বড়? উত্তর memory তে এঁটে গেলে আগে cache, তারপর replica।

১১ যে টার্মগুলো শিখলাম

টার্মমানে
Functional requirementSystem কী কী করবে, তার তালিকা (link বানানো, redirect, expiry)।
Non-functional requirementSystem কতটা ভালোভাবে করবে: গতি, availability, data হারাবে কিনা।
Read-heavyযে system এ write এর চেয়ে read অনেক বেশি হয় (এখানে ১০০:১)।
Back-of-envelope estimationমোটা দাগের দ্রুত হিসাব, যাতে বোঝা যায় চাপ কোথায় পড়বে।
QPSQueries per second: সেকেন্ডে কতগুলো request আসে।
Peakদিনের সবচেয়ে ব্যস্ত সময়ের traffic, সাধারণত গড়ের কয়েক গুণ।
RedirectBrowser কে অন্য একটা ঠিকানায় পাঠিয়ে দেওয়া।
Base62৬২টা অক্ষর (0-9, a-z, A-Z) দিয়ে সংখ্যা লেখা; ৭ অক্ষরে প্রায় সাড়ে তিন লাখ কোটি code হয়।
Hash functionযেকোনো লেখা থেকে নির্দিষ্ট মাপের একটা ছাপ বানায়; একই লেখায় সবসময় একই ছাপ আসে।
Hash collisionদুইটা আলাদা input থেকে একই hash (বা একই কেটে নেওয়া code) আসা।
SaltHash করার আগে input এর সাথে জুড়ে দেওয়া বাড়তি লেখা, যাতে নতুন hash পাওয়া যায়।
Key generation service (KGS)আগে থেকে unique key বানিয়ে রাখে, আর server দের batch করে দেয়।
301 redirect“চিরদিনের জন্য সরে গেছে”: browser মনে রাখে, পরে আর server কে জিজ্ঞেস করে না।
302 redirect“এখন ওখানে আছে”: browser প্রতিবার server কে জিজ্ঞেস করে, তাই click গোনা যায়।
Location headerRedirect response এর যে অংশে নতুন ঠিকানাটা লেখা থাকে।
Custom aliasUser এর নিজের পছন্দ করা short code, যেমন hack26।
Lazy expiryমেয়াদ শেষ কিনা সেটা কেউ link চাওয়ার সময় যাচাই করা।
410 Gone“জিনিসটা ছিলো, এখন আর নেই” বোঝানোর HTTP status code।
409 ConflictRequest টা বর্তমান অবস্থার সাথে মেলে না, যেমন নামটা আগেই নেওয়া।
Phishingনকল page দেখিয়ে মানুষের password বা PIN হাতিয়ে নেওয়ার প্রতারণা।
Pastebinলেখা জমা দিয়ে ছোট link পাওয়ার service।
Metadataআসল data এর বর্ণনা (id, size, মেয়াদ), আসল data নয়।

১২ শর্ট সামারি

  1. একটা পূর্ণাঙ্গ design শুরু হয় requirement আর estimation দিয়ে। URL shortener এর হিসাব দেখায় যে write মাত্র সেকেন্ডে ৩৫০টা, কিন্তু read ৩৫,০০০টা, আর এই ১০০:১ অনুপাতই পুরো design এর দিক ঠিক করে দেয়।
  2. Short code হয় ৭ অক্ষরের base62, যাতে প্রায় সাড়ে তিন লাখ কোটি code ধরে। Code বানানো যায় counter range দিয়ে, hash দিয়ে (তখন collision খুঁজে retry করতে হয়), বা আগে থেকে key বানিয়ে রাখা KGS দিয়ে।
  3. Click গুনতে আর destination বদলাতে চাইলে 302 redirect লাগে। Click এর হিসাব queue হয়ে পরে লেখা হয়, যাতে redirect কখনো ধীর না হয়।
  4. Read সামলায় Redis cache, miss যায় replica তে, আর বড় data shard এ ভাগ হয়। Pastebin এ একই কাঠামো থাকে, শুধু আসল লেখাটা যায় object storage এ, আর database এ থাকে metadata।

১৩ মনে রাখুন