সরাসরি পর্বে যান
পর্ব ০৬ · ৬৬-এর মধ্যে · মডিউল ১

একটি নকশা কীভাবে এগোয় — requirement থেকে API, data model আর deep dive

How a Design Unfolds — Requirements, Estimation, API, Data Model, Deep Dive & Failure Modes

Taught in Bangla, this lesson gives an eight-step method for any system design problem: scope, estimation, API, data model, high-level design, deep dive, failures and trade-offs, and wrap-up. A worked example designs an exam-results website, from peak-load estimates and a SQL access pattern to the final diagram, then tackles the thundering herd with cache pre-warming and request coalescing. It also covers pacing a 45-minute interview and writing design docs.

এই পর্বে যা শিখবেন

  • যেকোনো system design প্রশ্নে আট ধাপে এগোনো — কোন ধাপে কী প্রশ্ন, হাতে কী থাকবে
  • পাবলিক পরীক্ষার ফল প্রকাশের সাইটের পূর্ণ নকশা: হিসাব, API, data model আর চূড়ান্ত ছবি
  • Thundering herd, cache pre-warming আর request coalescing — সংখ্যা দিয়ে, simulator-এ
  • Failure mode খোঁজা, trade-off বলা, আর ইন্টারভিউর ৪৫ মিনিট ভাগ করা
  • বাস্তবের design doc আর ADR, আর AI-এর দেওয়া নকশা যাচাইয়ের চেকলিস্ট

১ · ফাঁকা বোর্ডের সামনে — কেন একটি কাঠামো দরকার

ইন্টারভিউর ঘরে আপনাকে বলা হলো: “একটি পাবলিক পরীক্ষার ফল প্রকাশের ওয়েবসাইট নকশা করুন।” সামনে ফাঁকা হোয়াইটবোর্ড, হাতে মার্কার, সময় ৪৫ মিনিট। অনেকে তখন হয় চুপ করে জমে যান, নয়তো সঙ্গে সঙ্গে load balancer, Kafka, Redis আর microservices-এর বাক্স আঁকতে শুরু করেন। দুটোই বিপদ — প্রথমটিতে সময় যায়, দ্বিতীয়টিতে এমন প্রশ্নের উত্তর দেওয়া হয় যা কেউ জিজ্ঞেসই করেনি।

অভিজ্ঞ প্রকৌশলীদের কাছে নকশা একটি প্রক্রিয়া: কিছু প্রশ্ন, একটি নির্দিষ্ট ক্রমে — এই পর্বের বিষয় সেটাই। মডিউল ১-এর আগের পাঁচ পর্বে যা শিখেছেন — system design কেন, requirement আর trade-off, হিসাব, latency আর availability — সব এখানে এক সুতোয় গাঁথা হবে।

গ্রামে দোতলা বাড়ি বানানোর আগে ভালো মিস্ত্রি জিজ্ঞেস করেন কতজন থাকবেন আর বাজেট কত; হিসাব করেন কত সিমেন্ট লাগবে; ঠিক করেন দরজা আর ঘরের সাজ; পুরো নকশা আঁকেন; সিঁড়ি আর ছাদের ঢালাই আলাদা করে খুঁটিয়ে দেখেন; ভাবেন বন্যার পানি উঠলে কী হবে; শেষে ভাবেন রক্ষণাবেক্ষণ আর ভবিষ্যতের তৃতীয় তলার কথা। System design-এর আটটি ধাপ হুবহু এই।

ধাপমূল প্রশ্নশেষে হাতে যা থাকবে
১. পরিধি ও requirementকী বানাচ্ছি, কার জন্য, কতটা ভালো — আর কী বানাচ্ছি না?FR, NFR আর “বাইরে রাখা” তালিকা
২. হিসাবকত অনুরোধ, কত ডেটা, কত bandwidth?peak QPS, storage, bandwidth
৩. APIবাইরের দুনিয়া সিস্টেমের সাথে কীভাবে কথা বলবে?২–৪টি মূল endpoint আর তাদের রূপ
৪. Data modelকোন কোন সত্তা, আর ডেটা কীভাবে খোঁজা হবে?টেবিল, key, access pattern, storage-এর ধরন
৫. High-level designএকটি অনুরোধ কোন পথে ঘোরে?বাক্স আর তীরের একটি ছবি
৬. Deep diveসবচেয়ে কঠিন এক-দুটি অংশ কীভাবে টিকবে?bottleneck-এর সমাধান, scaling, consistency
৭. Failure ও trade-offকী ভাঙতে পারে, তখন কী হবে? কী দাম দিলাম?failure-এর তালিকা ও প্রতিকার, সিদ্ধান্তের যুক্তি
৮. Wrap-upকীভাবে নজর রাখব, পরে কীভাবে বাড়বে?monitoring, বৃদ্ধির পথ, পরের কাজ
মূল ধারণা

ধাপগুলো সিঁড়ি নয়, চাকা। Deep dive করতে গিয়ে নতুন requirement বেরোয়, হিসাব করতে গিয়ে data model বদলায় — তখন পেছনে ফেরাই ঠিক কাজ। ক্রমটি শুরু করার নিরাপদ জায়গা আর কিছু না ভোলার চেকলিস্ট; চুক্তি নয়।

২ · ধাপ ১–৪: পরিধি, হিসাব, API আর data model

ধাপ ১ — পরিধি আর requirement: আগে প্রশ্ন

প্রথম কয়েক মিনিট প্রশ্ন করার জন্য। Functional requirement (FR) বলে সিস্টেম কী করবে — “roll নম্বর দিয়ে ফল দেখা যাবে।” Non-functional requirement (NFR) বলে কাজটি কতটা ভালোভাবে হবে — কত দ্রুত, কতজনের জন্য, কতটা নির্ভরযোগ্য, কতটা নিরাপদ। পর্ব ০২-এ দুটির পার্থক্য বিস্তারিত দেখেছেন। এখানে মনে রাখার মতো চারটি প্রশ্ন:

  • ব্যবহারকারী কারা, কতজন? মানুষ, প্রতিষ্ঠান, নাকি অন্য সার্ভিস? সাধারণ দিনে আর ভিড়ের সময়ে কতজন?
  • পড়া বেশি, না লেখা? প্রায় সবই পড়া (read-heavy) আর লেখাই বেশি (write-heavy) — দুটোর নকশা একেবারে আলাদা।
  • কোনটা কোনোভাবেই চলবে না? ভুল তথ্য, নাকি কয়েক সেকেন্ড দেরি? একটি বাছতে হলে কোনটি?
  • কী বাইরে থাকবে? “এবার পুনঃনিরীক্ষার আবেদন ধরছি না” — স্পষ্ট করে বলা পরিধিও একটি সিদ্ধান্ত।

ধাপ ২ — হিসাব: সংখ্যাই নকশার আকার ঠিক করে

পর্ব ০৩-এর back-of-the-envelope হিসাব এখানে কাজে লাগে। লক্ষ্য নিখুঁত সংখ্যা নয়, মাত্রা: প্রতি সেকেন্ডে ১০টি অনুরোধ, না ১০ হাজার? ১ GB ডেটা, না ১০০ TB? প্রথমটি হলে একটি সার্ভারেই চলে; দ্বিতীয়টি হলে ডেটা ভাগ করে বহু যন্ত্রে রাখতে হবে। তিনটি সংখ্যা প্রায় সবসময় বের করবেন — গড় আর সবচেয়ে ভিড়ের (peak) QPS, মোট storage, আর bandwidth. প্রতিটি অনুমান মুখে বলে নেবেন: “ধরে নিচ্ছি প্রতিজন দুবার দেখেন।”

ধাপ ৩ — API: রেস্তোরাঁর মেনু

রেস্তোরাঁয় আপনি মেনু দেখে অর্ডার দেন; রান্নাঘরে কে কী করছে, জানার দরকার পড়ে না। API (Application Programming Interface) হলো সিস্টেমের সেই মেনু — বাইরের ক্লায়েন্ট কী চাইতে পারে, কীভাবে চাইবে, কী ফেরত পাবে। এই ধাপে শুধু মূল দুই-চারটি endpoint লিখুন: HTTP method, path, request আর response-এর রূপ, ভুল হলে কোন status code. API পরের সব ধাপের চুক্তি; pagination, versioning আর idempotency-র খুঁটিনাটি আসবে পর্ব ০৮-এ (REST API নকশা)।

ধাপ ৪ — Data model: খোঁজার ধরন আগে, টেবিল পরে

মুদি দোকানের বাকির খাতার কথা ভাবুন। দোকানি পাতা সাজান ক্রেতার নাম ধরে, কারণ তিনি সবসময় নাম দিয়েই খোঁজেন — “রহিম সাহেবের বাকি কত?” তারিখ ধরে সাজালে প্রতিবার পুরো খাতা উল্টাতে হতো। Data model-এও তাই: আগে সত্তাগুলো (entity), তারপর সবচেয়ে জরুরি প্রশ্ন — access pattern: ডেটা কোন প্রশ্নে, কোন key দিয়ে, কত ঘনঘন পড়া বা লেখা হবে। সেখান থেকেই আসে key, index আর storage-এর ধরন। সম্পর্কযুক্ত ডেটা আর transaction-এর জন্য relational ডেটাবেস, শুধু key দিয়ে বিপুল পড়ার জন্য key-value store, বড় ফাইলের জন্য object storage — বিস্তারিত তুলনা পর্ব ১৯–২০-এ।

ধাপ ৩ আর ৪ ছোট রাখুন। ইন্টারভিউতে পুরো schema লিখতে গিয়ে অনেকে deep dive-এর সময় হারান। মূল টেবিল, primary key আর প্রধান access pattern — এটুকুই যথেষ্ট; বাকিটা প্রশ্ন এলে।

৩ · ধাপ ৫–৮: ছবি আঁকা, গভীরে নামা, ভাঙন খোঁজা আর গুছিয়ে শেষ

ধাপ ৫ — High-level design: বাক্স আর তীর

এবার ছবি। একটি অনুরোধ যে যে বাক্স পেরোয় — DNS, CDN, load balancer, অ্যাপ সার্ভার, cache, ডেটাবেস — সেগুলো আঁকুন, তীর দিয়ে জুড়ুন, তারপর একটি অনুরোধকে মুখে মুখে পুরো পথ হাঁটিয়ে আনুন। নিয়ম একটাই: প্রতিটি বাক্সের পেছনে একটি কারণ থাকবে — একটি requirement, বা ধাপ ২-এর একটি সংখ্যা। কারণ বলতে না পারলে বাক্সটি মুছে দিন; জটিলতা আসবে শুধু প্রয়োজনে।

ধাপ ৬ — Deep dive: যেখানে চাপ সবচেয়ে বেশি

সংখ্যাগুলো দেখলেই বোঝা যায় কোথায় চাপ জমবে — সেটিই bottleneck. এমন এক-দুটি অংশ বেছে গভীরে যান: ভিড় কীভাবে সামলাবেন (scaling), ডেটা কতটা মিলিয়ে রাখতে হবে (consistency), কোন অ্যালগরিদম লাগবে। ইন্টারভিউয়ার প্রায়ই নিজেই একটি অংশ দেখিয়ে দেন — “ধরুন সবাই একসাথে ঢুকল, তখন?”

ধাপ ৭ — Failure mode আর trade-off

প্রতিটি বাক্সে আঙুল রেখে জিজ্ঞেস করুন: “এটি বন্ধ হয়ে গেলে কী হবে?” যে অংশের মাত্র একটি কপি, সেটি বন্ধ হলে পুরো সিস্টেম বসে যায় — পর্ব ০৫-এর single point of failure. প্রতিটি failure mode-এর জন্য তিনটি কথা বলুন: কীভাবে টের পাবেন, ব্যবহারকারী কী দেখবেন, কীভাবে সামলাবেন। সাথে প্রতিটি বড় সিদ্ধান্তের trade-off স্পষ্ট করুন — “cache নিলাম, তাই কয়েক মিনিট পুরোনো তথ্য দেখানোর ঝুঁকি মেনে নিলাম।”

ধাপ ৮ — Wrap-up: নজরদারি, বৃদ্ধি আর পরের কাজ

শেষ কয়েক মিনিটে তিনটি কথা। এক, কোন সংখ্যার ওপর নজর রাখবেন — latency-র p99, error-এর হার, cache-এর hit ratio, ডেটাবেসের চাপ (observability নিয়ে পর্ব ৫০)। দুই, ব্যবহারকারী দশ গুণ হলে প্রথমে কোথায় ভাঙবে, তখন কী বদলাবেন। তিন, সময় পেলে পরের কোন কাজটি করতেন। শেষে এক মিনিটে পুরো নকশার সারাংশ।

সবচেয়ে প্রচলিত ফাঁদ: ধাপ ৫-এর ছবি শেষ হওয়ার আগেই একটি বাক্সে — ধরুন ডেটাবেস ভাগ করার কৌশলে — ২০ মিনিট ঢুকে যাওয়া। তখন পুরো ছবিটাই অসম্পূর্ণ থাকে। নিয়ম: আগে প্রস্থ, পরে গভীরতা।

৪ · পূর্ণ উদাহরণ — ফল প্রকাশের ওয়েবসাইট: পরিধি আর হিসাব

এবার আটটি ধাপ একটি বাস্তবধর্মী সমস্যায় চালিয়ে দেখা যাক। বড় পাবলিক পরীক্ষার ফল যেদিন প্রকাশ হয়, সেদিন লাখো পরীক্ষার্থী আর তাঁদের পরিবার প্রায় একই মুহূর্তে ফল দেখতে চান — ঈদের আগে অগ্রিম বাসের টিকিট বিক্রির প্রথম সকালের মতো, যখন সবাই একসাথে কাউন্টারে।

এই উদাহরণ পুরোপুরি কাল্পনিক। কোনো বাস্তব শিক্ষা বোর্ডের সিস্টেম, সংখ্যা বা অভিজ্ঞতা এখানে বর্ণনা করা হয়নি। প্রতিটি সংখ্যা হিসাবের সুবিধার জন্য ধরে নেওয়া; “ধরা যাক” দেখলেই বুঝবেন, এটি অনুমান।

আগে পুরো পথ এক নজরে; প্রতিটি ধাপের বিস্তারিত পরের অংশগুলোতে।

১ · পরিধিকী, কী নয়
২ · হিসাবpeak আর ডেটা
৩ · APIমেনু
৪ · Data modelkey
৫ · High-level designবাক্স আর তীর
৬ · Deep diveকঠিন মুহূর্ত
৭ · Failure ও trade-offকী ভাঙে
৮ · Wrap-upনজরদারি
ফল প্রকাশের সাইট — এই ধাপে
সিদ্ধান্ত
সংখ্যা বা চিহ্ন
  1. ধাপ ১ — পরিধি। মূল কাজ একটিই: বোর্ড, বছর আর roll দিয়ে ফল দেখা। NFR: ভিড়েও দ্রুত, কখনো ভুল নয়, নির্ধারিত সময়ের আগে নয়। পুনঃনিরীক্ষা, SMS আর ফল তৈরির সিস্টেম বাইরে। ধরা যাক পরীক্ষার্থী ২০ লাখ।
  2. ধাপ ২ — হিসাব। প্রথম ঘণ্টার গড় সেকেন্ডে ১,০০০ অনুরোধ, কিন্তু সবচেয়ে ভিড়ের মিনিটে প্রায় ৩,৬০০ — নকশা হবে এই peak ধরে। সব ফল মিলিয়ে ডেটা মাত্র প্রায় ২ GB; সমস্যা storage নয়, ভিড়।
  3. ধাপ ৩ — API. একটিই endpoint, GET দিয়ে একজনের ফল। 200 মানে পাওয়া গেছে, 404 মানে নেই বা এখনো প্রকাশ হয়নি, 429 মানে সীমা পেরিয়েছে, আর 503 মানে সাময়িক চাপ।
  4. ধাপ ৪ — Data model. একটি টেবিল, key তিন কলামে, কারণ একই roll ভিন্ন বোর্ডে বা বছরে আবার আসে। একবার লেখা, তারপর শুধু পড়া — তাই cache আর CDN এত কার্যকর।
  5. ধাপ ৫ — High-level design. CDN, rate limiter, load balancer, stateless API, cache, আর দরকারে read replica — পুরো ছবি নিচে ছবি ৬.১-এ।
  6. ধাপ ৬ — Deep dive. সবচেয়ে কঠিন মুহূর্ত দুপুর ২টা: ঠান্ডা cache-এ হাজারো miss. প্রথম সেকেন্ডে ডেটাবেসের কাজ ৪,০০০ query থেকে coalescing-এ ২,৫০০, আর pre-warming-এ শূন্য।
  7. ধাপ ৭ — Failure ও trade-off. ডেটাবেসে অতিরিক্ত চাপ, cache খালি, DNS বা CDN-এর গোলমাল। মিনিট ৩-এ cache restart হলে চাহিদা সেকেন্ডে ২,০৮০, সীমার দ্বিগুণেরও বেশি। বড় trade-off: static ফাইল, নাকি dynamic API?
  8. ধাপ ৮ — Wrap-up. নজর থাকবে hit ratio, p99, ডেটাবেসের চাপ আর error-এর হারে; hit ratio ৯৫ শতাংশের নিচে নামলেই সতর্কসংকেত। আগেভাগে peak-এর দ্বিগুণ চাপে load test.

ধাপ ১ বিস্তারিত — কী বানাচ্ছি, কী বানাচ্ছি না

FR — যা করবে

বোর্ড, বছর আর roll দিয়ে ফল দেখা, বিষয়ভিত্তিক grade-সহ; নির্ধারিত সময়ে সব ফল একসাথে প্রকাশ; প্রকাশের আগে ভেতরের পথে আপলোড আর যাচাই।

NFR — কতটা ভালো

ভিড়েও p99 latency ১ সেকেন্ডের নিচে; ভুল ফল কখনো নয় — দেরি সহ্য করা যায়, ভুল নয়; প্রকাশের দিনে সর্বোচ্চ availability; গণহারে ফল টেনে নেওয়া ঠেকানো।

বাইরে রাখা

পুনঃনিরীক্ষার আবেদন, SMS-এ ফল, নম্বর যোগ করে ফল তৈরি, প্রতিষ্ঠানভিত্তিক বিশ্লেষণ।

এই সিস্টেমের চরিত্র

একবার লেখা, লাখোবার পড়া — আর ভিড়ের সময় আগে থেকেই জানা। প্রকাশের পর ফল প্রায় বদলায় না; কখন আর প্রায় কতটা ভিড় আসবে, তাও আগে জানা। এই দুটি সত্যই পুরো নকশা ঠিক করবে।

ধাপ ২ বিস্তারিত — peak কত?

অনুমানগুলো আগে লিখে ফেলি। সবগুলোই ধরা যাক, বাস্তব কোনো পরীক্ষার তথ্য নয়:

  • মোট পরীক্ষার্থী ২০ লাখ; তাঁদের ৬০%-এর ফল প্রথম ঘণ্টাতেই দেখা হয়।
  • প্রতিটি ফল দুজন আলাদা যন্ত্রে দেখেন — পরীক্ষার্থী নিজে আর পরিবারের একজন।
  • প্রতি দর্শক গড়ে ১.৫টি অনুরোধ পাঠান (কেউ কেউ refresh করেন); সাইট ধীর হলে ৪টি।
  • প্রথম ঘণ্টার অনুরোধের ৪০% আসে প্রথম ১০ মিনিটে; সবচেয়ে ভিড়ের মিনিটে চাপ এই ১০ মিনিটের গড়ের ১.৫ গুণ।
  • ফলের JSON উত্তর ২ KB; পাতার কাঠামো (HTML, CSS, JavaScript, লোগো) ১৫০ KB.
peak RPS = পরীক্ষার্থী × প্রথম ঘণ্টার ভাগ × দর্শক × প্রতি দর্শকের অনুরোধ × প্রথম ১০ মিনিটের ভাগ ÷ ৬০০ × peak গুণক
2,000,000 × 0.6 × 2 × 1.5 × 0.4 ÷ 600 × 1.5 = 3,600

একই হিসাব ক্যালকুলেটরে — সাইট দ্রুত আর ধীর, সাথে bandwidth ও storage:

// Result day: back-of-the-envelope peak load. EVERY input is an assumption.
const examinees = 2000000;  // total examinees
const firstHour = 0.6;      // share whose result is checked in the first hour
const viewers   = 2;        // examinee + one family member/teacher, separate devices
const tenMin    = 0.4;      // share of the first hour's requests in the first 10 minutes
const peakX     = 1.5;      // busiest minute vs the 10-minute average
const resultKB  = 2;        // one JSON answer
const shellKB   = 150;      // page shell: HTML + CSS + JS + logo (first load)

const W = [5, 11, 11, 7, 14, 8, 12];                      // column widths
const row = (c) => c.map((x, i) => (i ? String(x).padStart(W[i]) : String(x).padEnd(W[i])))
  .join("");
const lookups = examinees * firstHour * viewers;          // viewers in hour 1
console.log("lookups in hour 1: " + lookups.toLocaleString("en-US"));
console.log(row(["site", "req/viewer", "req/hour", "avg/s", "10-min avg/s", "peak/s", "API Mbit/s"]));
for (const [site, perViewer] of [["fast", 1.5], ["slow", 4]]) {
  const reqHour = lookups * perViewer;
  const avg10 = reqHour * tenMin / 600;                  // 10 minutes = 600 s
  const peak = avg10 * peakX;
  const mbps = peak * resultKB * 8 / 1000;               // KB/s -> Mbit/s
  console.log(row([site, perViewer, reqHour.toLocaleString("en-US"),
    Math.round(reqHour / 3600), Math.round(avg10), Math.round(peak), mbps.toFixed(1)]));
}
const shellPeak = lookups * tenMin / 600 * peakX;         // new viewers per second at peak
const gbps = shellPeak * shellKB * 8 / 1e6;               // KB/s -> Gbit/s
console.log("page shell from origin, no CDN: " + gbps.toFixed(2) + " Gbit/s");
console.log("all results stored (1 KB/row): " + (examinees / 1e6).toFixed(1) + " GB");

আউটপুট:

lookups in hour 1: 2,400,000
site  req/viewer   req/hour  avg/s  10-min avg/s  peak/s  API Mbit/s
fast         1.5  3,600,000   1000          2400    3600        57.6
slow           4  9,600,000   2667          6400    9600       153.6
page shell from origin, no CDN: 2.88 Gbit/s
all results stored (1 KB/row): 2.0 GB

চারটি শিক্ষা। এক, peak (৩,৬০০) প্রথম ঘণ্টার গড়ের (১,০০০) ৩.৬ গুণ; গড় ধরে নকশা করলে ভিড়ের মুহূর্তেই সাইট বসে যেত। দুই, একই মানুষ, শুধু সাইট ধীর বলে peak ৯,৬০০ — ধীরগতি নিজেই ভিড় বাড়ায়; অংশ ৬-এ একে retry storm নামে দেখবেন। তিন, ফলের JSON-এ peak-এ মাত্র প্রায় ৫৮ Mbit/s, কিন্তু ১৫০ KB-র পাতার কাঠামো origin থেকে দিলে প্রায় ২.৮৮ Gbit/s — তাই স্থির ফাইল যাবে CDN থেকে। চার, সব ফল মিলিয়ে মাত্র প্রায় ২ GB, একটি সাধারণ সার্ভারের মেমরিতেই ধরে; ডেটা ভাগ করার (sharding) দরকার নেই, আসল লড়াই ভিড়ের সাথে। firstHour ০.৮ করে দেখুন peak কত হয়।

৫ · উদাহরণ চলছে — API, data model আর চূড়ান্ত নকশার ছবি

ধাপ ৩ — API: একটিই মূল endpoint

সরল রাখুন। সাধারণ মানুষের কাজ একটিই — নিজের ফল দেখা। তাই একটি endpoint, যার path-এই বছর, বোর্ড আর roll থাকে: GET /v1/results/{year}/{board}/{roll}.

GET /v1/results/2026/north/104521 HTTP/1.1
Host: results.example.org
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=300

{
  "board": "north", "year": 2026, "roll": 104521,
  "name": "Nusrat Jahan", "gpa": 5.00,
  "grades": { "bangla": "A+", "math": "A+" }
}
Statusকখননকশার খুঁটিনাটি
200 OKফল পাওয়া গেছেCDN আর ব্রাউজার কয়েক মিনিট কপি রাখতে পারে
404 Not Foundএই বোর্ড-বছর-roll-এ ফল নেই, বা এখনো প্রকাশ হয়নিCache-Control: no-store, যাতে “পাওয়া যায়নি” উত্তর প্রকাশের পরেও CDN-এ আটকে না থাকে
429 Too Many Requestsএকজন ক্লায়েন্ট সীমার বেশি অনুরোধ করেছেসাথে Retry-After; rate limiter-এর কাজ (পর্ব ৪৯)
503 Service Unavailableসাময়িক অতিরিক্ত চাপসাথে Retry-After আর পাতায় শান্ত একটি বার্তা

GET কেন? শুধু পড়া হচ্ছে; GET নিরাপদ আর idempotent, আর HTTP-র নিয়মে এর উত্তর cache করা যায় — তাই CDN আর ব্রাউজার নির্ভয়ে কপি রাখতে পারে, retry-ও নিরাপদ। Path-এ বছর আর বোর্ড কেন? একই roll ভিন্ন বোর্ডে বা বছরে থাকতে পারে। রেজিস্ট্রেশন নম্বরও চাইব? চাইলে গণহারে অন্যের ফল টেনে নেওয়া কঠিন হয়, কিন্তু ব্যবহারকারীর ঝামেলা বাড়ে — একটি trade-off. ফল আপলোডের ভেতরের API সাধারণ মানুষের জন্য খোলা নয়।

ধাপ ৪ — Data model: একটি টেবিল, একটি key

সত্তা মাত্র কয়েকটি: পরীক্ষা (বছর), বোর্ড, আর পরীক্ষার্থীর ফল — roll, রেজিস্ট্রেশন নম্বর, নাম, GPA আর বিষয়ভিত্তিক grade. Access pattern আরও সরল: প্রকাশের দিনের প্রায় সব পড়া একটিই প্রশ্ন — “এই বোর্ড, এই বছর, এই roll-এর ফল কী?” লেখা হয় একবার, প্রকাশের আগে, একসাথে। তাই key হবে তিনটি কলাম মিলিয়ে একটি composite key.

নিচের লাইভ SQL এডিটরে টেবিলটি বানিয়ে দেখুন ডেটাবেস কোন পথে খোঁজে। এটি একটি single-node SQLite ডেমো: index আর query plan দেখায়; replication, cache বা বহু যন্ত্রে ভাগ করা ডেটাবেস দেখায় না। SQLite-এর EXPLAIN QUERY PLAN query না চালিয়েই পথটি বলে দেয়।

-- Single-node SQLite demo: one table, one main access pattern.
CREATE TABLE results (
  board  TEXT    NOT NULL,          -- fictional board code
  year   INTEGER NOT NULL,
  roll   INTEGER NOT NULL,
  reg_no TEXT    NOT NULL,
  name   TEXT    NOT NULL,
  gpa    REAL    NOT NULL,
  grades TEXT    NOT NULL,          -- JSON: subject -> grade
  PRIMARY KEY (board, year, roll)   -- the lookup key
) WITHOUT ROWID;

INSERT INTO results VALUES
 ('north', 2026, 104521, 'R-7781', 'Nusrat Jahan', 5.00, '{"bangla":"A+","math":"A+"}'),
 ('north', 2026, 104522, 'R-7782', 'Rafi Ahmed',   4.83, '{"bangla":"A","math":"A+"}'),
 ('south', 2026, 104521, 'R-9120', 'Tanvir Hasan', 4.50, '{"bangla":"A","math":"A-"}'),
 ('north', 2025, 104521, 'R-6603', 'Mim Akter',    4.92, '{"bangla":"A+","math":"A"}');

EXPLAIN QUERY PLAN
SELECT name, gpa, grades FROM results
WHERE board = 'north' AND year = 2026 AND roll = 104521;

EXPLAIN QUERY PLAN
SELECT name, gpa FROM results WHERE roll = 104521;

SELECT board, year, roll, name, gpa FROM results WHERE roll = 104521;

প্রথম plan: SEARCH results USING PRIMARY KEY (board=? AND year=? AND roll=?) — তিনটি কলামই বাঁ দিক থেকে মিলেছে, তাই B-tree-তে সরাসরি একটি সারিতে নামা; টেবিলে ৪টি সারি থাকুক বা ২০ লাখ, কাজ মাত্র কয়েকটি page পড়া। দ্বিতীয় plan: SCAN results — শুধু roll দিলে key-এর প্রথম দুই কলাম বাদ পড়ে, গাছে নামার শুরুর বিন্দু থাকে না, তাই পুরো টেবিল পড়া। শেষ query-তে একই roll ১০৪৫২১-এর ৩টি সারি: north 2025 Mim Akter 4.92, north 2026 Nusrat Jahan 5 আর south 2026 Tanvir Hasan 4.5 — এজন্যই key-তে তিনটি কলাম। WITHOUT ROWID দিলে SQLite পুরো সারিটাই primary key-এর B-tree-তে রাখে। PostgreSQL বা MySQL-এও composite index বাঁ দিক থেকে কাজ করে (পর্ব ২১)।

ধাপ ৫ — চূড়ান্ত high-level design

এবার পুরো ছবি; প্রতিটি বাক্স কোনো সংখ্যা বা requirement-এর জবাব:

ফল প্রকাশের সাইটের চূড়ান্ত high-level design

CDN, rate limiter, stateless API, pre-warmed cache, read replicas and a publish job

ফল প্রকাশের সাইটের high-level design ব্যবহারকারী DNS CDN edge — কাছের সার্ভার Rate limiter Load balancer Result API ×N Cache প্রকাশ-প্রস্তুতি job (প্রকাশের আগে) Read replica Primary DB Monitoring Object storage ব্রাউজার · মোবাইল নাম → IP স্থির পাতা + ফলের response-এর কপি সীমা ছাড়ালে 429 stateless সার্ভার pre-warmed ×২ · শুধু পড়া লেখা শুধু এখানে hit ratio · p99 · DB আগে বানানো static ফাইল (বিকল্প পথ) আপলোড → যাচাই → pre-warm → flag চালু miss হলে replication pre-warm origin
ছবি ৬.১ — কঠিন রেখা মূল পথ, ভাঙা রেখা শর্তসাপেক্ষ বা বিকল্প পথ। সংখ্যাগুলো অংশ ৪-এর অনুমান থেকে; বাক্সের সংখ্যা ও আকার load test দেখে ঠিক হবে।

একজন অভিভাবকের অনুরোধ ধরে পথটি হাঁটি। DNS নাম থেকে কাছের CDN edge-এর ঠিকানা দিল; পাতার কাঠামো এল CDN থেকেই, মূল সার্ভার (origin) জানলই না — CDN-এর খুঁটিনাটি পর্ব ১৭-এ। ফলের অনুরোধ CDN-এ না মিললে rate limiter আর load balancer পেরিয়ে যেকোনো একটি stateless API সার্ভারে — কেউ কারও আলাদা তথ্য মনে রাখে না, তাই ভিড় বাড়লে শুধু সার্ভার বাড়ালেই হয়। API আগে cache-এ খোঁজে; আগে থেকে ভরা বলে প্রায় সবসময় পায়, না পেলে read replica থেকে পড়ে cache-এ রাখে। লেখা হয় শুধু primary-তে, প্রকাশের আগে একটি job-এর মাধ্যমে, আর replication-এ কপিগুলো হালনাগাদ হয়। বাঁ দিকের ভাঙা বাক্সটি বিকল্প: আগে থেকে বানানো ফলের ফাইল object storage-এ, যা দরকারে CDN সরাসরি দিতে পারে।

৬ · Deep dive — প্রকাশের মুহূর্তের thundering herd

কোন অংশে গভীরে যাবেন? সংখ্যাই বলে দেয়: দুপুর ২টা ০০ মিনিট ০০ সেকেন্ড। হাজারো মানুষ পাতা খুলে বসে আছেন; ঘড়ির কাঁটা ঘুরতেই সবাই একসাথে চাপ দেন, অথচ cache তখনো ঠান্ডা — এক মিনিট আগেও ফল প্রকাশিতই ছিল না। বিপুল অনুরোধ cache-এ না পেয়ে একসাথে ডেটাবেসের দরজায় — একে বলে thundering herd, বুনো পশুর পালের হঠাৎ দৌড়; cache-এর প্রসঙ্গে আরেক নাম cache stampede.

রেলস্টেশনের অগ্রিম টিকিটের কাউন্টার সকাল ৮টায় খোলে; ৮টা বাজতেই ধাক্কাধাক্কি, আর অধৈর্যরা বারবার সামনে ঠেলে ভিড় আরও বাড়ান। সমাধান তিন রকম: আগের রাতেই টিকিট ছাপিয়ে খামে ভরে রাখা (pre-warming), একই পরিবারের পাঁচজনের বদলে একজন সবার টিকিট নেওয়া (request coalescing), আর ভিন্ন রুটের টিকিট ভিন্ন সময়ে ছাড়া (ধাপে ধাপে প্রকাশ)।

প্রথম সেকেন্ডটি ধীরে ধীরে দেখুন। ধরা যাক প্রথম সেকেন্ডে ৪,০০০ অনুরোধ আসে ২,৫০০টি আলাদা roll-এর জন্য, আর ডেটাবেস নিরাপদে সেকেন্ডে ১,০০০টি query সামলায়।

  1. ব্যবহারকারীপ্রথম সেকেন্ড
  2. APIstateless
  3. Cacheফলের key
  4. Databaseসীমা ১,০০০/সে.
প্রথম সেকেন্ডের হিসাব (সব সংখ্যা ধরা যাক)
মুহূর্ত
অনুরোধ
DB-তে query
DB-র সামনে সারি
নতুন অনুরোধের অপেক্ষা
  1. ১:৫৯:৫৯ — ঝড়ের আগে। হাজারো মানুষ পাতা খুলে বসে, কেউ কেউ বারবার refresh করছেন। API জানাচ্ছে “এখনো প্রকাশ হয়নি” — তথ্যটি প্রতিটি API সার্ভারের মেমরিতে রাখা একটি flag, তাই ডেটাবেসে query যায় না। কিন্তু cache-এ একটিও ফল নেই।
  2. ২:০০:০০ — কোনো সুরক্ষা ছাড়া। প্রথম সেকেন্ডে ৪,০০০ অনুরোধ, ২,৫০০টি আলাদা roll-এর জন্য। Cache খালি, তাই ৪,০০০টিই miss হয়ে ডেটাবেসে, যা পারে ১,০০০টি। এক সেকেন্ড পরে ৩,০০০টি query সারিতে; নতুন কেউ এলে অপেক্ষা ৩,০০০ ÷ ১,০০০ = প্রায় ৩ সেকেন্ড।
  3. ২:০০:০১ — retry storm. পাতা ঘুরছে দেখে অপেক্ষমাণ ৩,০০০ জনের অর্ধেক refresh চাপলেন: নতুন ৪,০০০ আর ১,৫০০ নকল মিলে ৫,৫০০টি query. সারি ৩,০০০ + ৫,৫০০ − ১,০০০ = ৭,৫০০, অপেক্ষা প্রায় ৭.৫ সেকেন্ড। ধীরগতি ভিড় বাড়াচ্ছে, ভিড় ধীরগতি বাড়াচ্ছে।
  4. প্রতিকার ১ — request coalescing. একই roll-এর একসাথে আসা অনুরোধ একটি query-তে মিলল: প্রথমটি ডেটাবেসে যায়, বাকিরা সেই উত্তরের অপেক্ষায়। Query এখন ২,৫০০ — যতগুলো আলাদা roll; সারি ১,৫০০, অপেক্ষা প্রায় ১.৫ সেকেন্ড, আর refresh নতুন query বানায় না। তবু আলাদা roll বেশি হলে coalescing একা যথেষ্ট নয়।
  5. প্রতিকার ২ — cache আগে ভরে রাখা। প্রকাশের এক ঘণ্টা আগে একটি job ২০ লাখ ফলই cache-এ ভরে রেখেছে, “প্রকাশিত” flag বন্ধ রেখে; ২:০০:০০-তে শুধু flag চালু হলো। ৪,০০০টিই hit, ডেটাবেসে একটিও query নেই। জায়গা লাগে ২০ লাখ × ২ KB ≈ ৪ GB — একটি সাধারণ cache cluster-এই ধরে।
  6. প্রতিকার ৩ — ধাপে প্রকাশ আর CDN. সমান চারটি দলে ১০ মিনিট পরপর প্রকাশ করলে প্রথম সেকেন্ডের ভিড় দলপ্রতি প্রায় ৪,০০০ ÷ ৪ = ১,০০০. CDN ফলের উত্তর কয়েক মিনিট রাখে, আর অনেক CDN একই URL-এর একসাথে আসা অনুরোধ এক করে origin-এ পাঠায়। তিনটি একসাথে থাকলে ডেটাবেস প্রায় অলস। দাম: সবাই একসাথে ফল পান না — এটি নীতিরও সিদ্ধান্ত।
তিন অস্ত্র

Cache warming (pre-warming): ভিড়ের আগেই cache ভরা — জানা ভিড়ের সবচেয়ে শক্তিশালী উত্তর। Request coalescing: একই key-এর একসাথে আসা miss থেকে উৎসে একটিই অনুরোধ; Go-র singleflight বা nginx-এর proxy_cache_lock এ কাজ করে (আচরণ টুলভেদে আলাদা)। Staggered release: ভিড়কে সময়ের ওপর ছড়িয়ে দেওয়া। বাকি cache কৌশল — TTL, eviction, hot key — পর্ব ২৮–৩০-এ।

এবার মিনিট ধরে দেখা যাক। অংশ ৪-এর অনুমানে প্রথম ১০ মিনিটে প্রায় ৪.৮ লাখ পরীক্ষার্থী প্রথমবার ফল দেখেন, শুরুর দিকে বেশি। মডেল সরল: পরীক্ষার্থী যে মিনিটে দেখেন, পরিবারের সদস্য দেখেন তার পরের মিনিটে; cache-এ একবার আসা roll আর হারায় না। পরিস্থিতি তিনটি: cache ঠান্ডা, আগে থেকে ভরা, আর ভরা cache মিনিট ৩-এ হঠাৎ খালি।

// Result day, first 10 minutes: database queries/s behind a cache.
// Same assumptions as the estimate above; none of this is real board data.
const firstTen  = 2000000 * 0.6 * 0.4;      // examinees whose first lookup is in minutes 1-10
const share     = [16, 14, 12, 11, 10, 9, 8, 7, 7, 6]; // % of them per minute (front-loaded)
const perViewer = 1.5;                      // requests per viewer (lookup + some refresh)
const DB_SAFE   = 1000;                     // queries/s the database serves safely (assumed)
const fresh = share.map((s) => firstTen * s / 100 / 60);  // first-time rolls per second

// The examinee looks in minute m; a family member looks at the same roll in minute m + 1.
// Cache-aside: only the FIRST request for a roll misses; later ones hit (results never change).
function dbLoad(m, mode) {
  if (mode === "warm") return 0;                 // all results loaded before the release
  if (mode === "restart" && m < 2) return 0;     // warm cache until minute 3 begins
  if (mode === "restart" && m === 2) return fresh[2] + fresh[1]; // emptied: family rolls miss
  return fresh[m];                               // cold (or emptied) cache: new rolls miss
}
const W = [3, 7, 10, 12, 11, 16];
const row = (c) => c.map((x, i) => String(x).padStart(W[i])).join("");
const flag = (q) => Math.round(q) + (q > DB_SAFE ? " OVER" : "");
console.log("database serves " + DB_SAFE + " queries/s safely (assumed)");
console.log(row(["min", "req/s", "cold hit", "cold DB/s", "warm DB/s", "restart@3 DB/s"]));
for (let m = 0; m < 10; m++) {
  const rps = perViewer * fresh[m] + (m > 0 ? perViewer * fresh[m - 1] : 0);
  const cold = dbLoad(m, "cold");
  console.log(row([m + 1, Math.round(rps), (100 * (1 - cold / rps)).toFixed(1) + "%",
    flag(cold), dbLoad(m, "warm"), flag(dbLoad(m, "restart"))]));
}

আউটপুট:

database serves 1000 queries/s safely (assumed)
min  req/s  cold hit   cold DB/s  warm DB/s  restart@3 DB/s
  1   1920     33.3%   1280 OVER          0               0
  2   3600     68.9%   1120 OVER          0               0
  3   3120     69.2%         960          0       2080 OVER
  4   2760     68.1%         880          0             880
  5   2520     68.3%         800          0             800
  6   2280     68.4%         720          0             720
  7   2040     68.6%         640          0             640
  8   1800     68.9%         560          0             560
  9   1680     66.7%         560          0             560
 10   1560     69.2%         480          0             480

মিনিট ২-এর ৩,৬০০ অনুরোধ/সেকেন্ড অংশ ৪-এর peak-এর সাথে মেলে। ঠান্ডা cache-এ প্রথম মিনিটে hit মাত্র ৩৩.৩% — পরিবারের কেউ তখনো আসেননি, প্রায় সব roll নতুন। ডেটাবেস প্রথম দুই মিনিটে সীমা ছাড়ায় (১,২৮০ আর ১,১২০), মিনিট ৩-এ ৯৬০ — সীমার ৯৬%, যেখানে পর্ব ০৪-এর queueing অনুযায়ী অপেক্ষা আকাশ ছোঁয়। সবচেয়ে ভিড়ের মুহূর্তেই cache সবচেয়ে ঠান্ডা। আগে ভরা cache-এ দশ মিনিটই শূন্য। মিনিট ৩-এ restart হলে চাহিদা ২,০৮০ — নতুন ৯৬০টি roll আর মিনিট ২-এর পরিবারের ১,১২০টি, যেগুলো এক মিনিট আগেও cache-এ ছিল; তারপর আচরণ ঠান্ডা cache-এর মতো।

মডেলের সীমা: এটি মিনিটের গড়, প্রথম সেকেন্ডের ঝাঁকুনি নয়; আর ধরে নেয় একই roll-এর একসাথে আসা miss এক হয়ে যায়। perViewer ৪ করে দেখুন: মিনিট ২ থেকে hit ratio প্রায় ৮৮%, অথচ ডেটাবেসের চাপ আগের মতোই ১,২৮০, ১,১২০ … — বাড়তি অনুরোধগুলো refresh, যা cache-এই মেটে। তাই hit ratio-র পাশাপাশি ডেটাবেসের আসল query-সংখ্যাও মাপুন।

বড় trade-off — আগে থেকে বানানো static ফাইল, নাকি dynamic API?

আরেক পথ: প্রকাশের আগেই প্রতিটি ফলের একটি ছোট JSON বা HTML ফাইল বানিয়ে object storage-এ রাখা, যা CDN সরাসরি দেবে — static pre-generation. তখন প্রকাশের মুহূর্তে ডেটাবেস বা API প্রায় লাগেই না। তুলনা:

দিকStatic pre-generationDynamic (API + pre-warmed cache)
প্রকাশের মুহূর্তে origin-এর চাপপ্রায় শূন্য — CDN-ই দেয়cache-এর hit ratio-র ওপর নির্ভর
ফল সংশোধনফাইল আবার বানানো, তারপর CDN থেকে পুরোনো কপি মোছা (purge)ডেটাবেস ঠিক করে cache-এর একটি key মোছা
প্রকাশের সময় নিয়ন্ত্রণআগে রাখা ফাইল লুকিয়ে রাখা; পুরোনো “পাওয়া যায়নি” CDN-এ আটকে থাকার ঝুঁকিএকটি flag চালু
নতুন ফিচারকঠিনসহজ
খরচ ও জটিলতাকম — সার্ভার প্রায় লাগে নাবেশি — সার্ভার, cache, replica
আগের প্রস্তুতি২০ লাখ ফাইল বানানো ও আপলোডপ্রায় ৪ GB cache ভরা

আমাদের সিদ্ধান্ত: মূল পথ dynamic, কারণ সংশোধন আর প্রকাশের মুহূর্ত নিয়ন্ত্রণ সহজ; CDN ফলের উত্তর কয়েক মিনিট রাখবে, আর static ফাইলের পথ থাকবে জরুরি বিকল্প হিসেবে। উল্টো সিদ্ধান্তও যুক্তিসংগত — শুধু-পড়ার, আগে থেকে জানা ভিড়ে “সবচেয়ে সরল যা কাজ করে” প্রায়ই static ফাইলই। ইন্টারভিউতে দেখা হয়, দুটোর দাম আপনি জানেন কি না।

৭ · কী ভাঙতে পারে — failure mode, trade-off আর wrap-up

ছবি ৬.১-এর প্রতিটি বাক্সে একই প্রশ্ন: “এটি ভাঙলে কী হবে?” প্রকাশের দিনের প্রধান failure mode আর তাদের জবাব:

কী ভাঙলব্যবহারকারী কী দেখবেননকশায় প্রতিকার
ডেটাবেসে অতিরিক্ত চাপ (overload)পাতা ঘোরে, timeout, তারপর retry-র ঢেউআগে ভরা cache, coalescing, read replica, ডেটাবেসমুখী query-র সীমা, আর বাড়তি অনুরোধ নম্রভাবে ফেরানো — load shedding (পর্ব ১৮)
Cache খালি (cold start বা restart)হঠাৎ সব অনুরোধ ডেটাবেসেkey কয়েকটি node-এ ভাগ আর replica; snapshot থেকে দ্রুত ভরা; প্রকাশের দিন restart বা deploy নয়
DNS-এর ভুল বা দেরিতে ছড়ানো পরিবর্তনকেউ পুরোনো ঠিকানায়, কেউ নতুনেপ্রকাশের দিন DNS না বদলানো; বদলাতে হলে আগেই TTL কমানো; একাধিক nameserver
CDN-এর বিভ্রাট বা ভুল configপাতা খোলে না, বা পুরোনো “পাওয়া যায়নি” দেখায়আগে load test; “পাওয়া যায়নি” উত্তর জমা না রাখা (negative caching বন্ধ বা খুব ছোট); দরকারে দ্বিতীয় CDN
Rate limiter বেশি কড়াএকই IP-র পেছনের বহু মোবাইল ব্যবহারকারী আটকে যানশুধু IP নয়, roll বা টোকেনভিত্তিক সীমাও; সীমা মেপে ঠিক করা (পর্ব ৪৯)
প্রকাশের ঠিক আগে নতুন সংস্করণ চালুঅপ্রত্যাশিত বাগ, ফেরানোর সময় নেইআগের দিন থেকে কোনো পরিবর্তন নয় — change freeze

অনেক মোবাইল নেটওয়ার্কে বহু গ্রাহক একটি public IP ঠিকানা ভাগ করেন — একে বলে CGNAT. তাই “এক IP থেকে সেকেন্ডে ১০টির বেশি অনুরোধ নয়” ধরনের কড়া নিয়ম শত শত নিরীহ মানুষকে একসাথে আটকে দিতে পারে।

এবার সবচেয়ে ভয়ের পরিস্থিতি: আগে ভরা cache ঠিক ভিড়ের মধ্যে, মিনিট ৩-এ, হঠাৎ খালি। সংখ্যাগুলো অংশ ৬-এর simulator থেকে।

  1. APIstateless
  2. Cache clusterতিনটি node
  3. Databaseসীমা ১,০০০/সে.
ঘটনাক্রম
সময়
Cache hit
DB-র অবস্থা
সারিতে জমা
  1. মিনিট ১–২: সব ঠিকঠাক। Cache আগে থেকে ভরা, তাই অনুরোধ সেকেন্ডে ১,৯২০ থেকে ৩,৬০০-তে উঠলেও ডেটাবেসে প্রায় কিছুই যায় না; hit ratio প্রায় শতভাগ।
  2. মিনিট ৩-এর শুরু: cache cluster restart. ধরা যাক একটি ভুল config তিনটি cache node-এ একসাথে প্রয়োগ হলো, আর তিনটিই restart নিল। Cache ফিরল কয়েক সেকেন্ডে — কিন্তু একেবারে খালি। এখানেই alert বাজার কথা: hit ratio হঠাৎ শূন্যের দিকে।
  3. মিনিট ৩: ডেটাবেসে ঢেউ। প্রতি সেকেন্ডে ৩,১২০ অনুরোধ। নতুন ৯৬০টি roll miss করবেই; সাথে মিনিট ২-এর পরিবারের ১,১২০টি roll-ও, যেগুলো এক মিনিট আগেও cache-এ ছিল। চাহিদা সেকেন্ডে ২,০৮০, সীমার দ্বিগুণেরও বেশি; প্রতি সেকেন্ডে ১,০৮০টি query সারিতে জমছে — ১০ সেকেন্ডে ১০,৮০০।
  4. প্রথম প্রতিক্রিয়া: ডেটাবেস বাঁচানো। API সার্ভারগুলো ডেটাবেসের দিকে একসাথে চলা query সীমিত রাখে; বাড়তি অনুরোধ সঙ্গে সঙ্গে পায় 503 আর Retry-After: 30, পাতায় শান্ত বার্তা: “এক মিনিট পর আবার চেষ্টা করুন।” এটাই load shedding. কেউ কেউ দেরিতে পান, কিন্তু ডেটাবেস ভাঙে না — ভাঙলে কেউই পেতেন না।
  5. মিনিট ৪–৫: নিজে থেকে সেরে ওঠা। প্রতিটি roll একবার miss করে আবার cache-এ ঢুকছে; চাহিদা নামে সেকেন্ডে ৮৮০, তারপর ৮০০-তে — সীমার নিচে, তবে ৮০ থেকে ৯০ শতাংশ ব্যবহারে latency তখনো বেশি। আগে ভরা ডেটা হারানোর দাম: মিনিট ৩ থেকে ডেটাবেস আর অলস নেই।
  6. নকশার শিক্ষা। Key তিনটি node-এ ভাগ থাকলে আর একসাথে একটির বেশি restart না হলে হারায় বড়জোর এক-তৃতীয়াংশ: ২,০৮০ ÷ ৩ ≈ ৬৯৩ — সীমার নিচে। প্রতিটি key দুটি node-এ রাখলে প্রায় কিছুই হারায় না। খালি cache ডেটাবেস থেকে নয়, snapshot বা আগে বানানো ফাইল থেকে ভরুন; আর প্রকাশের দিন change freeze — কোনো config বদল বা deploy নয়।

ধাপ ৮ — Wrap-up: কী মাপবেন, পরে কী বদলাবেন

প্রকাশের দিন নিচের সংখ্যাগুলো একটি dashboard-এ পাশাপাশি থাকবে। সতর্কসংকেতের মানগুলো উদাহরণ; আসল মান ঠিক হবে নিজের load test দেখে।

কী মাপবেনকেনসতর্কসংকেত (উদাহরণ)
প্রতি সেকেন্ডে অনুরোধভিড় হিসাবের সাথে মিলছে কি নাহিসাবের peak-এর ৮০% পেরোলে
Cache hit ratioডেটাবেসের ঢাল কতটা কাজ করছে৯৫%-এর নিচে নামলে
p99 latencyসবচেয়ে ধীর ১% মানুষের অভিজ্ঞতা১ সেকেন্ড পেরোলে
ডেটাবেসের query আর connectionডেটাবেস সীমার কত কাছেনিরাপদ সীমার ৭০% পেরোলে
5xx আর 429-এর হারকতজন খালি হাতে ফিরছেন১%-এর বেশি হলে

বৃদ্ধি আর পরের কাজ: পরীক্ষার্থী দ্বিগুণ হলে peak-ও প্রায় দ্বিগুণ — API stateless আর ডেটা ছোট, তাই সার্ভার আর cache বাড়ানো সহজ। পরের কাজ: এক সপ্তাহ আগে peak-এর দ্বিগুণ চাপে load test, তার মধ্যেই একটি cache node বন্ধ করে দেখা, আর একটি অপেক্ষা-কক্ষের পাতা। সারাংশ: “জানা ভিড়, ছোট ডেটা — তাই CDN, আগে ভরা cache আর আড়ালে রাখা ডেটাবেস।”

৮ · ইন্টারভিউর ৪৫ মিনিট আর বাস্তবের design doc

সময় ভাগ — একটি সাধারণ ছক

প্রায় ৪৫ মিনিটের ইন্টারভিউয়ে সময় মোটামুটি এভাবে ভাগ করা যায়। এটি ছক, নিয়ম নয় — প্রতিষ্ঠান, পদ আর ইন্টারভিউয়ারভেদে বদলায়; কেউ হিসাব চান না, কেউ শুরু থেকেই একটি অংশের গভীরে নেন। ঘড়ি আর ইন্টারভিউয়ারের ইঙ্গিত, দুটোই খেয়াল রাখুন। পূর্ণ case study আসবে পর্ব ৫৪–৬১-এ, আর ঘড়ি ধরে mock interview পর্ব ৬৬-এ।

ধাপমোটামুটি সময়যা দেখাবেন
১. পরিধি ও requirement~৫ মিনিটভালো প্রশ্ন, স্পষ্ট পরিধি
২. হিসাব~৪ মিনিটমাত্রা, মুখে বলা অনুমান
৩–৪. API ও data model~৬ মিনিটমূল endpoint, key, access pattern
৫. High-level design~১০ মিনিটপুরো ছবি, একটি অনুরোধের যাত্রা
৬. Deep dive~১৩ মিনিটbottleneck-এর সমাধান, সংখ্যাসহ
৭. Failure ও trade-off~৪ মিনিটভাঙন আর তার জবাব
৮. Wrap-up~৩ মিনিটনজরদারি, বৃদ্ধি, সারাংশ

অফিসে একই আট ধাপ — design doc, RFC আর ADR

বাস্তব কাজে এই আট ধাপ লেখা হয় একটি design doc-এ — প্রেক্ষাপট, লক্ষ্য আর যা লক্ষ্য নয় (non-goals), প্রস্তাবিত নকশা, বিকল্প, ঝুঁকি আর monitoring. লেখাটি সহকর্মীদের কাছে যায়; অনেক দল একে বলে RFC-ধাঁচের পর্যালোচনা: মন্তব্য, আপত্তি, সংশোধন, তারপর সিদ্ধান্ত। আর প্রতিটি বড় সিদ্ধান্ত লেখা থাকে একটি ADR-এ (পর্ব ০২): প্রেক্ষাপট, সিদ্ধান্ত, পরিণতি।

১৯৬৯ সালের এপ্রিলে ARPANET প্রকল্পের তরুণ গবেষক Steve Crocker নেটওয়ার্ক-সফটওয়্যারের প্রাথমিক ভাবনা লিখে সহকর্মীদের মধ্যে বিলি করেন, শিরোনাম দেন “Request for Comments” — এটিই RFC 1. বিনয়ী নামটি ইচ্ছাকৃত: আদেশ নয়, আলোচনার আমন্ত্রণ। আজও ইন্টারনেটের মান এই নামেই প্রকাশ হয়, যেমন HTTP-র অর্থ বর্ণনা করা RFC 9110; আধুনিক design review-এর শিকড়ও এই সংস্কৃতিতে।

কথা বলার কৌশল

  • মুখে ভাবুন। চুপচাপ আঁকলে ইন্টারভিউয়ার আপনার যুক্তি দেখতে পান না; মুখে বললে ভুল পথেও তিনি শুধরে দিতে পারেন।
  • অনুমান স্পষ্ট করুন। “ধরে নিচ্ছি ফল প্রকাশের পর বদলায় না” — এক বাক্যেই পুরো cache-নীতির ভিত্তি।
  • গভীরে নামার আগে পুরো ছবি। আগে সব বাক্স, তারপর একটির ভেতর।
  • মতামত চান। “এখানে দ্রুততার চেয়ে নির্ভুলতাকে প্রাধান্য দিচ্ছি — আপনি কি অন্যভাবে দেখছেন?” এটা সহযোগিতার চিহ্ন, দুর্বলতা নয়।
  • মাঝে মাঝে সারাংশ। প্রতি ধাপ শেষে এক বাক্যে কী ঠিক হলো।

সাধারণ ভুল

ভুলকেন ক্ষতিকরবদলে কী করবেন
Requirement-এর আগেই প্রযুক্তির নাম — “Kafka দিয়ে শুরু করি”টুল সমস্যা খুঁজে বেড়ায়; কেন লাগবে, বলা যায় নাআগে প্রশ্ন আর সংখ্যা, তারপর টুল
কোনো সংখ্যা নেইএকটি সার্ভার লাগবে না একশো — বোঝা যায় নামোটা দাগের হিসাব, অনুমান মুখে বলে
শুধু সুবিধা, trade-off নেইমনে হয় নকশার দাম জানা নেইপ্রতিটি সিদ্ধান্তে “কী পেলাম, কী দিলাম”
নেই এমন scale-এর জন্য নকশাখরচ, জটিলতা আর ভুলের সুযোগ বাড়ে — over-engineeringআজকের সংখ্যা ধরে নকশা, বৃদ্ধির পথ খোলা রেখে
ব্যর্থতার কথা না ভাবাপ্রথম বিভ্রাটেই নকশা ধসে পড়েপ্রতিটি বাক্সে “এটি বন্ধ হলে?”

আরও গভীরে: Google-এর প্রকাশিত Site Reliability Engineering বইয়ে (২০১৬) বড় launch সামলানো নিয়ে একটি অধ্যায় আছে; সেখানে বর্ণিত Launch Coordination Engineering দল নতুন সেবা চালুর আগে চেকলিস্ট ধরে capacity, failure mode, ফেরানোর পরিকল্পনা আর monitoring যাচাই করে। টিকিট বিক্রির মতো জানা ভিড়ে অনেক সাইট virtual waiting room ব্যবহার করে — ব্যবহারকারীকে সারির পাতায় রেখে ক্ষমতা অনুযায়ী ভেতরে ঢুকতে দেয়। আর অনেক দল ইচ্ছা করে একটি cache node বা replica বন্ধ করে মহড়া দেয় (game day), নকশার দাবি বাস্তবে মেলে কি না দেখতে।

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • System design ইন্টারভিউয়ে কোন ক্রমে এগোবেন? — পরিধি ও requirement, হিসাব, API, data model, high-level design, deep dive, failure ও trade-off, wrap-up; দরকারে পেছনে ফেরা।
  • Read-heavy সিস্টেমে ডেটাবেস বাঁচাবেন কীভাবে? — CDN, আগে ভরা cache, read replica, আর আগে থেকে বানানো উত্তর।
  • Thundering herd বা cache stampede কী, ঠেকাবেন কীভাবে? — একসাথে বহু miss উৎসে আছড়ে পড়া; coalescing, pre-warming, মেয়াদে এলোমেলো সময় (jitter), মেয়াদের আগেই নবায়ন।
  • ফল প্রকাশ বা টিকিট বিক্রির মতো জানা spike-এর প্রস্তুতি? — হিসাব, load test, CDN, static বা আগে ভরা উত্তর, rate limit, waiting room, change freeze.

৯ · AI-এর যুগে system design — খসড়া AI-র, যাচাই আপনার

AI-এর কাছে ফল প্রকাশের সাইটের নকশা চাইলে কয়েক সেকেন্ডে সুন্দর খসড়া আসে — বাক্স, তীর, প্রযুক্তির নাম। কিন্তু কিছু চেনা ভুল বারবার দেখা যায়, আর সেগুলো ধরার দায়িত্ব আপনার:

AI-এর ভুলএই উদাহরণে কেমন দেখায়কীভাবে যাচাই করবেন
সব প্রশ্নে একই উত্তর — “Kafka, Redis আর microservices যোগ করুন”প্রায় লেখাহীন সাইটে message queueপ্রতিটি বাক্সের পেছনের requirement বা সংখ্যা জিজ্ঞেস করুন
হিসাবে গাণিতিক ভুলKB আর Kbit গুলিয়ে আট গুণ ভুল, বা গড়কে peak বলানিজে বা স্ক্রিপ্টে আবার হিসাব, একক মিলিয়ে
Failure আর consistency-র trade-off বাদcache খালি হলে কী হবে, তার কোনো কথা নেইপ্রতিটি অংশ বন্ধ ধরে failure-mode review
ACID-এর consistency আর CAP-এর consistency গুলিয়ে ফেলা“read replica দিলে ACID ভেঙে যাবে”ACID-এর C মানে নিয়ম ও constraint রক্ষা; CAP-এর C মানে সব কপি থেকে সর্বশেষ মান (পর্ব ২৪, ৪০)
পণ্যের সীমা বা দাম বানিয়ে বলাpurge-এর গতি বা cache-এর সীমা নিয়ে আত্মবিশ্বাসী কিন্তু উৎসহীন সংখ্যাঅফিশিয়াল ডকুমেন্টেশন, তারিখসহ
Over-engineering২ GB ডেটার জন্য sharding আর তিন অঞ্চলে কপিআজকের সংখ্যার সাথে মিলিয়ে সরলতম নকশা খুঁজুন

AI-কে স্থপতি নয়, কড়া reviewer বানান। নিজের নকশা আগে দাঁড় করান, তারপর এভাবে যাচাই করান:

তুমি একজন অভিজ্ঞ backend প্রকৌশলী আর কড়া design reviewer. একটি পাবলিক পরীক্ষার ফল প্রকাশের সাইটের নকশা আমি আট ধাপে করেছি (পরিধি, হিসাব, API, data model, high-level design, deep dive, failure, wrap-up): [এখানে আপনার নকশা পেস্ট করুন] ১. প্রতিটি বাক্সের জন্য বলো কোন requirement বা সংখ্যা এটিকে দরকারি করে; কারণ না থাকলে বাদ দেওয়ার কথা বলো। ২. আমার হিসাব (peak RPS, bandwidth, storage) ধাপে ধাপে আবার করো, একক মিলিয়ে; ভুল পেলে দেখাও। ৩. অন্তত পাঁচটি failure mode (cache restart, ডেটাবেস overload, DNS বা CDN সমস্যা ইত্যাদি) আর প্রতিটির প্রতিকার দাও। ৪. কোথাও consistency বললে বলো সেটা ACID অর্থে না CAP অর্থে। ৫. কোনো পণ্যের সীমা বা দাম বললে উৎস দাও; নিশ্চিত না হলে স্পষ্ট লেখো। নতুন প্রযুক্তি যোগের আগে সরলতর বিকল্পটি বলো।

AI-এর নকশা গ্রহণের আগে — চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১আটটি ধাপ কি সত্যিই একবারে, একটার পর একটা হয়? ‘Waterfall’-এর ইতিহাস এখানে কী শেখায়?

না — আর এই ভুল বোঝাবুঝির একটি মজার ইতিহাস আছে। সফটওয়্যার প্রকৌশলে “waterfall” মডেলের উৎস হিসেবে প্রায়ই উদ্ধৃত হয় Winston Royce-এর ১৯৭০ সালের একটি লেখা, যেখানে requirement থেকে বিশ্লেষণ, নকশা, কোড, পরীক্ষা আর পরিচালনা পর্যন্ত ধাপগুলো সিঁড়ির মতো আঁকা। অথচ সেই একই লেখায় Royce সতর্ক করেছিলেন, এভাবে সোজা নিচে নামা ঝুঁকিপূর্ণ আর ব্যর্থতাকে ডেকে আনে। তিনি পরামর্শ দেন ধাপগুলোর মধ্যে পেছনে ফেরার পথ রাখতে, এমনকি আসল সিস্টেমের আগে একটি ছোট পরীক্ষামূলক সংস্করণ বানাতে — তাঁর ভাষায় “do it twice”. পাঁচ বছর পর Fred Brooks তাঁর The Mythical Man-Month (১৯৭৫) বইয়ে কথাটি আরও তীক্ষ্ণভাবে বলেন: একটি সংস্করণ ফেলে দেওয়ার পরিকল্পনা রাখুন, কারণ ফেলবেনই।

System design-এর আট ধাপও তাই একটি চাকা। Deep dive-এ গিয়ে হয়তো বুঝলেন, “প্রকাশের আগে কেউ দেখবে না” শর্তটি API-র নকশা বদলে দেয় — ফিরতে হলো ধাপ ৩-এ। হিসাব করে দেখলেন ডেটা মাত্র ২ GB — ধাপ ৪ থেকে sharding-এর পরিকল্পনা বাদ। ইন্টারভিউয়ার নতুন শর্ত দিলেন, “এবার পুনঃনিরীক্ষার আবেদনও ধরুন” — ফিরলেন ধাপ ১-এ।

তাহলে ক্রমের দাম কী? দুটি। প্রথমত, শুরু করার একটি নিরাপদ জায়গা: requirement না জেনে ছবি আঁকলে পরে সব মুছতে হয়। দ্বিতীয়ত, একটি চেকলিস্ট: সময়ের চাপে failure mode বা monitoring-এর কথা ভুলে যাওয়া খুব সহজ। বাস্তবের design doc-ও একাধিক খসড়ায় বদলায়, আর প্রতিটি বড় পরিবর্তন ADR-এ লেখা থাকে, যাতে পরে বোঝা যায় কেন বদলানো হয়েছিল।

মূল উপলব্ধি: ধাপের ক্রম শুরু করার জন্য, আটকে রাখার জন্য নয়। নতুন তথ্য পেয়ে পেছনে ফেরা দুর্বলতা নয় — সেটাই নকশা।

প্র ০২‘Thundering herd’ নামটা কোথা থেকে এলো, আর বড় সিস্টেম এটা কীভাবে সামলায়?

নামটি এসেছে অপারেটিং সিস্টেম থেকে। ধরুন একটি সার্ভারের অনেকগুলো process বা thread একই ঘটনার অপেক্ষায় ঘুমিয়ে আছে — যেমন একটি নতুন network connection. Connection এলে kernel সবাইকে জাগিয়ে দিল, অথচ কাজটি নিতে পারবে মাত্র একজন; বাকিরা জেগে, প্রতিযোগিতায় হেরে, আবার ঘুমাতে গেল। এই অকারণ দৌড়ঝাঁপে CPU নষ্ট হয় — বুনো পশুর পাল হঠাৎ একসাথে দৌড়ানোর ছবি থেকেই নাম। Linux ধীরে ধীরে এর প্রতিকার যোগ করেছে; যেমন epoll-এর EPOLLEXCLUSIVE flag (kernel ৪.৫, ২০১৬) একটি ঘটনায় অপেক্ষমাণদের মধ্যে একজনকে বা অল্প কয়েকজনকে জাগায়।

Cache-এর জগতে একই ছবি: একটি জনপ্রিয় key-এর মেয়াদ শেষ হলো বা cache খালি হলো, আর হাজারো অনুরোধ একসাথে miss করে ডেটাবেসে। Facebook-এর প্রকৌশলীদের “Scaling Memcache at Facebook” (NSDI ২০১৩) গবেষণাপত্রে এর একটি সমাধানের নাম lease: miss হলে cache একজন ক্লায়েন্টকে একটি টোকেন দেয় — ডেটাবেস থেকে মান এনে বসানোর অধিকার; একই key-এর জন্য কিছুক্ষণ আর নতুন টোকেন দেয় না, বাকিদের একটু অপেক্ষা করে আবার দেখতে বলে। এটি আসলে request coalescing-এরই একটি রূপ। আরেক কৌশল হলো মেয়াদ শেষ হওয়ার আগেই, সম্ভাবনার নিয়মে, কোনো একটি অনুরোধকে দিয়ে মান নবায়ন করানো — Vattani ও সহকর্মীদের VLDB (২০১৫) গবেষণাপত্রে এর একটি গাণিতিকভাবে উন্নত রূপ আছে, যা XFetch নামে পরিচিত। সবচেয়ে সরল কৌশল: সব key-এর মেয়াদ একই মুহূর্তে না রেখে একটু এলোমেলো রাখা।

আমাদের ফল প্রকাশের সাইটে খবরটা আসলে ভালো: ভিড়ের মুহূর্ত আগে থেকেই জানা। অজানা মুহূর্তে মেয়াদ ফুরানোর চেয়ে জানা মুহূর্তের জন্য প্রস্তুতি অনেক সহজ — তাই pre-warming এখানে সবচেয়ে শক্তিশালী অস্ত্র, আর coalescing তার পেছনের নিরাপত্তা-জাল।

মূল উপলব্ধি: জানা ভিড়ের উত্তর প্রস্তুতি; অজানা ভিড়ের উত্তর সমন্বয় (coalescing, lease) আর এলোমেলো মেয়াদ। দুটো একসাথে রাখলে একটি ব্যর্থ হলে অন্যটি ধরে।

প্র ০৩সাইট ধীর হলে ভিড় আরও বাড়ে কেন? Retry storm আর ‘metastable failure’ কী?

আমাদের হিসাবেই উত্তর লুকিয়ে আছে: একই মানুষ, একই সময় — সাইট দ্রুত থাকলে peak প্রায় ৩,৬০০ অনুরোধ/সেকেন্ড, ধীর হলে ৯,৬০০. মানুষ বাড়েনি; একেকজন বেশিবার চেষ্টা করছেন। মোবাইল অ্যাপ আর ব্রাউজারের কোডও প্রায়ই নিজে থেকে আবার চেষ্টা করে। ফলে একটি দুষ্টচক্র তৈরি হয়: ধীরগতি থেকে retry, retry থেকে বেশি চাপ, বেশি চাপ থেকে আরও ধীরগতি। একেই বলে retry storm.

২০২১ সালে Nathan Bronson ও সহকর্মীরা HotOS কর্মশালায় “Metastable Failures in Distributed Systems” নামের গবেষণাপত্রে এই আচরণের নাম দেন metastable failure. তাঁদের মূল পর্যবেক্ষণ: একটি ছোট ধাক্কা (trigger) — কয়েক সেকেন্ডের network সমস্যা বা একটি cache restart — সিস্টেমকে খারাপ অবস্থায় ঠেলে দেয়, আর ধাক্কাটি সরে গেলেও সিস্টেম সেরে ওঠে না। কারণ retry-র মতো কোনো টিকিয়ে রাখা প্রভাব (sustaining effect) নিজেই চাপ ধরে রাখে। তখন উদ্ধারের উপায় সাধারণত চাপ ইচ্ছা করে কমানো: load shedding, বা retry বন্ধ করা।

প্রতিরক্ষা তাই নকশাতেই রাখতে হয়। ক্লায়েন্টে exponential backoff আর jitter — প্রতিবার অপেক্ষা দ্বিগুণ, সাথে একটু এলোমেলো সময়, যাতে সবাই একসাথে ফিরে না আসে। AWS-এর Architecture Blog-এ Marc Brooker-এর ২০১৫ সালের একটি লেখা simulation দিয়ে jitter-এর প্রভাব দেখিয়ে কৌশলটিকে জনপ্রিয় করে। সার্ভারে timeout, retry-র বাজেট, circuit breaker (পর্ব ৪৮) আর load shedding (পর্ব ১৮)। Google-এর SRE বইয়ে (২০১৬) cascading failure নিয়ে পুরো একটি অধ্যায়ই আছে। ফল প্রকাশের পাতায় এর সরল রূপ: ব্যর্থ হলে পাতা প্রতি সেকেন্ডে নিজে থেকে চেষ্টা করবে না; বরং গণনা করে দেখাবে “৩০ সেকেন্ড পর আবার চেষ্টা”, সাথে একটি বোতাম।

মূল উপলব্ধি: ব্যবহারকারীর অধৈর্যও load-এর অংশ। ধীরগতি শুধু লক্ষণ নয়, নতুন ভিড়ের কারণও — তাই retry-কে নকশার ভেতরে নিয়ন্ত্রণ করুন।

প্র ০৪কোড তো নিজেই সব বলে — তাহলে design doc, RFC আর ADR লেখার এত গুরুত্ব কেন?

কোড বলে সিস্টেম কী করে; বলে না কেন এভাবে করা হলো, আর কোন কোন বিকল্প ভেবে বাদ দেওয়া হয়েছিল। দুই বছর পর নতুন একজন প্রকৌশলী দেখবেন ফলের সাইটে কোনো sharding নেই, আর ভাববেন “এটা তো ভুল!” — যদি না কোথাও লেখা থাকে: “ডেটা ২ GB; sharding-এর জটিলতা অপ্রয়োজনীয়; ডেটা ৫০ GB ছাড়ালে আবার ভাবা হবে।”

লিখে আলোচনার সংস্কৃতির একটি সুন্দর উৎস আছে। ১৯৬৯ সালের এপ্রিলে Steve Crocker-এর লেখা RFC 1 ছিল আদেশ নয়, মতামতের আমন্ত্রণ; সেই ধারায় আজও ইন্টারনেটের মান প্রকাশ হয়। Python ভাষার বড় পরিবর্তনগুলো ২০০০ সাল থেকে PEP (Python Enhancement Proposal) নামের লিখিত প্রস্তাব আর প্রকাশ্য আলোচনার মধ্য দিয়ে যায়। আর ২০১১ সালে Michael Nygard-এর একটি ব্লগ লেখা থেকে ADR জনপ্রিয় হয়: প্রতিটি বড় সিদ্ধান্তের জন্য এক-দুই পাতার একটি নম্বরযুক্ত নথি — প্রেক্ষাপট, সিদ্ধান্ত, পরিণতি। সিদ্ধান্ত বদলালে পুরোনোটি মোছা হয় না; নতুন ADR লিখে পুরোনোটিকে “বাতিল” চিহ্নিত করা হয়, যাতে ইতিহাস থাকে।

লেখার আরেকটি লুকানো লাভ: লিখতে গেলেই ফাঁক ধরা পড়ে। মাথায় যে নকশা নিখুঁত মনে হয়, কাগজে “cache খালি হলে কী হবে?” অংশটি লিখতে গিয়ে আটকে যায়। ভিন্ন শহরে বা ভিন্ন সময়ে কাজ করা সহকর্মীরাও নিজের সময়মতো পড়ে মত দিতে পারেন। ঝুঁকিও আছে — নথি হালনাগাদ না থাকলে বিভ্রান্তি ছড়ায়, আর অতিরিক্ত আনুষ্ঠানিকতা কাজের গতি কমায়। তাই নথি ছোট রাখা আর সিদ্ধান্তের সাথে সাথেই লেখা ভালো অভ্যাস।

মূল উপলব্ধি: ইন্টারভিউর আট ধাপ আসলে মুখে বলা একটি design doc. যিনি পরিষ্কার লিখতে পারেন, তিনি সাধারণত পরিষ্কার ভাবতেও পারেন।

প্র ০৫কত বড় scale-এর জন্য নকশা করবেন? Over-engineering আর “সবচেয়ে সরল যা কাজ করে”-র মধ্যে সীমা কোথায়?

দুই দিকেই খাদ। একদিকে over-engineering: প্রথম দিন থেকেই দশটি microservice, তিনটি ডেটাবেস আর একটি message queue — অথচ প্রতি সেকেন্ডে অনুরোধ আসে ২০টি। প্রতিটি বাড়তি অংশ নতুন খরচ, বিভ্রাটের নতুন পথ আর শেখার নতুন বোঝা। অন্যদিকে under-engineering: জানা ভিড়ের দিনেও একটি সার্ভার আর “দেখা যাবে” মনোভাব। দুটোরই দাম আছে: প্রথমটিতে টাকা আর সময় যায় এমন সমস্যার পেছনে, যা কখনো আসেইনি; দ্বিতীয়টিতে সাইট বসে যায় ঠিক সেই দিনে, যেদিন সবচেয়ে বেশি মানুষের দরকার।

এ নিয়ে কম্পিউটার বিজ্ঞানের সবচেয়ে বিখ্যাত বাক্যটি Donald Knuth-এর, ১৯৭৪ সালের “Structured Programming with go to Statements” লেখা থেকে: অকালে optimization সব অনিষ্টের মূল। কিন্তু উদ্ধৃতির বাকি অংশ প্রায়ই বাদ পড়ে — Knuth বলেছিলেন প্রায় ৯৭% সময় ছোটখাটো দক্ষতার কথা ভুলে থাকা উচিত, তবে সেই গুরুত্বপূর্ণ ৩%-এর সুযোগ হাতছাড়া করা উচিত নয়। Extreme Programming আন্দোলনেও দুটি পরিচিত বাণী আছে: “যা কাজ করতে পারে এমন সবচেয়ে সরল জিনিসটি করুন” (Ward Cunningham আর Kent Beck-এর নামে প্রচলিত), আর YAGNI — “You aren’t gonna need it”.

আমাদের উদাহরণে এর অর্থ পরিষ্কার। ডেটা ২ GB — sharding, Kafka বা আলাদা search cluster-এর দরকার নেই; একটি relational ডেটাবেস আর কয়েকটি read replica যথেষ্ট। কিন্তু প্রকাশের মুহূর্তের ভিড় সেই “৩%” — বাস্তব, জানা আর ব্যয়বহুল; সেখানে CDN, pre-warming, load test আর ব্যর্থতার মহড়ায় বিনিয়োগ পুরোপুরি যুক্তিসংগত। একটি প্রচলিত মোটা দাগের নিয়ম: আজকের সংখ্যার প্রায় দশ গুণের জন্য নকশা করুন, আর একশো গুণে পৌঁছানোর আগে নতুন নকশার পথ খোলা রাখুন। সংখ্যা বদলালে সিদ্ধান্তও বদলাবে — সেজন্যই ADR-এ কারণের সাথে সীমাটিও লিখে রাখা দরকার।

মূল উপলব্ধি: নকশা হবে সংখ্যার জন্য, কল্পনার scale-এর জন্য নয়। জটিলতা খরচ করুন শুধু সেখানে, যেখানে সংখ্যা তা দাবি করে।

অনুশীলন

আগে নিজে খাতায় লিখুন, তারপর উত্তরের সাথে মেলান। নকশার প্রশ্নে একটিমাত্র সঠিক উত্তর নেই — যুক্তিটাই আসল।

  1. কাঠামো প্রয়োগ — লাইভ ভোটের অ্যাপ: একটি টিভি কুইজ অনুষ্ঠানে দর্শকরা অ্যাপে লাইভ ভোট দেন; ভোটের জানালা ২ মিনিট, আর পর্দায় প্রায় সঙ্গে সঙ্গে ফল। আট ধাপে সংক্ষিপ্ত নকশা লিখুন; সংখ্যা নিজে ধরে নিন।

    একটি নমুনা উত্তর (অনুমানসহ):

    • ১. পরিধি: FR — poll খোলা-বন্ধ, একজনের একটিই ভোট, প্রায় লাইভ ফল। NFR — ২ মিনিটের ভিড়, কোনো ভোট হারাবে না বা দুবার গোনা হবে না, ফল কয়েক সেকেন্ড পিছিয়ে থাকলে চলে। বাইরে — প্রতারণা বিশ্লেষণ, পুরস্কার।
    • ২. হিসাব: ধরা যাক ৫ লাখ দর্শক ২ মিনিটে ভোট দেন: ৫,০০,০০০ ÷ ১২০ ≈ ৪,১৬৭ লেখা/সেকেন্ড; শুরুর দিকে দ্বিগুণ ধরলে peak ≈ ৮,৩৩৩. ভোটপ্রতি প্রায় ৫০ বাইটে একটি poll প্রায় ২৫ MB.
    • ৩. API: POST /v1/polls/{id}/votes (বিকল্পের id আর ব্যবহারকারীর টোকেন; দ্বিতীয় ভোটে 409) আর GET /v1/polls/{id}/results, যা এক-দুই সেকেন্ড cache করা যায়।
    • ৪. Data model: votes(poll_id, user_id, option_id, ts), key (poll_id, user_id) — এটাই দ্বিগুণ ভোট ঠেকায়; গণনা আলাদা counter-এ।
    • ৫. High-level design: load balancer → stateless ভোট API → টেকসই log বা queue → counter-এর worker; ফলের পাতা CDN-এর পেছনে।
    • ৬. Deep dive: একটি poll-এর সব লেখা একই counter-এ — hot key. সমাধান: প্রতিটি বিকল্পে ১৬টি ছোট counter, পড়ার সময় যোগফল।
    • ৭. Failure ও trade-off: counter হারালে log থেকে আবার গোনা; queue পিছিয়ে পড়লে ফল দেরিতে, কিন্তু ভোট নিরাপদ। নির্ভুল লাইভ গণনার বদলে কয়েক সেকেন্ড পিছিয়ে থাকা গণনা — এখানে সেটাই যথেষ্ট।
    • ৮. Wrap-up: লেখার হার, queue-এর পিছিয়ে থাকা আর প্রত্যাখ্যাত দ্বিতীয় ভোট মাপা; পরের কাজ বট ঠেকানো আর load test.
  2. ত্রুটিপূর্ণ নকশার সমালোচনা: একজন সহকর্মী ফল প্রকাশের সাইটের জন্য এই নকশা দিলেন — (ক) একটি শক্তিশালী সার্ভারে অ্যাপ আর ডেটাবেস একসাথে; (খ) প্রতিটি অনুরোধে ডেটাবেস থেকে পড়ে নতুন করে HTML বানানো; (গ) “তাজা ডেটার জন্য” প্রকাশের ৫ মিনিট আগে cache পুরো খালি করা; (ঘ) প্রকাশের দিন সকালে নতুন ডিজাইনের পাতা চালু; (ঙ) পাতায় ২ MB-র একটি ব্যানার ছবি; (চ) ব্যর্থ হলে পাতা নিজে থেকে প্রতি ১ সেকেন্ডে আবার চেষ্টা করে। সমস্যাগুলো কী, আর কীভাবে ঠিক করবেন?
    • (ক) একটি সার্ভার মানে single point of failure, আর অ্যাপ ও ডেটাবেস একই CPU-মেমরির জন্য লড়ে। ঠিক করা: load balancer-এর পেছনে কয়েকটি stateless অ্যাপ সার্ভার, আলাদা ডেটাবেস আর read replica.
    • (খ) যে ফল বদলায় না, তা প্রতিবার বানানো অপচয়। ঠিক করা: আগে ভরা cache, CDN, বা আগে বানানো static ফাইল।
    • (গ) ভিড়ের মুখে নিজেই cold start আর cache stampede ডেকে আনা। ঠিক করা: আগেই ভরা, প্রকাশের মুহূর্তে শুধু flag চালু।
    • (ঘ) ফেরানোর সময় ছাড়াই নতুন বাগের ঝুঁকি। ঠিক করা: আগের দিন থেকে change freeze.
    • (ঙ) হিসাবটি দেখুন: peak-এ প্রতি সেকেন্ডে প্রায় ২,৪০০ জন নতুন দর্শক (অংশ ৪-এর অনুমানে) × ২ MB = ৪,৮০০ MB/সেকেন্ড ≈ ৩৮.৪ Gbit/s — origin থেকে অসম্ভব। ঠিক করা: ছবি বাদ বা খুব ছোট, আর স্থির ফাইল CDN থেকে।
    • (চ) প্রতি সেকেন্ডে স্বয়ংক্রিয় retry মানে নিজেই retry storm. ঠিক করা: exponential backoff আর jitter, সাথে “৩০ সেকেন্ড পর আবার চেষ্টা” বোতাম।

    আর নকশায় যা নেই — rate limiter, monitoring, load test আর failure-এর পরিকল্পনা — চারটিই যোগ করতে হবে।

  3. Spike-এর হিসাব: একটি বিশ্ববিদ্যালয়ের ভর্তি পরীক্ষার ফল (কাল্পনিক) রাত ৮টায় প্রকাশ হবে। ধরা যাক আবেদনকারী ৩ লাখ; তাঁদের ৭০% প্রথম ৩০ মিনিটে ফল দেখেন; প্রতিজন গড়ে ৩টি অনুরোধ পাঠান; ৩০ মিনিটের অনুরোধের ৪০% আসে প্রথম ৫ মিনিটে; প্রতি উত্তর ৫ KB. বের করুন — (ক) ৩০ মিনিটের গড় অনুরোধ/সেকেন্ড, (খ) প্রথম ৫ মিনিটের গড় অনুরোধ/সেকেন্ড, (গ) প্রথম ৫ মিনিটের bandwidth (Mbit/s), (ঘ) সাইট ধীর হয়ে প্রতিজন ৬টি অনুরোধ পাঠালে প্রথম ৫ মিনিটের অনুরোধ/সেকেন্ড।

    প্রথম ৩০ মিনিটে দর্শক: ৩,০০,০০০ × ০.৭ = ২,১০,০০০ জন; অনুরোধ: ২,১০,০০০ × ৩ = ৬,৩০,০০০.

    • (ক) ৬,৩০,০০০ ÷ ১,৮০০ সেকেন্ড = ৩৫০ অনুরোধ/সেকেন্ড।
    • (খ) প্রথম ৫ মিনিটে ৬,৩০,০০০ × ০.৪ = ২,৫২,০০০ অনুরোধ, ৩০০ সেকেন্ডে: ২,৫২,০০০ ÷ ৩০০ = ৮৪০ অনুরোধ/সেকেন্ড — ৩০ মিনিটের গড়ের ২.৪ গুণ।
    • (গ) ৮৪০ × ৫ KB = ৪,২০০ KB/সেকেন্ড = ৪.২ MB/সেকেন্ড; × ৮ = ৩৩.৬ Mbit/s.
    • (ঘ) প্রতিজনের অনুরোধ দ্বিগুণ, তাই প্রথম ৫ মিনিটে ১,৬৮০ অনুরোধ/সেকেন্ড, bandwidth ৬৭.২ Mbit/s.

    নকশার শিক্ষা: সংখ্যাটি বড় নয়, তবু load test করুন অন্তত দ্বিগুণ চাপে (১,৬৮০), কারণ ধীরগতি নিজেই চাপ দ্বিগুণ করে।

  4. একটি ফিচারের API লিখুন: শিক্ষাপ্রতিষ্ঠানের প্রধান লগইন করে নিজের প্রতিষ্ঠানের সব পরীক্ষার্থীর ফল তালিকা আকারে দেখবেন, আর পুরো তালিকা CSV ফাইল হিসেবে নামাবেন। মূল endpoint, request-response-এর রূপ, status code, cache আর নিরাপত্তার নিয়ম লিখুন।
    GET /v1/institutions/{inst_code}/results?year=2026&limit=100&cursor=c2
    Authorization: Bearer <token>
    
    200 OK
    Cache-Control: private, no-store
    { "items": [ { "roll": 104521, "name": "Nusrat Jahan", "gpa": 5.00 }, ... ],
      "next_cursor": "c3" }
    
    POST /v1/institutions/{inst_code}/exports      { "year": 2026, "format": "csv" }
    202 Accepted   { "export_id": "exp_81f2", "status": "pending" }
    
    GET /v1/exports/exp_81f2
    200 OK         { "status": "ready", "download_url": "(short-lived signed link)" }
    
    Errors: 401 no/expired token, 403 token belongs to another institution,
            404 unknown institution or year, 429 too many requests
    • অনুমতি যাচাই: শুধু লগইন নয় — প্রতিটি অনুরোধে দেখতে হবে টোকেনের প্রতিষ্ঠান আর path-এর inst_code একই কি না; না মিললে 403. URL বদলে অন্যের তালিকা দেখা ঠেকানোর এটিই মূল নিয়ম।
    • Pagination: cursor দিয়ে, roll-এর ক্রমে — বড় তালিকা এক উত্তরে নয়। বিস্তারিত পর্ব ০৮-এ।
    • Cache: private, no-store — ব্যক্তিগত তালিকা CDN বা কোনো shared cache-এ যাবে না।
    • CSV: বড় কাজ, তাই অ্যাসিঙ্ক্রোনাস — 202, পরে অবস্থা দেখা, তৈরি হলে স্বল্পমেয়াদি লিংক।
    • Data model: নতুন access pattern, তাই নতুন index: (board, year, inst_code, roll). সাথে প্রতিষ্ঠানপ্রতি rate limit আর প্রতিটি ডাউনলোডের audit log.

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমSystem design-এর ধাপগুলো শুরু করার সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. পরিধি আর requirement — কী বানাচ্ছি, কী বানাচ্ছি না
  2. হিসাব — peak QPS, storage, bandwidth
  3. API — মূল endpoint আর request-response
  4. Data model — সত্তা, key আর access pattern
  5. High-level design — বাক্স আর তীর
  6. Deep dive, তারপর failure আর trade-off

একটি system design ইন্টারভিউয়ে প্রশ্ন পাওয়ার পর প্রথম কাজ হিসেবে কোনটি সবচেয়ে ভালো?

  • সঙ্গে সঙ্গে load balancer আর ডেটাবেসের ছবি আঁকা
  • ব্যবহারকারী, scale আর পরিধি নিয়ে প্রশ্ন করে requirement স্পষ্ট করা
  • কোন প্রোগ্রামিং ভাষায় লেখা হবে তা ঠিক করা
  • সবচেয়ে কঠিন অংশে সরাসরি deep dive শুরু করা

১০ লাখ পরীক্ষার্থীর ৫০%-এর ফল প্রথম ঘণ্টায় দেখা হয়; প্রতিজনের জন্য গড়ে ২টি অনুরোধ আসে; সেই ঘণ্টার অনুরোধের ৪০% আসে প্রথম ১০ মিনিটে। প্রথম ১০ মিনিটে গড়ে প্রতি সেকেন্ডে কতটি অনুরোধ?

  • প্রায় ১১১
  • প্রায় ২৭৮
  • প্রায় ৬৬৭
  • প্রায় ১,৬৬৭

একটি সাইটে প্রতি সেকেন্ডে ৫,০০০ অনুরোধ আসে; cache-এ না পেলে (miss) অনুরোধ ডেটাবেসে যায়। Cache hit ratio ৯৫% থেকে নেমে ৮০% হলে ডেটাবেসে প্রতি সেকেন্ডের query কত গুণ হয়?

  • প্রায় ১৫% বাড়ে
  • ৪ গুণ
  • প্রায় ১.২ গুণ
  • একই থাকে, কারণ মোট অনুরোধ বদলায়নি

একটি জনপ্রিয় key-এর cache মেয়াদ শেষ হতেই একসাথে ২,০০০ অনুরোধ miss করল। কোন কৌশলে এদের মধ্যে মাত্র একটি অনুরোধ ডেটাবেসে যায়, আর বাকিরা সেই উত্তরের অপেক্ষায় থাকে?

  • আরও read replica যোগ করা
  • Request coalescing
  • TTL দ্বিগুণ করা
  • ডেটাবেসের সার্ভার বড় করা (vertical scaling)

একটি টেবিলের primary key (board, year, roll) — এই ক্রমে। নিচের কোন শর্তে খুঁজলে ডেটাবেস এই key-এর index ধরে সরাসরি একটি সারিতে পৌঁছাতে পারে?

  • WHERE roll = 104521
  • WHERE year = 2026 AND roll = 104521
  • WHERE board = 'north' AND year = 2026 AND roll = 104521
  • WHERE name = 'Rafi Ahmed'

পরীক্ষার সব ফল আগে থেকে static ফাইল বানিয়ে CDN-এর পেছনে রাখার নকশায় সবচেয়ে বড় দুর্বলতা কোনটি?

  • প্রকাশের মুহূর্তে origin-এ অনেক বেশি চাপ পড়ে
  • ফল সংশোধনে ফাইল আবার বানিয়ে CDN-এর পুরোনো কপি মুছতে হয়, আর আগের “পাওয়া যায়নি” উত্তর আটকে থাকতে পারে
  • CDN শুধু ছবি রাখতে পারে, JSON নয়
  • Static ফাইল কখনো cache করা যায় না

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. নকশা আট ধাপে: পরিধি → হিসাব → API → data model → high-level design → deep dive → failure ও trade-off → wrap-up; নতুন তথ্য পেলে পেছনে ফেরা চলে।
  2. প্রযুক্তির নাম শেষে; প্রতিটি বাক্সের পেছনে একটি requirement বা একটি সংখ্যা।
  3. গড় নয়, peak ধরে নকশা; আর মনে রাখুন, ধীরগতি নিজেই retry দিয়ে ভিড় বাড়ায়।
  4. জানা spike-এ অস্ত্র হলো প্রস্তুতি: CDN, আগে ভরা cache, request coalescing, rate limit আর ধাপে প্রকাশ।
  5. প্রতিটি বাক্সে “এটি বন্ধ হলে?” — আর ইন্টারভিউতে মুখে ভাবা, অনুমান বলা, মতামত চাওয়া।

পরের পর্বে: মডিউল ২ শুরু — পর্ব ০৭, system design-এর জন্য networking: DNS, TCP, UDP, HTTP আর TLS-এর যা জানা দরকার। আজ যে তীরগুলো আঁকলেন, তাদের ভেতরে আসলে কী চলে, সেটাই দেখব।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান