Part 01 · Backend Mindset

Anatomy of a Simple Backend

ArchitectureURLShortener

এক লাইন code লেখার আগে একটা URL shortener কে টুকরো টুকরো করে খুলে দেখা।

⏱ 1-2 ঘণ্টা · Station 10 / 153

120 অক্ষরের একটা link

একটা lesson এর link WhatsApp group এ দিতে চাও। Link টা 120 অক্ষরের, slash আর প্রশ্নবোধক চিহ্নে ভরা, চার লাইন জুড়ে ছড়িয়ে আছে। তাই একটা link shortener এ paste করলে, আর পেলে cf.io/q0U এর মতো কিছু। Group এর কেউ tap করলো, এক মুহূর্ত পরে lesson টা খুলে গেলো।

ওই tap আর lesson খোলার মাঝখানে একটা ছোট backend অবাক হওয়ার মতো অনেক কাজ করেছে। কাজের backend গুলোর মধ্যে এটা সবচেয়ে সহজগুলোর একটা, তাই খুলে দেখার জন্য একদম ঠিক জিনিস।

এই lesson দিয়ে Module 1 শেষ। "আসল" code লেখার আগে একটা URL shortener এর mental model বানাবো, টুকরো টুকরো করে, আর এই module এর প্রতিটা ধারণা এর ভেতরে খুঁজে পাবো: client আর server, চারটা কাজ, state, race, single source of truth আর business এর নিয়ম।

সমস্যা: আসলে কী কী করতে হবে?

User দের কী লাগে লিখে ফেলো, প্রতিটা দরকার system এর একেকটা অংশ হয়ে যায়।

User দের দরকার...তাই backend এর লাগে...
long URL কে ছোট বানানোছোট, unique code বানানোর একটা উপায়
ছোট link খুলে long URL এ পৌঁছানোCode থেকে URL দ্রুত খুঁজে পাওয়া, আর একটা redirect response
ভরসা যে একটা code সবসময় একই URL বোঝাবেএমন storage যেটা restart এর পরেও থাকে, সব server share করে (lesson 5 আর 7)
কতজন click করলো দেখাএমন click counter যেটা চাপের মধ্যেও click হারায় না (lesson 6)
Spam বা আজেবাজে কাজে ব্যবহার না হওয়ানিয়ম: শুধু http(s) link, হয়তো একটা blocklist আর rate limit (lesson 8)

আর এদের সবার পেছনে একটা অদৃশ্য দরকার আছে: লেখার চেয়ে পড়া অনেক অনেক বেশি। একটা link একবার বানানো হয়, click হতে পারে দশ লাখ বার। প্রায় প্রতিটা design এর সিদ্ধান্ত এই একটা fact দিয়ে ঠিক হয়।

কোট রাখার কাউন্টারের কথা ভাবো

বিয়ের হলে তুমি কোট জমা দিলে, আর পেলে নম্বর লেখা একটা ছোট টোকেন: 47। টোকেনটা ছোট, সাথে রাখা সহজ, আর নিজে থেকে এর কোনো মানে নেই। ফেরার সময় 47 দিলে, কোট ফেরত পেলে।

  • কাউন্টারের লোক প্রতিটা কোটের জন্য একটা unique নম্বর বানান। দুটো কোট কখনো একই নম্বর পাবে না।
  • র‍্যাকে কোট গুলো নম্বর ধরে রাখা থাকে, নিরাপদে, পুরো সন্ধ্যা।
  • টোকেন ফেরত দেওয়া মানে একটা খোঁজা: নম্বর → কোট।

URL shortener হলো link এর জন্য কোট রাখার কাউন্টার। long URL হলো কোট, ছোট code হলো টোকেন, আর redirect হলো কোট ফেরত দেওয়া।

four parts, two journeysbrowserrouterwhich handler?servicerules, base62storeq0U → long URLPOST long URLsaved as q0UGET /q0U302 Location: …the browser follows the redirect to the long URL
একটা long URL router হয়ে service এ যায়, service একটা code বানিয়ে store এ রাখে। পরে ছোট code এ একটা click একই রাস্তা ধরে, store long URL খুঁজে পায়, আর server একটা redirect দিয়ে উত্তর দেয় যেটা browser অনুসরণ করে।

Analogy টা কোথায় খাটে না। কোট রাখার কাউন্টার প্রতিটা কোট একবারই ফেরত দেয়। ছোট link "ফেরত দেওয়া" হয় লাখ লাখ বার, যার কাছেই code আছে তাকে, একসাথে অনেক server থেকে। এজন্যই store কে shared আর দ্রুত হতে হয়, আর link গোপন হলে code আন্দাজ করা কঠিন হতে হয়।

মূল জিনিসগুলো

System টা খুলে ফেললে সেই একই স্তরগুলো পাবে যেগুলো প্রায় সব backend এ দেখবে।

  1. Router। Method আর path দেখে একটা handler বাছে: POST /shorten → বানাও, GET /q0U → নিয়ে যাও, GET /q0U/stats → হিসাব দেখাও। বাকি যেকোনো কিছু পায় 404 বা 405।
  2. Handler আর service (business এর নিয়ম)। Input check করে (শুধু http:// বা https://), একটা code বানায়, কী উত্তর দেবে ঠিক করে। Lesson 3 এর "decide" আর "compute" এটাই।
  3. Code বানানো। একটা unique সংখ্যা নিয়ে সেটাকে base 62 এ লেখো (অঙ্ক, ছোট হাতের, বড় হাতের অক্ষর)। 100,000 হয়ে যায় q0U; 3 অক্ষরে আঁটে 62³ ≈ 238,000 টা code, আর 7 অক্ষরে 62⁷ ≈ 3.5 trillion। Unique হওয়াটা আসে সংখ্যা থেকে, তাই দুটো link কখনো একই code পায় না।
  4. Store। Code → long URL আর click এর সংখ্যা রাখে। এটা source of truth (lesson 7), তাই production এ এটা একটা database, memory র একটা map না (lesson 5)।
  5. Redirect। Link এ যাওয়া কোনো page না; এটা একটা HTTP response যার status 302 (বা 301) আর একটা Location header। Browser এটা দেখে সাথে সাথে long URL টা চায়। Shortener নিজে কখনো lesson টা দেয় না।
  6. Click গোনা। প্রতিটা follow এ একটা counter বাড়ে। খুব বেশি traffic এ পড়া-যোগ-লেখা করলে click হারায় (lesson 6), তাই হয় atomic increment লাগবে, নয়তো click গুলো event হিসেবে লিখে রেখে পরে গোনা হয়।
আগে অনুমান করো

একজন user cf.io/q0U তে tap করলো। Shortener এর server কী ফেরত পাঠায়?

ভেতরে কী হয়: বানানো, তারপর যাওয়া

ধাপে ধাপে দেখো

ছোট link এর দুই যাত্রা

BrowserRouterServiceStore

ধাপ 1/6 বানানো: POST /shorten

User paste করে https://codingfoding.com/learn, তারপর Shorten চাপে। Browser এটা একটা POST request এ পাঠায়।

request
POST /shorten
body
https://codingfoding.com/learn

মাথায় রাখার ছবি

যত backend দেখবে, প্রায় সবগুলোর কঙ্কাল এটার মতোই:

client → router → handlers → service (নিয়ম) → store

আর বড় হতে হতে কয়েকটা বাড়তি বাক্স জোড়া লাগে: বারবার পড়া জিনিসের জন্য store এর সামনে একটা cache, ধীর পাশের কাজের জন্য (যেমন click এর হিসাব update) একটা queue আর worker, আর load balancer এর পেছনে stateless অংশগুলোর আরো copy।

অচেনা কোনো backend দেখলে আগে এই কঙ্কালটা আঁকো, তারপর এর ওপর Module 1 এর প্রশ্নগুলো করো: client কারা, আর ওদের কাছ থেকে কী বিশ্বাস করা যাবে না? State থাকে কোথায়? Source of truth কী? দুটো request একসাথে একই কাজ করলে কী হয়? Business এর নিয়ম কোথায় থাকে?

চেষ্টা করো: design এর সিদ্ধান্ত

প্রতিটা case চালিয়ে দেখো

এই অংশটা তুমি কীভাবে বানাবে?

code = random(62^7)
নেওয়া থাকলে আবার চেষ্টা
কাজ করে: কাজ করে, আর code আন্দাজ করা কঠিন, গোপন link এর জন্য যেটা জরুরি। সমস্যা: collision check করতেই হবে (একটা unique constraint দিয়ে, lesson 6), আর table যত ভরে ততই এর সম্ভাবনা বাড়ে।

গোড়া থেকে code

পুরো shortener টা সাধারণ function হিসেবে, স্তরগুলোর গায়ে নাম লাগানো: একটা store (একটা map, database এর জায়গায়), নিয়ম আর base 62 সহ একটা service, আর একটা router যে method + path কে একটা call বানায়। এখনো network নেই, যাতে plumbing ছাড়া কঙ্কালটা দেখা যায়; এরকম function এর চারপাশে কীভাবে server বসাতে হয়, lesson 9 এ দেখেছি।

এর ভেতর দিয়ে আটটা request যায়: দুটো ঠিক link, একটা ফেরত যাওয়া link, দুটো click, একটা অচেনা code, হিসাব দেখা, আর এমন একটা method যেটা router নেয় না।

package main

import (
	"fmt"
	"strings"
)

const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

// --- store: the only part that remembers (a database in real life) ---
type link struct {
	url    string
	clicks int
}

var (
	links  = map[string]*link{}
	nextID = 100000 // a counter; real systems get unique ids from the database
)

// --- service: the business rules ---

// base62 turns a number into a short code: 100000 -> "q0U".
func base62(n int) string {
	code := ""
	for ; n > 0; n /= 62 {
		code = string(alphabet[n%62]) + code
	}
	return code
}

func shorten(url string) (int, string) {
	if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
		return 400, "only http(s) links"
	}
	code := base62(nextID)
	nextID++
	links[code] = &link{url: url}
	return 201, "short link: /" + code
}

func follow(code string) (int, string) {
	l, ok := links[code]
	if !ok {
		return 404, "no such link"
	}
	l.clicks++
	return 302, "Location: " + l.url
}

func stats(code string) (int, string) {
	l, ok := links[code]
	if !ok {
		return 404, "no such link"
	}
	return 200, fmt.Sprintf("%d clicks", l.clicks)
}

// --- router: turns a request into a call to the right handler ---
func handle(method, path, body string) (int, string) {
	switch {
	case method == "POST" && path == "/shorten":
		return shorten(body)
	case method == "GET" && strings.HasSuffix(path, "/stats"):
		return stats(strings.TrimSuffix(strings.TrimPrefix(path, "/"), "/stats"))
	case method == "GET":
		return follow(strings.TrimPrefix(path, "/"))
	}
	return 405, "method not allowed"
}

func main() {
	requests := [][3]string{
		{"POST", "/shorten", "https://codingfoding.com/learn"},
		{"POST", "/shorten", "ftp://files.example.com"},
		{"POST", "/shorten", "https://example.com"},
		{"GET", "/q0U", ""},
		{"GET", "/q0U", ""},
		{"GET", "/zzz", ""},
		{"GET", "/q0U/stats", ""},
		{"DELETE", "/q0U", ""},
	}
	for _, r := range requests {
		status, out := handle(r[0], r[1], r[2])
		req := strings.TrimSpace(r[0] + " " + r[1] + " " + r[2])
		fmt.Printf("%-46s -> %d %s\n", req, status, out)
	}
}

Output (পাঁচটা language এই একই):

POST /shorten https://codingfoding.com/learn   -> 201 short link: /q0U
POST /shorten ftp://files.example.com          -> 400 only http(s) links
POST /shorten https://example.com              -> 201 short link: /q0V
GET /q0U                                       -> 302 Location: https://codingfoding.com/learn
GET /q0U                                       -> 302 Location: https://codingfoding.com/learn
GET /zzz                                       -> 404 no such link
GET /q0U/stats                                 -> 200 2 clicks
DELETE /q0U                                    -> 405 method not allowed

Output টা পড়ি:

  • 201 short link: /q0U: base 62 এ id 100000। পরের link পায় q0V, কারণ counter এগিয়ে গেছে। Code ধাক্কা খেতে পারে না।
  • 400 only http(s) links: service এর একটা business নিয়ম, router বা store এর না।
  • 302 Location: … দুবার: link এ যাওয়া মানে একটা redirect, আর প্রতিটা একটা click গোনে।
  • 404 no such link: অচেনা code একটা স্বাভাবিক, প্রত্যাশিত উত্তর, crash না।
  • 200 2 clicks: হিসাবটা save করা state থেকে হিসাব করা, প্রতিটা follow এ update হয়।
  • 405 method not allowed: কোন path কোন method নেয় router জানে।

প্রতিটা লাইন Module 1 এর একেকটা ধারণার সাথে মেলে: store হলো source of truth (lesson 7), নিয়ম থাকে এক জায়গায় (lesson 8), আর আসল চাপে click counter এর জন্য atomic increment লাগবে (lesson 6)।

আসল engineering

  • পড়া বেশি, তাই পড়ার জন্য design করো। একবার লেখা, লাখবার পড়া। আসল shortener database এর সামনে একটা cache (Redis, বা CDN নিজেই) বসায়, আর follow এর পথ যতটা সম্ভব ছোট রাখে। (Module 6, lesson 17–18।)
  • Id আসে database থেকে। Process এর ভেতরের counter দুটো server হলেই ভাঙে (lesson 5)। Database এর sequence, বা প্রতিটা server কে block ধরে id দেওয়া, সব জায়গায় code unique রাখে।
  • Click এর হিসাব যায় background এ। প্রতিটা click এ counter update করা মানে সবচেয়ে ব্যস্ত পথে একটা বাড়তি লেখা। অনেক system click কে একটা queue তে event হিসেবে লিখে রাখে আর worker রা পরে যোগ করে। (Module 8।)
  • অপব্যবহার একটা আসল খরচ। Phishing link লুকাতে shortener ব্যবহার হয়। আসল গুলো URL কে blocklist এর সাথে মেলায়, প্রতি user আর IP তে বানানোর সীমা রাখে, আর দরকারে একটা code বন্ধ করতে পারে। (Module 9।)
  • Link চিরকালের। একবার code share হয়ে গেলে, সেটা কোথায় যায় বদলালে বা হারিয়ে ফেললে হাজারটা message এ ভেঙে যায়। Backup, migration আর 301 বনাম 302 এর সিদ্ধান্ত, সবই শুরুতে যত মনে হয় তার চেয়ে বেশি জরুরি।

যখন ভাঙে

প্রতিটা case চালিয়ে দেখো

একটা failure বাছো

GET /q0U → 404
(কালও কাজ করছিল)
ভাঙে: Link রাখা ছিল process এর memory তে। Restart মুছে দিয়েছে। Link রাখো database এ; server গুলো stateless রাখো (lesson 5)।

পড়ার আগে ভাবো

তোমার shortener এ 50 কোটি link আছে, ব্যস্ত সময়ে সেকেন্ডে 100,000 redirect হয়, কিন্তু নতুন link বানানো হয় সেকেন্ডে মাত্র 50টা। যদি একটাই দ্রুত করতে পারতে, বানানো নাকি যাওয়া, কোনটা বাছতে, আর design এ কী যোগ করতে?

উত্তর দেখো

যাওয়া। এটা 2,000 গুণ বেশি হয়, আর user এটাই টের পায়: redirect ধীর হলে share করা প্রতিটা link ভাঙা মনে হয়। Code → URL খোঁজার জন্য একটা cache যোগ করতে (প্রায়ই edge এ, user দের কাছাকাছি), follow এর পথ থেকে ধীর কাজ সরাতে (click গোনা background এ), আর load balancer এর পেছনে অনেকগুলো stateless copy চালাতে। Link বানানো একটু ধীর হলেও চলে, সোজা database এ যেতে পারে, source of truth এ।

নিজেকে check করো

1. কেউ ছোট link খুললে shortener এর server কী পাঠায়?

উত্তর

একটা redirect: status 302 (বা 301/307), সাথে long URL সহ একটা Location header। তারপর browser নিজেই ওই URL চায়।

2. Base 62 code কে ছোট রাখে কেন, আর এদের unique থাকার নিশ্চয়তা দেয় কী?

উত্তর

প্রতিটা অক্ষরের 62টা সম্ভাব্য মান, তাই কয়েকটা অক্ষরেই লাখ লাখ সংখ্যা ধরে যায় (62³ ≈ 238,000; 62⁷ ≈ 3.5 trillion)। Unique হয় কারণ একটা unique সংখ্যা encode করা হয়, যেমন database এর দেওয়া id, তাই দুটো link কখনো একটা code ভাগ করে না।

3. Request থেকে ভেতরের দিকে, shortener এর স্তরগুলোর নাম বলো।

উত্তর

Router (কোন handler?), handler আর service (নিয়ম, code বানানো), আর store (code → URL আর click এর source of truth)।

4. এই module এর কোন lesson ব্যাখ্যা করে click counter এ কেন atomic increment লাগে?

উত্তর

Lesson 6, Two Users, One Action: একসাথে অনেক click পড়া-যোগ-লেখা করলে race এর কারণে কিছু যোগ হারিয়ে যায়।

তোমার challenge: নিজের পছন্দের code

User রা নিজেরা code বাছতে চায়: POST /shorten, body এর মতো https://codingfoding.com/learn as learn।

  • পছন্দের code ফাঁকা থাকলে সেটাই দাও। নেওয়া থাকলে উত্তর দাও 409 code already taken।
  • পছন্দের code এ শুধু অক্ষর, অঙ্ক আর - চলবে, 3 থেকে 20 অক্ষর; বাকি সব 400।
  • নিশ্চিত করো যে পছন্দের code কখনো এমন code এর সাথে ধাক্কা খাবে না যেটা counter পরে বানাবে। (Hint: কেউ যদি q0W বেছে নেয়?)

লেখার আগে আন্দাজ করো দুজন একই মুহূর্তে learn চাইলে কী হবে, আর দুজনকেই পাওয়া থেকে আটকাতে lesson 6 কী বলে।

সফল হয়েছো যখন ... as learn দেয় 201 short link: /learn, দ্বিতীয়বার ... as learn দেয় 409, ... as !! দেয় 400, GET /learn redirect করে, আর তুমি বুঝিয়ে বলতে পারো তোমার design কীভাবে পছন্দের আর বানানো code আলাদা রাখে।

Mental model

সবচেয়ে সহজ backend এরও একই কঙ্কাল: router একটা handler বাছে, service নিয়ম খাটায়, আর store মনে রাখে, single source of truth হিসেবে। URL shortener হলো link এর জন্য কোট রাখার কাউন্টার: একটা unique ছোট code বানাও, রাখো, আর প্রতিটা click এর উত্তর দাও একটা redirect দিয়ে। যে পথ সবচেয়ে বেশি চলে (পড়া) তার জন্য design করো, state server এর বাইরে রাখো, আর shared আর একসাথে চলা যেকোনো জিনিস, যেমন id আর counter, atomic করো।

Station stamp

Chapter শেষ করো

পড়া শেষ হলে Complete চাপো, progress save হবে। XP শুধু Pro তে — Pro থাকলে এই chapter এ +10 XP পেতে।

Pro নিয়ে XP পাও →