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

Requirement আর trade-off — functional, non-functional আর ভালো নকশার মাপকাঠি

Requirements & Trade-offs — Functional vs Non-functional Requirements, Describing Load, Quality Attributes, the Trade-off Catalogue & Architecture Decision Records

Taught in Bangla, this lesson turns a vague request to build a photo-sharing app into clear functional and non-functional requirements. It describes load with DAU, read:write ratio, peak factor and fan-out, defines measurable NFRs such as p99 latency, availability and durability, lists classic trade-offs including strong versus eventual consistency, models push versus pull feeds in numbers, and records decisions with MoSCoW, a weighted decision matrix and Architecture Decision Records.

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

  • অস্পষ্ট অনুরোধ থেকে প্রশ্ন করে FR আর NFR-এর স্পষ্ট তালিকা বানানো
  • Load parameter দিয়ে চাহিদা বর্ণনা, আর গড় ও peak সময়ের read-write QPS হিসাব
  • প্রধান NFR-গুলোর সংজ্ঞা আর মাপার উপায়
  • নয়টি ক্লাসিক trade-off, আর strong বনাম eventual consistency বেছে নেওয়ার যুক্তি
  • MoSCoW, weighted decision matrix আর Architecture Decision Record দিয়ে সিদ্ধান্ত স্পষ্ট করা

১ · “একটি ছবি শেয়ারের অ্যাপ বানান” — অস্পষ্ট অনুরোধ থেকে requirement

পর্ব ০১-এ system design-এর বড় ছবিটা দেখেছেন; এবার প্রথম কাজ। ধরুন ইন্টারভিউতে, বা অফিসের মিটিংয়ে, আপনাকে বলা হলো: “একটি ছবি শেয়ারের অ্যাপ বানান।” একজন প্রকৌশলী সঙ্গে সঙ্গে বোর্ডে বাক্স আঁকলেন — load balancer, Kafka, Redis, দশটি microservice. আরেকজন আগে প্রশ্ন করলেন: “কারা ব্যবহার করবেন? কতজন? ভিডিও লাগবে? ছবি হারালে কতটা ক্ষতি?” অভিজ্ঞ ইন্টারভিউয়ার দ্বিতীয়জনকেই বেছে নেবেন, কারণ উত্তর না জেনে আঁকা নকশা ঠিক না ভুল, তা বিচারের কোনো মাপকাঠিই থাকে না।

বিয়ের ক্যাটারারকে শুধু বললেন, “খাবার দেবেন।” অভিজ্ঞ ক্যাটারার জিজ্ঞেস করবেন: মেনু কী? অতিথি কতজন, কয় ব্যাচে? বাজেট কত? মেনু হলো কী দিতে হবে — functional; অতিথি, ব্যাচ আর বাজেট হলো কতজনের জন্য, কত খরচে — non-functional. মেনু একই থাকলেও ৫০ জন আর ২,০০০ জনের রান্নাঘর সম্পূর্ণ আলাদা।

Functional requirement (FR) বলে সিস্টেম কী করবে — যেমন “ব্যবহারকারী ছবি আপলোড করতে পারবেন।” Non-functional requirement (NFR) বলে কাজগুলো কতটা ভালোভাবে হবে — কত দ্রুত, কতজনের জন্য, কত সময় চালু থেকে, ডেটা না হারিয়ে, কতটা নিরাপদে আর কত খরচে; একে quality attribute-ও বলা হয়। সহজ পরীক্ষা: বাক্যটি বাদ দিলে একটি feature-ই হারায়? তাহলে FR. Feature থাকে, কিন্তু ধীর বা ঝুঁকিপূর্ণ হয়? তাহলে NFR.

মূল ধারণা

নকশার আকার ঠিক করে NFR: “ছবি আপলোড” ১০০ জনের জন্য একটি সস্তা সার্ভারেই চলে, ১ কোটি জনের জন্য লাগে অনেক সার্ভার, cache আর queue. আর সংখ্যা ছাড়া NFR শুধু শুভকামনা — “দ্রুত হবে” মাপা যায় না, “feed-এর ৯৯% অনুরোধ ৫০০ ms-এর মধ্যে” মাপা যায়।

নিচে পাঁচটি প্রশ্নে পূর্ণ তালিকা দাঁড়ানো দেখুন। উত্তরগুলো “ধরা যাক” ধরনের অনুমান; বাস্তবে আসে product দল আর আগের metric থেকে।

এই ধাপের প্রশ্ন আর উত্তর
প্রশ্ন
উত্তর (ধরা যাক)
অনুরোধ“ছবি শেয়ারের অ্যাপ বানান”
প্রশ্ন ১কী কী কাজ?
প্রশ্ন ২কতজন, কত ঘন ঘন?
প্রশ্ন ৩কত দ্রুত, কত সময় চালু?
প্রশ্ন ৪কী হারানো চলবে না?
প্রশ্ন ৫গোপনীয়তা আর সীমা?
F1 আপলোডক্যাপশনসহ ছবি
F2 followfollow আর unfollow
F3 home feedনতুন ছবি আগে
F4 like, commentএকজন একবারই like
এবার নয়ভিডিও, story, message
N1 load১ কোটি DAU, পড়া:লেখা ১০:১
N2 latencyfeed p99 < ৫০০ ms
N3 availabilityfeed দেখা ৯৯.৯%
N4 durabilityনিশ্চিত ছবি হারাবে না
N5 consistencylike-সংখ্যা eventual
N6 privacyprivate, মোছা মানে মোছা
C1 constraint৫ জনের দল, সীমিত বাজেট
  1. অস্পষ্ট অনুরোধ। এতে একটি ইচ্ছা আছে, কোনো মাপ নেই। এখান থেকেই নকশা আঁকলে আপনি নিজের কল্পনার সমস্যার সমাধান করবেন, প্রশ্নকর্তার নয়।
  2. প্রশ্ন ১ — কী কী কাজ? চারটি FR: F1 আপলোড, F2 follow, F3 home feed, F4 like আর comment. সমান জরুরি স্পষ্ট “এবার নয়” তালিকা — ভিডিও, story, message. Scope ছোট হলে গভীরে যাওয়ার সময় মেলে।
  3. প্রশ্ন ২ — কতজন, কত ঘন ঘন? প্রথম NFR হলো load: ধরা যাক দৈনিক সক্রিয় ব্যবহারকারী (DAU) ১ কোটি, প্রত্যেকে দিনে ৫০টি অনুরোধ করেন, আর প্রতি ১০টি পড়ায় ১টি লেখা। অংশ ২-এ এই তিন সংখ্যা থেকেই প্রতি সেকেন্ডের চাপ বের হবে।
  4. প্রশ্ন ৩ — কত দ্রুত, কত সময় চালু? N2: feed লোডের p99 ৫০০ ms-এর কম — ১০০টি অনুরোধের অন্তত ৯৯টি ৫০০ ms-এর মধ্যে (পর্ব ০৪)। N3: feed দেখা মাসে ৯৯.৯% সফল, অর্থাৎ ৩০ দিনের মাসে মোটামুটি ৪৩ মিনিট বিভ্রাট চলে (পর্ব ০৫)।
  5. প্রশ্ন ৪ — কী হারানো চলবে না? দুটি আলাদা গুণ। N4 durability: “সফল” দেখানো ছবি কখনো হারাবে না। N5 consistency: like-এর সংখ্যা কয়েক সেকেন্ড পুরোনো দেখালে চলে — এই উত্তরটিই পরে নকশাকে অনেক সস্তা করবে (অংশ ৫)।
  6. প্রশ্ন ৫ — গোপনীয়তা আর সীমা? N6 privacy: private ছবি শুধু অনুমোদিত follower দেখবেন, মোছা ছবি নির্দিষ্ট সময়ে সব কপি থেকে মুছবে। আর একটি constraint: ৫ জনের দল, সীমিত বাজেট — requirement নয়, কিন্তু প্রতিটি সিদ্ধান্তের সীমা।
  7. ফল: এক পাতার requirement. একটি অস্পষ্ট বাক্য হয়ে গেল ৪টি FR, ৬টি NFR, একটি constraint আর একটি “এবার নয়” তালিকা। এখন নকশার প্রতিটি বাক্সের পাশে লেখা যাবে, সেটি কোন requirement-এর জন্য (অংশ ৯)।

এই ID-গুলো (F1–F4, N1–N6, C1) পুরো পর্বে ফিরে আসবে। একটি সূক্ষ্মতা খেয়াল করুন: N5-এ like-এর সংখ্যা পিছিয়ে থাকলে চলে, কিন্তু কেউ block বা private করলে তা সঙ্গে সঙ্গে কার্যকর হতে হবে — একই সিস্টেমে দুই রকম consistency (অংশ ৫)।

২ · Load বর্ণনা — চাহিদাকে কয়েকটি সংখ্যায় বাঁধা

“আমাদের সিস্টেম কি scale করবে?” — প্রশ্নটির অর্থ নেই, যতক্ষণ না বলা হয় কোন চাপে। Martin Kleppmann-এর বই Designing Data-Intensive Applications (O’Reilly, ২০১৭) এই চিন্তাটি জনপ্রিয় করেছে: আগে বর্তমান চাপকে কয়েকটি সংখ্যায় বর্ণনা করুন — বইটি এদের বলে load parameter — তারপর জিজ্ঞেস করুন, “সংখ্যাটি দ্বিগুণ হলে কী ঘটবে?” (বইটির উদাহরণ মূল বইয়ে দেখে নেবেন; এখানে ধারণাটি আমাদের মতো করে সাজানো।) সবচেয়ে প্রচলিত পাঁচটি:

Load parameterমানেছবির অ্যাপে (ধরা যাক)
অনুরোধের হার (QPS বা RPS)প্রতি সেকেন্ডে কতটি অনুরোধ — গড়ে আর সবচেয়ে ব্যস্ত সময়েfeed লোড, আপলোড, like — আলাদা করে
Read:write ratioপ্রতি একটি লেখায় কতবার পড়া১০:১ — প্রতি ১১টি অনুরোধে ১টি লেখা
সক্রিয় ব্যবহারকারীদিনে কতজন (DAU); একই মুহূর্তে কতজন যুক্ত১ কোটি DAU
ডেটার আকার ও বৃদ্ধিমোট ডেটা, দৈনিক বৃদ্ধি, কতদিন রাখতে হয়দৈনিক নতুন ছবি × গড় আকার (পর্ব ০৩)
Fan-outএকটি কাজ ভেতরে কয়টি কাজ তৈরি করেএকটি পোস্ট → সব follower-এর feed

Fan-out দুই অর্থে আসে: social fan-out — একটি পোস্ট সব follower-এর feed-এ পৌঁছানো (অংশ ৬); আর request fan-out — একটি অনুরোধ ভেতরে ২০টি সার্ভিসকে ডাকে, আর সবচেয়ে ধীরটিই পুরো অনুরোধকে ধীর করে (tail latency, পর্ব ০৪)।

গড় QPS = DAU × জনপ্রতি দিনের অনুরোধ ÷ ৮৬,৪০০
write QPS = গড় QPS ÷ (R + ১) · read QPS = গড় − write
peak QPS = গড় QPS × peak factor

R মানে প্রতি লেখায় কয়টি পড়া (১০:১ হলে R = ১০), তাই প্রতি (R + ১)-টি অনুরোধে ১টি লেখা; ৮৬,৪০০ = এক দিনের সেকেন্ড। আমাদের অনুমানে: ১ কোটি × ৫০ = দিনে ৫০ কোটি অনুরোধ; ÷ ৮৬,৪০০ ≈ প্রতি সেকেন্ডে ৫,৭৮৭টি; এর ১১ ভাগের ১ ভাগ লেখা ≈ ৫২৬, পড়া ≈ ৫,২৬১. Peak factor (সবচেয়ে ব্যস্ত সময়ের হার ÷ সারাদিনের গড়) ৩ ধরলে peak-এ লেখা ≈ ১,৫৭৮ আর পড়া ≈ ১৫,৭৮৩. এই সংখ্যা থেকে storage আর সার্ভারের হিসাব পর্ব ০৩-এ।

Workload-এর চরিত্র: পড়া-ভারী, লেখা-ভারী, আর ভিড়ের সময়

Read-heavy সিস্টেমে পড়া অনেক বেশি — সংবাদের সাইট, পণ্যের ক্যাটালগ, social feed, পরীক্ষার ফলের পাতা; প্রধান অস্ত্র cache, CDN আর read replica. Write-heavy সিস্টেমে লেখা বেশি বা প্রায় সমান — chat, log সংগ্রহ, ride-sharing-এ কয়েক সেকেন্ড পরপর গাড়ির অবস্থান (পর্ব ৫৮); এখানে cache কম কাজে আসে, লাগে লেখার পথ ভাগ করা (পর্ব ২৩), queue আর লেখায় দক্ষ storage engine (পর্ব ১৯)।

চাহিদা সময় ধরেও বদলায় — রাতে কম, সন্ধ্যায় বেশি, আর ঋতু ধরে (seasonality). চেনা উদাহরণ: ঈদের আগে অনলাইন কেনাকাটা আর বাস-ট্রেনের টিকিট; SSC বা HSC-র ফল প্রকাশের মুহূর্ত, যখন লাখো পরীক্ষার্থী আর অভিভাবক প্রায় একই মিনিটে পাতাটি খোলেন; বড় ক্রিকেট ম্যাচের শেষ ওভারে streaming-এর হঠাৎ ভিড়। শেষ দুটিতে চাহিদা ধীরে বাড়ে না, একটি মুহূর্তে লাফ দেয়। সবাই একসাথে একই জিনিস চাইলে তাকে বলে thundering herd (পর্ব ১৮, ২৯)।

ঈদের আগে বাস টার্মিনালের কাউন্টার ভাবুন: “বছরের গড়” দেখে দুটি কাউন্টার রাখলে ঈদের সপ্তাহে লাইন রাস্তায় ঠেকে, আর সারা বছর বিশটি খোলা রাখা অপচয়। ভালো ব্যবস্থাপক আগের বছরের হিসাব দেখে ভিড়ের সময় অস্থায়ী কাউন্টার খোলেন, লাইনে টোকেন দেন — autoscaling আর queue-র একই বুদ্ধি।

Peak factor নিজের তথ্য থেকে মাপুন। নিচের কোডের ৩ বা ৬ শুধু অনুমান; বাস্তবে আগের একই ঘটনার metric দেখে, বাড়তি জায়গা (headroom) রেখে ঠিক করা হয়। নকশা হয় peak-এর জন্য, গড়ের জন্য নয়।

নিচের ক্যালকুলেটর চারটি পরিস্থিতির গড় আর peak read-write QPS বের করে; নিজের সংখ্যা বসিয়ে দেখুন।

// Load from requirements for a photo-sharing app.
// Every input below is an ASSUMPTION, not a measurement: put in your own numbers.
const DAY = 86400;                                  // seconds in a day
const f = (x) => Math.round(x).toLocaleString("en-US").padStart(13);

function load(name, dau, actionsPerUser, readsPerWrite, peakFactor) {
  const avg = (dau * actionsPerUser) / DAY;         // average requests per second
  const w = avg / (readsPerWrite + 1);              // 1 write for every readsPerWrite reads
  const r = avg - w;
  console.log(name.padEnd(21) + f(r) + f(w) + f(r * peakFactor) + f(w * peakFactor));
}

console.log("scenario".padEnd(21) + ["read/s", "write/s", "peak read/s", "peak write/s"]
            .map((h) => h.padStart(13)).join(""));
//   name                   DAU     actions/user/day  reads per write  peak factor
load("A: normal day",       10e6,   50,               10,              3);
load("B: festival evening", 10e6,   50,               10,              6);
load("C: chat-like, 1:1",   10e6,   50,               1,               3);
load("D: users doubled",    20e6,   50,               10,              3);

আউটপুট:

scenario                    read/s      write/s  peak read/s peak write/s
A: normal day                5,261          526       15,783        1,578
B: festival evening          5,261          526       31,566        3,157
C: chat-like, 1:1            2,894        2,894        8,681        8,681
D: users doubled            10,522        1,052       31,566        3,157

সারি A হাতের হিসাব মেলায়: ৫,২৬১ পড়া আর ৫২৬ লেখা প্রতি সেকেন্ডে, peak-এ ১৫,৭৮৩ আর ১,৫৭৮. সারি B-তে শুধু উৎসবের peak factor ৬ — peak-এ লেখা ৩,১৫৭, হুবহু সারি D-র সমান, যেখানে ব্যবহারকারীই দ্বিগুণ: একটি ব্যস্ত সন্ধ্যা দ্বিগুণ ব্যবহারকারীর capacity চাইতে পারে। সারি C-তে মোট অনুরোধ একই, কিন্তু ১:১ হলে লেখা ৫২৬ থেকে ২,৮৯৪ — সাড়ে পাঁচ গুণ; read replica বা cache লেখার চাপ কমায় না। readsPerWrite ১০০ করে দেখুন, লেখা কত নামে?

৩ · Non-functional requirement-এর তালিকা — সংজ্ঞা আর মাপার উপায়

NFR-এর নামগুলো চেনা, কিন্তু আলোচনায় প্রায়ই গুলিয়ে যায়। নিচে প্রতিটির সংজ্ঞা আর কীভাবে মাপবেন — যা মাপা যায় না, তার লক্ষ্যও ঠিক করা যায় না।

গুণসংজ্ঞাকীভাবে মাপা হয়গভীরে
Scalabilityচাপ বাড়লে resource যোগ করে performance ধরে রাখাload test-এ চাপ বাড়িয়ে latency আর throughput; প্রতি সার্ভারে QPSপর্ব ১৩
Performance: latency, throughputএকটি অনুরোধে কত সময়; প্রতি সেকেন্ডে কতটা কাজlatency-র percentile (p50, p99); QPSপর্ব ০৪
Availabilityদরকারের মুহূর্তে সেবা কাজ করছে কি নাসফল অনুরোধ ÷ মোট অনুরোধ, বা চালু সময় ÷ মোট সময়; প্রায়ই “nines” দিয়ে বলা হয় (যেমন ৯৯.৯%)পর্ব ০৫
Reliabilityত্রুটি (fault) সত্ত্বেও সময় ধরে সঠিক কাজব্যর্থতার হার, দুই ব্যর্থতার মাঝের গড় সময় (MTBF), ভুল উত্তরের হারপর্ব ০৫
Durabilityনিশ্চিত (acknowledged) হওয়া ডেটা পরে হারায় নাকয়টি স্বাধীন কপি, কোথায়; backup থেকে ফেরত আনার মহড়াপর্ব ২৬, ৫১
Consistencyকপি আর পাঠকেরা কতটা সর্বশেষ, একই মান দেখেনreplication lag; পুরোনো মান পড়ার (stale read) হারপর্ব ২২, ৪০
Maintainabilityসহজে চালানো, বোঝা আর বদলানো যায় কি নাdeploy-এর সময়, কত পরিবর্তন সমস্যা ঘটায়, বিভ্রাট সারাতে গড় সময় (MTTR)পর্ব ৪৭, ৫০, ৫৩
Security ও privacyঅননুমোদিত প্রবেশ ঠেকানো; দরকারের বেশি তথ্য না রাখাaccess review, audit log, রাখার মেয়াদ মানা হচ্ছে কি নাপর্ব ৫২
Costবানানো আর চালানোর খরচমাসিক বিল; প্রতি ১,০০০ অনুরোধে বা প্রতি সক্রিয় ব্যবহারকারীতে খরচপর্ব ০৩

Availability, reliability আর durability — তিনটি আলাদা প্রশ্ন

Availability: এখন চাইলে সেবা পাব কি? Reliability: সময় ধরে, ত্রুটি সত্ত্বেও, সঠিক উত্তর দিয়ে যাবে কি? Durability: জমা নিশ্চিত হওয়া ডেটা কখনো হারাবে কি? প্রতি মিনিটে ১ সেকেন্ড বন্ধ হওয়া সার্ভিসের availability প্রায় ৯৮.৩৩%, কিন্তু মাঝপথ থেকে আবার শুরুর (resume) সুবিধা না থাকলে এক মিনিটের বেশি সময়ের আপলোড কখনো শেষ হবে না — reliability খুব কম। Database এক ঘণ্টা বন্ধ অথচ ডেটা অক্ষত: availability কমেছে, durability নয়। শুধু memory-তে রাখা cache সবসময় সাড়া দেয়, কিন্তু restart-এ সব হারায় — available, durable নয়। “Nines”-এর অঙ্ক পর্ব ০৫-এ, percentile পর্ব ০৪-এ।

Consistency — strong বনাম eventual, প্রথম পরিচয়

একই ডেটার একাধিক কপি থাকলে (replication), এক কপিতে লেখার পর অন্য কপি থেকে পড়লে কী দেখব? Strong consistency: লেখা সম্পন্ন হওয়ার পর প্রতিটি পড়া সেটি দেখবে, যেন একটিই কপি। Eventual consistency: নতুন লেখা থামলে কপিগুলো শেষ পর্যন্ত মিলবে, কিন্তু মাঝে পুরোনো মান দেখা যেতে পারে — এই দেরি replication lag. Strong-এর দাম latency আর availability, eventual-এর দাম পুরোনো মান। পুরো তত্ত্ব পর্ব ৪০-এ (CAP, PACELC আর consistency model).

ACID-এর C আর CAP-এর C এক জিনিস নয়। ACID-এর consistency: transaction ডেটাবেসকে এক বৈধ অবস্থা থেকে আরেক বৈধ অবস্থায় নেয় — নিয়ম (invariant) রক্ষা হয়, যেমন balance কখনো ঋণাত্মক নয়; নিয়ম ঠিক করে মূলত application (ডেটাবেস কোর্সের Transaction আর ACID পর্ব)। CAP-এর consistency মানে linearizability: সব কপি মিলে যেন একটিই কপি — লেখা সম্পন্ন হলে পরের প্রতিটি পড়া, যে কপি থেকেই হোক, সেটি দেখবে। পুরোপুরি ACID একটি database-ও asynchronous replica থেকে পুরোনো মান পড়াতে পারে — তখন সে CAP অর্থে consistent নয়। “Eventual consistency” তৃতীয় আরেক অর্থ। তাই “consistent” শুনলেই জিজ্ঞেস করুন: কোন অর্থে?

Maintainability, security-privacy আর cost

Kleppmann-এর বই maintainability-কে তিন ভাগে দেখায়: operability — চালানোর দল যেন সহজে নজর রাখতে, সারাতে আর deploy করতে পারে (metric, log, নির্দেশিকা); simplicity — অপ্রয়োজনীয় জটিলতা কম; evolvability — নতুন requirement এলে বদলানো সহজ। আজ দ্রুত কিন্তু কেউ বোঝে না এমন সিস্টেম এক বছর পরে সবচেয়ে দামি হয়।

Security ও privacy শুরু থেকেই NFR. দুটি নীতি নকশাকে সরাসরি বদলায়। Data minimisation: যে তথ্য কাজে লাগে না, তা সংগ্রহই নয় — যা নেই, তা ফাঁসও হয় না। Retention: কোন তথ্য কতদিন থাকবে, আর মেয়াদ শেষে cache, replica, backup-সহ সব কপি থেকে কীভাবে মুছবে। ইউরোপের GDPR (২০১৮ থেকে কার্যকর)-এর মতো আইনে দুটিই মূলনীতি; দেশভেদে নিয়ম আলাদা হতে পারে, তাই নির্দিষ্ট আইনি দায় compliance বিশেষজ্ঞের সাথে মিলিয়ে নিন (প্রতিরক্ষার কৌশল পর্ব ৫২-এ)। আর cost-ও requirement: খরচ মাপুন প্রতি ১,০০০ অনুরোধে বা প্রতি সক্রিয় ব্যবহারকারীতে, তাহলে বৃদ্ধির সাথে বিল কীভাবে বাড়বে, আগেই দেখা যায় (পর্ব ০৩)।

৪ · Constraint আর assumption — নকশার সীমানা আর অনুমান

Requirement বলে কোথায় পৌঁছাতে হবে; constraint বলে কোন পথ বন্ধ, অন্তত এখন; আর assumption হলো যা সত্য ধরে নিয়েছি, কিন্তু যাচাই করিনি। কেউ বললেন “Kubernetes চাই” — এটা requirement, constraint, না নিছক পছন্দ? প্রশ্নটা করলেই অনেক তর্ক মেটে।

ধরনউদাহরণনকশায় প্রভাব
বাজেটমাসিক cloud খরচের সীমাmanaged service, একটি region দিয়ে শুরু
দলের আকার ও দক্ষতা৫ জন; সবাই PostgreSQL-এ দক্ষচেনা প্রযুক্তি, কম চলমান অংশ; module-এ ভাগ করা monolith (পর্ব ৪৭)
বিদ্যমান সিস্টেমপুরোনো user database, চালু payment ব্যবস্থাএগুলোর সাথে সংযোগ; adapter আর ধাপে ধাপে migration
Compliance ও নিয়মব্যক্তিগত বা আর্থিক তথ্য; কার্ডের তথ্য নিলে PCI DSS-এর মতো শিল্প-মানদণ্ড প্রযোজ্য হতে পারেডেটা কোথায় থাকবে, কে দেখবে, কতদিন — শুরুতেই; audit log
সময়৬ মাসে প্রথম সংস্করণbuild বনাম buy-তে কেনার দিকে ঝোঁক (অংশ ৫)

Assumption নম্বর দিয়ে লিখে রাখুন। “A1: বেশিরভাগ ব্যবহারকারী ধীর মোবাইল নেটওয়ার্কে” — সত্য হলে ছবি ছোট রাখা আর কয়েকটি resolution বানানো জরুরি। “A2: পড়া:লেখা ≈ ১০:১” — ভুল হলে cache-নির্ভর নকশা দুর্বল হবে। লিখে না রাখা অনুমান লুকানো ঝুঁকি; তাই পাশে লিখুন কীভাবে যাচাই হবে — প্রথম মাসের metric, ছোট load test বা জরিপ।

৫ · Trade-off-এর তালিকা — প্রতিটি সুবিধার একটি দাম

Trade-off মানে একটি গুণ পেতে আরেকটি কিছুটা ছাড়া। ঢাকায় একই গন্তব্যের কয়েকটি পথ: flyover দ্রুত কিন্তু ঘুরপথ, গলি ছোট কিন্তু অনিশ্চিত, মেট্রো নির্ভরযোগ্য কিন্তু স্টেশন পর্যন্ত হাঁটা। কোনটি “সেরা”, তা নির্ভর করে আজ কী দরকার। ভালো প্রকৌশলী প্রতিটি সিদ্ধান্তে বলতে পারেন: কী পেলাম, কী দাম দিলাম, আর কোন requirement-এর কারণে দামটি গ্রহণযোগ্য।

Trade-offযা পাবেনযে দামছোট বাস্তব উদাহরণগভীরে
Latency বনাম consistencyকাছের replica থেকে দ্রুত উত্তরমান পুরোনো হতে পারে; সর্বশেষ চাইলে leader বা quorum পর্যন্ত বাড়তি সময়Google-এর Spanner (২০১২ সালের প্রকাশিত paper অনুযায়ী) commit-এর পর ঘড়ির অনিশ্চয়তা কাটা পর্যন্ত অপেক্ষা করে (commit wait) — consistency কেনে latency দিয়েপর্ব ৪০
Consistency বনাম availabilitynetwork ভাগ হলেও লেখা নেওয়া যায়বিরোধী কপি, পরে মেলাতে হয়Amazon-এর Dynamo paper (২০০৭): shopping cart-এ লেখা কখনো ফেরানো হয় না; মেলানোর সময় মুছে ফেলা জিনিস ফিরে আসতে পারেপর্ব ৪০, ৪১
Cost বনাম performanceগরম ডেটা RAM-এ, অনেক replica — দ্রুতবিল বাড়েপুরোনো, কম দেখা ছবি সস্তা storage স্তরে: সঞ্চয়, কিন্তু আনতে বেশি সময় বা খরচ (সেবাভেদে আলাদা)পর্ব ২৬
Simplicity বনাম flexibilityএকটি monolith: বোঝা, deploy, debug সহজঅংশগুলো আলাদা করে বাড়ানো কঠিন৫ জনের দলে শুরুতেই ২০টি microservice — network call আর deploy-এর জটিলতা বহু গুণপর্ব ৪৭
Build বনাম buyনিজে বানালে পূর্ণ নিয়ন্ত্রণসময় আর রক্ষণাবেক্ষণ; কিনলে খরচ আর vendor lock-inকার্ডে পেমেন্ট নিতে প্রায় সবাই payment gateway ব্যবহার করেন — card network-এর নিয়ম আর নিরীক্ষা বিশাল কাজপর্ব ৫২
Normalisation বনাম denormalisationNormalized: এক তথ্য এক জায়গায়, হালনাগাদ সহজপড়তে join, ধীর হতে পারেছবির সারিতে like_count রাখা (denormalized) বনাম প্রতিবার COUNT(*) — দ্রুত পড়া, কিন্তু দুই জায়গা মেলানোর দায়ডেটাবেস কোর্স: Denormalization
Push বনাম pullPush: লেখার সময়ই কাজ, পড়া সস্তাজনপ্রিয় লেখকের প্রতিটি পোস্টে বিপুল লেখাFeed-এ fan-out on write বনাম on read (অংশ ৬)পর্ব ৫৬
Synchronous বনাম asynchronousAsync: দ্রুত সাড়া, ভিড় queue-তে জমেফল পরে আসে; ব্যর্থতা সামলানো জটিলআপলোডের পর thumbnail queue-তে; কিন্তু টাকা পাঠানোর ফল স্পষ্ট জানাতে হয়পর্ব ৩১
SQL বনাম NoSQLSQL: join, transaction, constraintবিশাল scale-এ ভাগ করা কঠিন হতে পারেkey ধরে পড়ার মতো নির্দিষ্ট access pattern-এ NoSQL সহজে ছড়ায়; তবে আজ সীমারেখা অনেকটাই ঝাপসাপর্ব ২০

প্রতিটি সারিতে “কোনটি ভালো?” প্রশ্নের উত্তর একই — নির্ভর করে, আর নির্ভর করে requirement-এর ওপর। এবার একটি trade-off ধাপে ধাপে: একই প্রশ্ন, দুই ধরনের ডেটা, দুই উত্তর।

দুটি তথ্য, প্রতিটির দুটি কপি: leader ক আর replica খ
ঘটনা
Like · ক
Like · খ
Balance · ক
Balance · খ
মোট তোলা
ফল
  1. দুটি তথ্য, একই প্রশ্ন। একটি ছবির like ১২০৩টি; রাফির (কাল্পনিক) mobile wallet-এ balance ৳৫,০০০. দুটিরই দুটি কপি — প্রধান কপি (leader) ক, আর পড়ার ভার কমাতে replica খ. Replica কি সবসময় সর্বশেষ মান দেখাবে, নাকি একটু পিছিয়ে থাকলেও চলবে?
  2. Like: replica পিছিয়ে। দুজন like দিলেন, ক-তে এখন ১২০৫; খ এখনো ১২০৩ দেখাচ্ছে — replication lag (পর্ব ২২)। খ থেকে পড়া একজন কয়েক মুহূর্ত ১২০৩ দেখলেন। ক্ষতি প্রায় শূন্য।
  3. শেষ পর্যন্ত মিলে গেল। পরিবর্তন খ-তে পৌঁছাল, দুই কপিই ১২০৫ — eventual consistency. বিনিময়ে দ্রুত লেখা, কারণ কোনো লেখাকে সব কপির জন্য অপেক্ষা করতে হয়নি।
  4. এবার টাকা। রাফি ৳৫,০০০ তুললেন; ক-তে balance ৳০, কিন্তু খ-তে এখনো পুরোনো ৳৫,০০০. এখানেও কি এই অবস্থা নিরীহ?
  5. Eventual হলে বিপদ। তখনই আরেকটি ৳৫,০০০ তোলার অনুরোধ; সিস্টেম balance পড়ল খ থেকে (৳৫,০০০) আর অনুমতি দিল। দুটি debit মেলানোর পর balance −৳৫,০০০, মোট তোলা ৳১০,০০০ — সরাসরি আর্থিক ক্ষতি।
  6. Strong হলে। ধাপ ৪-এর অবস্থা থেকে আবার: দ্বিতীয় অনুরোধ balance পড়ে leader ক থেকে (বা সর্বশেষ লেখা দেখতে বাধ্য quorum থেকে, পর্ব ৪১) — ৳০, তাই প্রত্যাখ্যান। দাম: বাড়তি সময় (latency), আর leader-কে না পেলে “পরে চেষ্টা করুন” (availability কমে)।
  7. সিদ্ধান্ত আসে requirement থেকে। Like-এ eventual, তবে “একজন একবারই like” নিয়ম unique key দিয়ে কঠোরভাবে রক্ষা। Balance-এ strong — সাথে ledger, transaction আর idempotency key, যাতে একই অনুরোধ দুবার এলেও টাকা একবারই কাটে (পর্ব ৬০)। টেবিলে strong পথের শেষ অবস্থা: like ১২০৫, balance দুই কপিতেই ৳০, মোট তোলা ৳৫,০০০. এবার লিখে রাখার পালা (অংশ ৮)।
নীতি

একই কোম্পানি, একই database, অথচ দুই জায়গায় দুই উত্তর। Consistency-র সিদ্ধান্ত হয় ডেটার প্রতিটি অংশের জন্য আলাদাভাবে। প্রশ্ন সবসময় একই: পুরোনো মান দেখালে কার কী ক্ষতি, আর তা ঠেকানোর দাম কত?

৬ · Push বনাম pull — একটি trade-off সংখ্যায় মাপা

Feed বানানোর দুটি মৌলিক উপায়। Fan-out on write (push): পোস্ট করলেই সেটি প্রত্যেক follower-এর আগে-তৈরি feed-এ লেখা হয়; feed খোলা মানে নিজের তালিকাটি পড়া। Fan-out on read (pull): পোস্ট থাকে লেখকের নিজের তালিকায়; feed খোলার সময় যাঁদের follow করেন, তাঁদের প্রত্যেকের তালিকা পড়ে মেলানো হয়।

ডাকঘরের দুই ব্যবস্থা। এক: পিয়ন প্রতিটি চিঠি প্রাপকের বাড়িতে পৌঁছে দেন — পাঠানোয় খাটুনি, কিন্তু দরজা খুললেই চিঠি (push). দুই: চিঠি থাকে প্রেরকের নামের তাকে, আর আপনি গিয়ে প্রত্যেক প্রেরকের তাক ঘেঁটে দেখেন — পাঠানো সস্তা, পড়ায় খাটুনি (pull). কেউ রোজ ৫০ লাখ বাড়িতে চিঠি পাঠালে প্রথম ব্যবস্থায় পিয়নের দিন শেষ হবে না।

কোনটি সস্তা — একটি ছোট প্রমাণ

ধরুন N জন ব্যবহারকারী, তাঁদের মধ্যে মোট E-টি follow সম্পর্ক (গড়ে প্রত্যেকের E/N জন follower). প্রত্যেকে দিনে p-টি পোস্ট করেন আর L-বার feed খোলেন। সরল মডেলে একটি feed-এ একটি লেখা, বা একটি তালিকা একবার পড়া — প্রতিটিকে একটি কাজ ধরি:

  • Push: লেখা = pE (মোট pN-টি পোস্ট, প্রতিটি গড়ে E/N জনের feed-এ); পড়া = LN (প্রতিবার নিজের একটি তালিকা)।
  • Pull: লেখা = pN (প্রতিটি পোস্ট একবার); পড়া = LE (প্রতিবার গড়ে E/N জনের তালিকা)।
Push − Pull = (pE + LN) − (pN + LE) = (E − N)(p − L)

গড়ে প্রত্যেকের একজনের বেশি follower থাকলে E > N, তাই চিহ্নটি ঠিক করে (p − L): মোট কাজের হিসাবে push সস্তা ঠিক তখনই, যখন মানুষ পোস্ট করার চেয়ে বেশিবার feed খোলেন (L > p). Social অ্যাপে প্রায় সবসময় তাই। কিন্তু মোট সংখ্যা একটি বিপদ লুকায়: push-এ একটি পোস্টের খরচ লেখকের follower সংখ্যার সমান, তাই লাখো follower-ওয়ালা একজনের একটি পোস্ট এক মুহূর্তে লাখো লেখা তৈরি করে। প্রচলিত সমাধান hybrid: সাধারণ ব্যবহারকারীর পোস্ট push, খুব জনপ্রিয়দের (“celebrity”) পোস্ট pull — feed খোলার সময় এনে মিশিয়ে দেওয়া। Kleppmann-এর বইয়ের প্রথম অধ্যায়ে, Twitter-এর ২০১২ সালের একটি প্রকাশ্য বক্তৃতার তথ্য ধরে, ঠিক এই পথ — pull থেকে push, তারপর hybrid — বর্ণনা করা আছে। পুরো নকশা পর্ব ৫৬-এ।

নিচের মডেল একটি seed-দেওয়া (প্রতিবার একই ফল দেয় এমন) এলোমেলো সংখ্যার উৎস দিয়ে ১০,০০০ জনের follower সংখ্যা বানায় — বেশিরভাগের কয়েক ডজন, অল্প কয়েকজনের হাজার হাজার — তারপর দুই ধরনের দিনে তিন মডেলের দৈনিক কাজ গোনে:

// Feed cost model: fan-out-on-write (push) vs fan-out-on-read (pull) vs hybrid.
// A seeded PRNG (mulberry32) makes the follower distribution identical on every run.
function mulberry32(a) {
  return function () {
    a = (a + 0x6D2B79F5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
const rand = mulberry32(42), N = 10000, T = 1000;   // T: "celebrity" = more than 1000 followers
// Heavy tail (Pareto, capped): most users have a few dozen followers, a few have thousands.
const fol = Array.from({ length: N }, () =>
  Math.min(N - 1, Math.floor(10 / (1 - rand()) ** (1 / 1.1))));
const edges = fol.reduce((a, b) => a + b, 0);       // total follow relations
const celebs = fol.filter((x) => x > T), celebEdges = celebs.reduce((a, b) => a + b, 0);
const maxPush = Math.max(...fol), maxHybrid = Math.max(...fol.filter((x) => x <= T));
console.log(`users ${N}, follows ${edges}, avg followers ${(edges / N).toFixed(1)}, celebrities ${celebs.length}`);
console.log(`one post, worst case: push ${maxPush} writes, hybrid ${maxHybrid} writes`);
const g = (x) => Math.round(x).toLocaleString("en-US").padStart(12);
function day(label, postsPerUser, loadsPerUser) {
  const posts = N * postsPerUser, loads = N * loadsPerUser;
  const rows = [  // [model, writes/day, reads/day]
    ["push (on write)", postsPerUser * edges, loads],                  // copy post into every follower's feed
    ["pull (on read)", posts, loads * (edges / N)],                    // read each followee's list per load
    ["hybrid", postsPerUser * (edges - celebEdges + celebs.length),    // push normal users only,
               loads * (1 + celebEdges / N)],                          // pull celebrities at read time
  ];
  console.log(`\n${label}: ${posts} posts/day, ${loads} feed loads/day`);
  console.log("model".padEnd(16) + ["writes/day", "reads/day", "total"].map((h) => h.padStart(12)).join(""));
  for (const [m, w, r] of rows) console.log(m.padEnd(16) + g(w) + g(r) + g(w + r));
}
day("W1 read-heavy feed", 0.2, 20);
day("W2 many posts, few loads", 3, 1);

আউটপুট:

users 10000, follows 598669, avg followers 59.9, celebrities 65
one post, worst case: push 9999 writes, hybrid 1000 writes

W1 read-heavy feed: 2000 posts/day, 200000 feed loads/day
model             writes/day   reads/day       total
push (on write)      119,734     200,000     319,734
pull (on read)         2,000  11,973,380  11,975,380
hybrid                81,659   4,008,780   4,090,439

W2 many posts, few loads: 30000 posts/day, 10000 feed loads/day
model             writes/day   reads/day       total
push (on write)    1,796,007      10,000   1,806,007
pull (on read)        30,000     598,669     628,669
hybrid             1,224,885     200,439   1,425,324

১০,০০০ জনের ৫,৯৮,৬৬৯টি follow সম্পর্ক, গড়ে প্রায় ৬০ জন follower; ১,০০০-এর বেশি follower ৬৫ জনের। W1-এ (০.২টি পোস্ট, ২০ বার feed খোলা — L > p) push-এ মোট প্রায় ৩.২ লাখ কাজ, pull-এ প্রায় ১.২ কোটি — push প্রায় ৩৭ গুণ সস্তা। W2-তে (৩টি পোস্ট, ১ বার খোলা — p > L) উল্টো: pull প্রায় ৬.৩ লাখ, push প্রায় ১৮ লাখ। পার্থক্য দুটি সূত্র মেলায়: (৫,৮৮,৬৬৯) × (০.২ − ২০) ≈ −১,১৬,৫৫,৬৪৬ আর (৫,৮৮,৬৬৯) × (৩ − ১) = ১১,৭৭,৩৩৮. দ্বিতীয় লাইনটিই hybrid-এর যুক্তি: push-এ একটি পোস্টে সবচেয়ে খারাপ ক্ষেত্রে ৯,৯৯৯টি লেখা, hybrid-এ কখনো ১,০০০-এর বেশি নয়। দাম: W1-এ hybrid-এর পড়া push-এর প্রায় ২০ গুণ, কারণ প্রতিবার গড়ে প্রায় ১৯ জন celebrity-র তালিকা পড়তে হয়; বাস্তবে সেগুলো সবার জন্য একই, তাই cache-এ সস্তা — সরল মডেলটি তা ধরেনি। T বদলে ৫০০ বা ৫,০০০ করে দেখুন।

৭ · অগ্রাধিকার আর weighted decision matrix — সিদ্ধান্তকে স্পষ্ট করা

সব NFR একসাথে সর্বোচ্চ রাখা যায় না — সবচেয়ে কম latency, সবচেয়ে কম খরচ আর সবচেয়ে কড়া consistency একসাথে চাওয়া মানে কিছুই না চাওয়া। তাই প্রথম কাজ অগ্রাধিকার ঠিক করা। একটি জনপ্রিয় পদ্ধতি MoSCoW (১৯৯০-এর দশকে Dai Clegg-এর তৈরি বলে পরিচিত): প্রতিটি requirement-কে চারটি ঝুড়ির একটিতে রাখুন।

ঝুড়িমানেছবির অ্যাপে
Mustনা থাকলে প্রকাশই করা যাবে নাF1 আপলোড, F2 follow, F3 feed, N4 durability, N6 privacy
Shouldখুব জরুরি, তবে কিছুদিন সাময়িক বিকল্পে চলেF4 like-comment, N2-এর p99 লক্ষ্য
Couldথাকলে ভালো, সময় পেলেছবির filter, এক পোস্টে কয়েকটি ছবি
Won't (এবার নয়)স্পষ্ট করে বাদ, পরে ভাবা হবেভিডিও, story, message

নকশার বিকল্পগুলোর মধ্যে বাছতে কাজে লাগে weighted decision matrix: (১) বিকল্প লিখুন; (২) মাপকাঠি নিন NFR থেকে; (৩) অগ্রাধিকার থেকে weight দিন, যোগফল ১; (৪) প্রতিটি মাপকাঠিতে ১ থেকে ৫ নম্বর দিন; (৫) নম্বর × weight যোগ করুন; (৬) weight একটু বদলে দেখুন, বিজয়ী বদলায় কি না। নিচে photo metadata আর feed রাখার তিনটি বিকল্প; নম্বর একই, কিন্তু তিন দলের তিন রকম অগ্রাধিকার:

// Weighted decision matrix: 3 options for photo metadata + feed storage.
// Scores 1 (poor) .. 5 (excellent) are the team's own judgement; weights sum to 1.
const criteria = ["latency", "scalability", "consistency", "cost", "simplicity"];
const options = {
  "A: one SQL DB + replicas": [3, 2, 5, 5, 5],
  "B: NoSQL cluster":         [4, 5, 2, 2, 2],
  "C: SQL + cache":           [5, 3, 4, 3, 3],
};
const weightSets = {
  "small team, tight budget": [0.15, 0.10, 0.20, 0.30, 0.25],
  "fast-growing feed":        [0.35, 0.30, 0.10, 0.15, 0.10],
  "huge scale, stale OK":     [0.25, 0.45, 0.05, 0.15, 0.10],
};
console.log("criteria: " + criteria.join(", "));
for (const [label, w] of Object.entries(weightSets)) {
  const sum = w.reduce((a, b) => a + b, 0);
  if (Math.abs(sum - 1) > 1e-9) throw new Error(label + ": weights must sum to 1");
  const ranked = Object.entries(options)
    .map(([name, s]) => [name, s.reduce((acc, x, i) => acc + x * w[i], 0)])
    .sort((a, b) => b[1] - a[1]);
  console.log("\n" + label + "  [" + w.join(", ") + "]");
  for (const [name, total] of ranked) console.log("  " + name.padEnd(26) + total.toFixed(2));
  console.log("  winner: " + ranked[0][0] + " (ahead by " +
              (ranked[0][1] - ranked[1][1]).toFixed(2) + ")");
}

আউটপুট:

criteria: latency, scalability, consistency, cost, simplicity

small team, tight budget  [0.15, 0.1, 0.2, 0.3, 0.25]
  A: one SQL DB + replicas  4.40
  C: SQL + cache            3.50
  B: NoSQL cluster          2.60
  winner: A: one SQL DB + replicas (ahead by 0.90)

fast-growing feed  [0.35, 0.3, 0.1, 0.15, 0.1]
  C: SQL + cache            3.80
  B: NoSQL cluster          3.60
  A: one SQL DB + replicas  3.40
  winner: C: SQL + cache (ahead by 0.20)

huge scale, stale OK  [0.25, 0.45, 0.05, 0.15, 0.1]
  B: NoSQL cluster          3.85
  C: SQL + cache            3.55
  A: one SQL DB + replicas  3.15
  winner: B: NoSQL cluster (ahead by 0.30)

একটি নম্বরও বদলায়নি, অথচ তিন অগ্রাধিকারে তিন বিজয়ী। ছোট দল আর কম বাজেটে (cost ০.৩০, simplicity ০.২৫) A — একটি SQL database আর read replica — ০.৯০ ব্যবধানে এগিয়ে। দ্রুত বাড়তে থাকা feed-এ latency আর scalability-র weight বাড়তেই C (SQL + cache) জেতে, কিন্তু মাত্র ০.২০ ব্যবধানে। বিশাল scale, যেখানে পুরোনো মান চলে, সেখানে B (NoSQL cluster) ০.৩০ এগিয়ে। শিক্ষা দুটি। এক: matrix সিদ্ধান্ত নেয় না, অগ্রাধিকারকে দৃশ্যমান করে — তর্ক “কোন database ভালো” থেকে সরে আসে “আমাদের কাছে cost বেশি জরুরি, না scalability” প্রশ্নে, যার উত্তর requirement-এ আছে। দুই: ০.২০-র মতো ছোট ব্যবধান নম্বরের অনিশ্চয়তার ভেতরেই পড়ে; তখন ছোট prototype বা load test চালিয়ে আসল সংখ্যা মাপুন।

Matrix-এর তিনটি চেনা ফাঁদ। পছন্দের বিকল্প আগে ঠিক করে পরে weight সাজানো; একই জিনিস দুবার গোনা (latency আর scalability প্রায়ই হাত ধরে চলে); আর দশমিকের ভুয়া নির্ভুলতা — ৩.৮০ বনাম ৩.৬০ মানে আসলে “প্রায় সমান”. Weight আর নম্বর দলের সবাই মিলে আগে ঠিক করুন, আর যুক্তিসহ ADR-এ লিখে রাখুন।

৮ · সিদ্ধান্ত লিখে রাখা — Architecture Decision Record (ADR)

ছয় মাস পরে নতুন একজন সদস্য জিজ্ঞেস করলেন: “like-এর সংখ্যা দেরিতে বদলায় — বাগ? ঠিক করে দিই?” যিনি সিদ্ধান্তটি নিয়েছিলেন, তিনি অন্য চাকরিতে; চ্যাটের আলোচনা হারিয়ে গেছে। কোড বলে কী করা হয়েছে, কেন — তা কোথাও নেই।

হাসপাতালের রোগীর ফাইল ভাবুন। একজন ডাক্তার অ্যালার্জির কারণে একটি ওষুধ বন্ধ করে কারণটি ফাইলে লিখে রাখলেন। মাসখানেক পরে আরেক ডাক্তার ফাইল দেখেই জানেন, ওষুধটি কেন দেওয়া যাবে না। ADR হলো সফটওয়্যারের সেই ফাইল।

Architecture Decision Record (ADR)-এর ধারণা জনপ্রিয় হয় Michael Nygard-এর ২০১১ সালের একটি ব্লগ লেখা থেকে: প্রতিটি গুরুত্বপূর্ণ নকশা-সিদ্ধান্তের জন্য এক-দুই পাতার ছোট লেখা, কোডের সাথেই (যেমন docs/adr/ ফোল্ডারে), ক্রমিক নম্বরসহ। মূল লেখায় অংশ পাঁচটি — Title, Context, Decision, Status আর Consequences; অনেক টেমপ্লেটে Status থাকে শিরোনামের ঠিক পরে, কেউ কেউ “বিবেচিত বিকল্প” অংশও যোগ করেন। পুরোনো ADR মোছা হয় না: সিদ্ধান্ত বদলালে নতুন ADR লেখা হয়, আর পুরোনোটির Status হয় “superseded”. নিচে অংশ ৫-এর like-সিদ্ধান্তটিই ADR হয়ে লেখা হচ্ছে।

docs/adr/0004-like-count.mdNygard-ধাঁচের ADR
# ADR-0004: Like-এর সংখ্যায় eventual consistency
তারিখ: 2026-10-28 · সিদ্ধান্তদাতা: backend দল

## Status


## Context
- F4: একজন একটি ছবিতে একবারই like দিতে পারবেন।
- N2: feed-এর p99 < 500 ms. N5: like-এর সংখ্যা কয়েক সেকেন্ড
  পিছিয়ে থাকলে চলবে (product দলের সাথে নিশ্চিত করা)।
- জনপ্রিয় ছবিতে এক সেকেন্ডে বহু like; প্রতিটিতে একই সারির
  counter বদলালে সেই সারিতে lock-এর লাইন পড়ে।
- বিবেচিত বিকল্প: (১) একই transaction-এ counter বাড়ানো,
  (২) প্রতিবার গুনে দেখানো, (৩) আলাদা counter পরে বাড়ানো।

## Decision
আমরা প্রতিটি like লিখব likes(user_id, photo_id) টেবিলে, এই দুই
কলামে unique key দিয়ে। দেখানোর সংখ্যা থাকবে আলাদা counter-এ,
queue-র মাধ্যমে asynchronous-ভাবে বাড়ানো; সেটি সর্বোচ্চ কয়েক
সেকেন্ড পিছিয়ে থাকতে পারে।

## Consequences
+ like দেওয়া দ্রুত; জনপ্রিয় ছবিতেও এক সারিতে lock-এর ভিড় নেই।
+ একজনের দুবার like unique key-ই আটকাবে।
− সংখ্যা কিছুক্ষণ পিছিয়ে থাকবে; প্রতি রাতে counter মেলানোর
  একটি কাজ (reconciliation job) লাগবে।
− queue আর worker — নজর রাখার নতুন দুটি অংশ।
− like-সংখ্যার ভিত্তিতে কখনো টাকা দেওয়া হলে আবার ভাবতে হবে।

(পরে বদলালে: ফাইলটি মুছবেন না; Status-এ লিখুন
 “superseded by ADR-00xx”, আর নতুন ADR-এ এটির উল্লেখ দিন।)
  1. শিরোনাম আর নম্বর। ছোট নামবাচক শিরোনাম, আগে ক্রমিক নম্বর — ADR-0004. নম্বর কখনো পুনর্ব্যবহার হয় না; তারিখ আর সিদ্ধান্তদাতা থেকে জানা যায় কখন, কারা।
  2. Status. শুরুতে “প্রস্তাবিত”, কারণ দল এখনো একমত হয়নি। পরে সিদ্ধান্ত বদলালেও পুরোনো ADR মোছা হয় না, শুধু Status বদলায়।
  3. Context — কোন শক্তিগুলো কাজ করছে। Requirement-এর ID (F4, N2, N5), মাপা তথ্য আর বিবেচিত বিকল্প। ভাষা নিরপেক্ষ: এখনো সিদ্ধান্ত নয়, শুধু পরিস্থিতি।
  4. Decision — আমরা কী করব। Nygard-এর পরামর্শমতো সক্রিয় বাক্যে, স্পষ্ট সীমাসহ: কোন টেবিল, কোন key, কতটা পিছিয়ে থাকা গ্রহণযোগ্য।
  5. Consequences — ভালো আর মন্দ, দুটোই। কী পেলাম (+), কী দাম দিলাম (−), আর কোন অবস্থায় আবার ভাবতে হবে। শুধু ভালো দিক লেখা ADR আসলে বিজ্ঞাপন।
  6. গৃহীত। আলোচনার পর Status হলো “গৃহীত”, তারিখসহ। নতুন সদস্যের প্রশ্নের উত্তর এখন এক পাতায়: দেরিটা ইচ্ছাকৃত, কারণসহ — আর কখন বদলানো উচিত, তাও লেখা।

নিজের প্রকল্পে শুরু করতে নিচের ফাঁকা টেমপ্লেটটি কপি করে নিতে পারেন:

# ADR-NNNN: <ছোট শিরোনাম>
তারিখ: YYYY-MM-DD · সিদ্ধান্তদাতা: …

## Status
প্রস্তাবিত / গৃহীত / বাতিল / superseded by ADR-NNNN

## Context
কোন requirement (ID), কোন মাপা সংখ্যা, কোন সীমা — আর বিবেচিত বিকল্পগুলো

## Decision
আমরা … করব

## Consequences
+ যা পেলাম
− যে দাম দিলাম; কোন অবস্থায় আবার ভাবতে হবে

Context-কে requirement-এর ID দিয়ে বাঁধলে requirement বদলানোর সময় প্রভাবিত সিদ্ধান্তগুলো খুঁজে পাওয়া যায়। পঞ্চাশ পাতার নকশা-দলিল কেউ পড়ে না, এক পাতার ADR পড়ে।

৯ · Requirement থেকে নকশা — প্রতিটি বাক্সের একটি কারণ

এবার সব জোড়া লাগাই। ছবি দেখার অনুরোধ যায় CDN-এ; বাকি অনুরোধ load balancer হয়ে কয়েকটি stateless API সার্ভারে। তৈরি-feed থাকে cache-এ, ছবির ফাইল object storage-এ, আর ধীর কাজ (thumbnail, feed-এ ছড়ানো) যায় message queue হয়ে worker-এর কাছে। প্রতিটি বাক্সের নিচে লেখা, কোন requirement তাকে দরকারি করেছে; যে বাক্সের পাশে কোনো ID বসে না, সেটি সম্ভবত over-engineering.

ছবি শেয়ারের অ্যাপ — প্রতিটি অংশ কোন requirement-এর জন্য

Every box exists because of a requirement (F = functional, N = non-functional)

ছবি শেয়ারের অ্যাপের architecture, প্রতিটি অংশের requirement ID-সহ মোবাইল অ্যাপ CDN Load balancer API সার্ভিস ×N Metadata DB Queue Object storage Feed cache Worker client · F1–F4 N2 · কাছ থেকে ছবি N1 N3 · ভার ভাগ stateless · N1 autoscaling N4 N5 · replica F1 · async কাজ N4 · ছবির ফাইল N2 · তৈরি feed F3 · fan-out ছবি দেখা API কল miss হলে আপলোড thumbnail লেখা
ছবি ২.১ — প্রতিটি বাক্সের নিচে তার requirement-এর ID. CDN-এ ছবি না পেলে আসে object storage থেকে; আপলোডের পর thumbnail আর fan-out-এর কাজ queue হয়ে worker করে, তাই ব্যবহারকারীকে অপেক্ষা করতে হয় না।
Requirementনকশার সিদ্ধান্তদামগভীরে
N1 load (১০:১, peak ৩–৬ গুণ)stateless API, load balancer, autoscaling; read replicareplica lag; নতুন সার্ভার চালু হতে সময়পর্ব ১৩, ১৪, ২২
N2 feed-এর p99 < ৫০০ msআগে-তৈরি feed cache (fan-out on write); ছবির জন্য CDNলেখা বহু গুণ; cache হালনাগাদের ঝামেলাপর্ব ১৭, ২৮, ৫৬
N3 availability ৯৯.৯%একাধিক API সার্ভার, database failover; বিকল হলে cache-এর পুরোনো feed দেখানোবাড়তি খরচ; failover-এর জটিলতাপর্ব ০৫, ১৮, ৫১
N4 durabilityছবি object storage-এ একাধিক কপিতে; টেকসই জমা নিশ্চিত হলে তবেই “সফল”; নিয়মিত backupstorage-এর খরচ; আপলোডে সামান্য বেশি সময়পর্ব ২৬, ৫১
N5 consistencylike-সংখ্যা async counter-এ; block আর privacy সেটিং সবসময় leader থেকে পড়াসংখ্যা সাময়িক পুরোনো; দুই রকম পড়ার পথপর্ব ২২, ৪০
N6 privacyপ্রতিটি অনুরোধে অনুমতি যাচাই; private ছবির স্বল্পমেয়াদি signed URL; cache-backup পর্যন্ত মোছার pipelineCDN-এ caching কঠিন; বাড়তি কাজপর্ব ৫২
F1 আপলোড, উৎসবের ভিড়queue আর worker: thumbnail আর fan-out পরেছবি কয়েক সেকেন্ড “প্রক্রিয়াধীন”; queue চালানোর দায়পর্ব ৩১, ৩৭
C1 ৫ জনের দল, সীমিত বাজেটmanaged service; module-এ ভাগ করা monolith দিয়ে শুরুvendor-নির্ভরতা; পরে ভাগ করার কাজপর্ব ৪৭

আরও গভীরে: NFR-কে software engineering-এ quality attribute বলা হয়; ISO/IEC 25010 মানদণ্ড (২০১১, ২০২৩ সালে হালনাগাদ) এদের একটি আনুষ্ঠানিক তালিকা দেয়। Carnegie Mellon-এর Software Engineering Institute-এর ATAM (Architecture Tradeoff Analysis Method, প্রায় ২০০০ সালের দিকে প্রকাশিত) প্রতিটি quality attribute-কে একটি “scenario”-তে বাঁধতে শেখায় — উৎস, উদ্দীপনা (stimulus), পরিবেশ, সিস্টেমের অংশ, প্রতিক্রিয়া আর তার মাপ; যেমন “উৎসবের সন্ধ্যায় গড়ের ৬ গুণ feed অনুরোধ এলে API-র p99 ৫০০ ms-এর নিচে থাকবে”.

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

  • FR আর NFR-এর পার্থক্য কী, উদাহরণসহ? — FR: সিস্টেম কী করবে, যেমন ছবি আপলোড; NFR: কতটা ভালোভাবে, যেমন feed-এর p99 ৫০০ ms-এর কম থাকা।
  • দৈনিক ১ কোটি ব্যবহারকারী, জনপ্রতি ৫০টি অনুরোধ — গড় QPS কত? — ৫০ কোটি ÷ ৮৬,৪০০ ≈ ৫,৭৮৭; তারপর peak factor দিয়ে গুণ।
  • Availability আর durability কি এক? — না: প্রথমটি এখন সেবা পাওয়া, দ্বিতীয়টি নিশ্চিত ডেটা না হারানো।
  • ACID-এর C আর CAP-এর C? — ACID: ডেটার নিয়ম (invariant) রক্ষা; CAP: linearizability, অর্থাৎ সব কপি মিলে যেন একটিই কপি।
  • Fan-out on write কখন, on read কখন? — feed খোলা পোস্টের চেয়ে বেশি হলে write; খুব জনপ্রিয় অ্যাকাউন্টের জন্য read বা hybrid.

১০ · AI-এর যুগে requirement আর trade-off

AI-এর কাছে “একটি ছবি শেয়ারের অ্যাপের system design” চাইলে কয়েক সেকেন্ডে সুন্দর নকশা আসে — load balancer, Kafka, Redis, দশটি microservice. কিন্তু নকশাটি কোন requirement-এর উত্তর, AI তা জানে না; সে একটি “গড়” প্রশ্নের “গড়” উত্তর দেয়। প্রশ্ন করা, সংখ্যা বসানো আর দাম লেখার কাজটি আপনার; সেখানে AI ভালো সহকারী — প্রশ্নের তালিকা মনে করায়, বিকল্প খোঁজে, ADR-এর খসড়া লেখে।

AI-এর সাধারণ ভুলকেন সমস্যাকীভাবে যাচাই করবেন
প্রশ্ন না করেই “Kafka + Redis + microservices”requirement ছাড়া কোনো অংশের যুক্তি নেইপ্রতিটি বাক্সের পাশে requirement-এর ID; না মিললে বাদ
অঙ্কে ভুল: দিনে ৩,৬০০ সেকেন্ড ধরা, peak ভুলে যাওয়াcapacity দশ গুণ কম বা বেশিঅংশ ২-এর ক্যালকুলেটরে নিজে আবার হিসাব
ব্যর্থতা আর consistency-র প্রশ্ন এড়ানোwallet-এর balance-এও “eventual চলবে”প্রতিটি অংশ বিকল হলে কী হয় — failure-mode review
ACID আর CAP-এর consistency গুলিয়ে ফেলা“database ACID, তাই replica-ও সর্বশেষ” — ভুলকোন অর্থে, জিজ্ঞেস করুন; replica lag মাপুন
পণ্যের limit বা দাম বানিয়ে বলাভুল বাজেট আর capacityofficial documentation আর দামের পাতা, তারিখসহ
Over-engineering: এক হাজার ব্যবহারকারীর অ্যাপে কয়েকটি regionলাভ ছাড়াই খরচ আর জটিলতাMoSCoW আর constraint মেলান; ছোট load test বা simulation

AI-কে নকশা চাওয়ার আগে requirement বের করার কাজে লাগান, আর প্রতিটি সংখ্যা ও সিদ্ধান্ত যাচাইযোগ্য করে চান:

তুমি একজন অভিজ্ঞ backend architect-এর মতো কাজ করো। আমি একটি ছবি শেয়ারের অ্যাপের নকশা করছি। নকশা দেওয়ার আগে: ১. আগে ৮–১০টি clarifying প্রশ্ন করো: মূল কাজ, ব্যবহারকারীর সংখ্যা, read:write ratio, peak, latency ও availability-র লক্ষ্য, কোন ডেটা হারানো চলবে না, গোপনীয়তা, বাজেট, দলের আকার। ২. উত্তর পেলে FR আর NFR আলাদা তালিকায় দাও; প্রতিটি NFR মাপা যায় এমন সংখ্যায় (যেমন p99 latency), আর অনুমানগুলো "ধরা যাক" দিয়ে চিহ্নিত করো। ৩. QPS-এর হিসাব প্রতিটি ধাপসহ দেখাও (দিনে 86,400 সেকেন্ড)। ৪. প্রতিটি বড় সিদ্ধান্তে অন্তত দুটি বিকল্প আর trade-off দাও; Kafka, Redis বা microservices দিলে কোন requirement-এর জন্য, তা বলো। ৫. consistency বললে ACID-এর C না CAP-এর C, স্পষ্ট করো। ৬. পণ্যের limit বা দাম বললে উৎস দাও; নিশ্চিত না হলে "নিশ্চিত নই" লেখো। ৭. প্রতিটি অংশ বিকল হলে কী ঘটবে, তালিকা দাও; শেষে সবচেয়ে বড় সিদ্ধান্তটির ADR লেখো (Title, Status, Context, Decision, Consequences).

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

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

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

প্র ০১Requirement কি শুরুতেই পুরোপুরি লিখে ফেলা যায়? Waterfall আর Agile-এর ইতিহাস এ নিয়ে কী বলে?

বহু দশক ধরে সফটওয়্যার শিল্পের স্বপ্ন ছিল: আগে সব requirement নিখুঁতভাবে লিখে ফেলা, তারপর নকশা, তারপর কোড, তারপর পরীক্ষা — এক ধাপ শেষ হলে তবে পরের ধাপ। এই ছবির সাথে প্রায়ই Winston Royce-এর ১৯৭০ সালের প্রবন্ধ “Managing the Development of Large Software Systems”-এর নাম জোড়া হয়। মজার ব্যাপার, Royce সেখানে সরল ক্রমিক মডেলটি এঁকেই সতর্ক করেছিলেন যে এটি ঝুঁকিপূর্ণ আর ব্যর্থতাকে আমন্ত্রণ জানায়; তিনি ধাপগুলোর মধ্যে ফিরে আসা, আগে একটি prototype বানানো আর ব্যবহারকারীকে যুক্ত রাখার পরামর্শ দিয়েছিলেন। “Waterfall” নামটিও পরে অন্যরা দেন। তবু অনেক প্রতিষ্ঠান সরল ছবিটিই অনুসরণ করে, আর বড় প্রকল্পে বারবার দেখা যায় — দীর্ঘ সময় পরে তৈরি জিনিস ব্যবহারকারীর আসল দরকারের সাথে মেলে না।

২০০১ সালে ১৭ জন সফটওয়্যার প্রকৌশলী Agile Manifesto-তে লেখেন, তাঁরা পরিকল্পনা মেনে চলার চেয়ে পরিবর্তনে সাড়া দেওয়াকে বেশি মূল্য দেন। এর পেছনের উপলব্ধিটি requirement নিয়েই: মানুষ অনেক সময় নিজের দরকার নিজেই জানেন না, যতক্ষণ না হাতে কাজ করে এমন একটি জিনিস পান।

System design-এ এর অর্থ দুটি। প্রথমত, NFR-এর সংখ্যাগুলো — DAU, পড়া-লেখার অনুপাত, peak factor — আসলে অনুমান; সেগুলো লিখে রাখুন, চালুর পর মাপুন, আর না মিললে নকশা হালনাগাদ করুন। দ্বিতীয়ত, evolvability-কে শুরু থেকেই requirement ধরুন: এমন নকশা, যা ভুল প্রমাণিত অনুমানের পরও সস্তায় বদলানো যায়। ইন্টারভিউতেও একই নীতি — কয়েকটি জরুরি প্রশ্ন করে যুক্তিসংগত অনুমান স্পষ্ট ঘোষণা করুন, তারপর এগিয়ে যান। অনন্তকাল প্রশ্ন করা আর একেবারে প্রশ্ন না করা, দুটোই ভুল।

মূল উপলব্ধি: requirement হলো সবচেয়ে ভালো বর্তমান অনুমান, চিরসত্য নয়। লিখুন, মাপুন, আর বদলালে নকশা ও ADR দুটোই হালনাগাদ করুন।

প্র ০২ACID-এর C আর CAP-এর C — একই শব্দ, দুই অর্থ। গুলিয়ে ফেললে বাস্তবে কী ভুল হয়?

ACID শব্দটি ১৯৮৩ সালে Theo Härder আর Andreas Reuter-এর একটি গবেষণাপত্রে আসে — Atomicity, Consistency, Isolation, Durability. এখানে consistency মানে, একটি transaction ডেটাবেসকে এক বৈধ অবস্থা থেকে আরেক বৈধ অবস্থায় নেয়। “বৈধ” কী, তা ঠিক করে application — যেমন হিসাবের খাতায় মোট debit আর মোট credit সমান থাকবে। Martin Kleppmann তাঁর বইয়ে মন্তব্য করেছেন, এই C আসলে database-এর নয়, application-এর গুণ; database atomicity আর isolation দিয়ে সাহায্য করে মাত্র।

CAP-এর ইতিহাস আলাদা। Eric Brewer ২০০০ সালে একটি বক্তৃতায় অনুমানটি দেন; ২০০২ সালে Seth Gilbert আর Nancy Lynch আনুষ্ঠানিকভাবে প্রমাণ করেন। সেই প্রমাণে C মানে atomic বা linearizable consistency: প্রতিটি পড়া সর্বশেষ সম্পন্ন লেখা দেখবে, যেন সব কপি মিলে একটিই কপি।

গুলিয়ে ফেললে চেনা ভুলগুলো ঘটে। “আমাদের database ACID, তাই consistency নিয়ে চিন্তা নেই” — অথচ asynchronous read replica থেকে পড়লে সদ্য বদলানো balance পুরোনো দেখাতে পারে; ACID ঠিকই আছে, linearizability নেই। উল্টো দিকে, একটি linearizable key-value store একাধিক key জুড়ে transaction না-ও দিতে পারে, তাই “দুই অ্যাকাউন্টের যোগফল ঠিক থাকবে” — এমন নিয়ম সে নিজে রক্ষা করবে না। Kleppmann ২০১৫ সালের একটি লেখায় database-গুলোকে “CP” বা “AP” তকমা দেওয়া বন্ধ করার আহ্বান জানান, কারণ বাস্তব সিস্টেম প্রায়ই কোনো ঘরেই পরিষ্কার পড়ে না। ভালো প্রশ্ন হলো: কোন অপারেশনে, কোন isolation level-এ, কোন কপি থেকে পড়লে ঠিক কী নিশ্চয়তা পাব? ইন্টারভিউতেও এই প্রশ্নটি নিজে থেকে তুললে বোঝা যায়, আপনি শব্দ নয়, আচরণ নিয়ে ভাবছেন।

মূল উপলব্ধি: “consistent” শব্দটি শুনলেই জিজ্ঞেস করুন — কোন অর্থে? ডেটার নিয়ম রক্ষা, কপিগুলোর তাৎক্ষণিক মিল, নাকি শেষ পর্যন্ত মিলে যাওয়া? তিনটির নকশা আর দাম আলাদা।

প্র ০৩Amazon-এর Dynamo paper-এর shopping cart: availability-কে কেন consistency-র ওপরে রাখা হয়েছিল, আর তার দাম কী?

২০০৭ সালে Amazon-এর প্রকৌশলীরা SOSP সম্মেলনে Dynamo নামের একটি key-value store-এর নকশা প্রকাশ করেন। Paper-টির কেন্দ্রীয় উদাহরণ shopping cart: ক্রেতা যেন সবসময় cart-এ জিনিস যোগ করতে পারেন — disk বিকল হোক, network টলমল করুক, এমনকি পুরো একটি data center বিপর্যয়ে পড়ুক। কারণটি ব্যবসার: “cart-এ যোগ” প্রত্যাখ্যান মানে বিক্রি হারানো আর ক্রেতার বিরক্তি।

এই requirement থেকেই নকশা: কোনো লেখা ফেরানো হবে না। যে কপি হাতের কাছে আছে, লেখা সেখানেই নেওয়া হয় (sloppy quorum আর hinted handoff — পর্ব ৪১); একই cart-এর একাধিক সংস্করণ তৈরি হলে vector clock দিয়ে চেনা হয় (পর্ব ৩৯); আর পড়ার সময় সংস্করণগুলো মেলানো হয় — cart-এর ক্ষেত্রে দুই তালিকার জিনিস একসাথে করে। Paper নিজেই দামটি স্বীকার করে: এই মেলানোয় “cart-এ যোগ” কখনো হারায় না, কিন্তু মুছে ফেলা জিনিস কখনো কখনো আবার ফিরে আসতে পারে।

দামটি গ্রহণযোগ্য, কারণ ক্ষতি ছোট: ক্রেতা ফিরে আসা জিনিসটি আবার মুছে দেন, বা checkout-এর আগে দেখে বাদ দেন। একই যুক্তি পেমেন্ট বা মজুতের হিসাবে চলে না — সেখানে পুরোনো বা দ্বিগুণ মানের দাম সরাসরি টাকা। Paper-টি আরেকটি NFR শিক্ষাও দেয়: সেখানে কর্মক্ষমতার লক্ষ্য গড় দিয়ে নয়, ৯৯.৯তম percentile দিয়ে বলা হয়েছে, যাতে প্রায় সব ক্রেতার অভিজ্ঞতা ভালো থাকে (পর্ব ০৪)। পরে Cassandra-র মতো open-source সিস্টেম এই নকশার অনেক ধারণা গ্রহণ করে।

মূল উপলব্ধি: trade-off-এর উত্তর আসে ব্যবসার requirement থেকে। একই কোম্পানিতে cart আর পেমেন্টের উত্তর আলাদা হওয়াই স্বাভাবিক — সিদ্ধান্ত হয় ডেটার প্রতিটি অংশের জন্য আলাদাভাবে।

প্র ০৪দল আর প্রতিষ্ঠানও কি নকশার constraint? Conway-র সূত্র কী বলে?

১৯৬৮ সালে Melvin Conway Datamation পত্রিকায় “How Do Committees Invent?” নামের প্রবন্ধে লেখেন: যে প্রতিষ্ঠান কোনো সিস্টেম নকশা করে, তার নকশা সেই প্রতিষ্ঠানের ভেতরের যোগাযোগের কাঠামোর প্রতিলিপি হয়ে ওঠে। Fred Brooks ১৯৭৫ সালে তাঁর বিখ্যাত বই The Mythical Man-Month-এ একে “Conway’s law” নামে জনপ্রিয় করেন। প্রচলিত একটি রসিক উদাহরণ: চারটি দল মিলে একটি compiler বানালে চার ধাপের compiler তৈরি হয়।

এর অর্থ, দল শুধু নকশা বাস্তবায়ন করে না, নকশাকে আকারও দেয়। পাঁচ জনের একটি দল যদি ২০টি microservice বানায়, প্রত্যেকের ঘাড়ে চারটি সার্ভিস: আলাদা deploy, আলাদা নজরদারি, রাত তিনটায় আলাদা বিভ্রাট। সেই দলের জন্য পরিষ্কার module-এ ভাগ করা একটি monolith প্রায়ই অনেক বেশি টেকসই। আবার বিশাল প্রতিষ্ঠানে ৫০টি দল একটি monolith-এ কাজ করলে প্রতিটি deploy-এ সবার সমন্বয় লাগে — তখন দলের সীমানা ধরে সার্ভিস ভাগ করা যুক্তিসংগত (পর্ব ৪৭)। পরে প্রকৌশলীরা উল্টো কৌশলের কথাও বলেছেন: যে architecture চান, সেই আকারে দল সাজান — একে কখনো “inverse Conway maneuver” বলা হয়।

দক্ষতাও constraint. দলের কেউ যদি একটি নতুন database কখনো production-এ না চালিয়ে থাকেন, তাহলে কাগজে তার যত সুবিধাই থাকুক, operability-র হিসাবে সেটি ঝুঁকি। তাই requirement-এর আলোচনায় বাজেট আর সময়ের পাশাপাশি দলের আকার, দক্ষতা আর রাতে সমস্যা কারা সামলাবেন — এগুলোও লিখুন। দল বড় হলে বা দক্ষতা বাড়লে এই constraint বদলায়, তখন নকশাও নতুন করে ভাবার সুযোগ আসে।

মূল উপলব্ধি: নকশা শুধু প্রযুক্তি নয়; কে বানাবে আর কে চালাবে, তারাও requirement-এর অংশ। দলের সাধ্যের বাইরের নকশা কাগজে সেরা, বাস্তবে দুর্বল।

প্র ০৫সিদ্ধান্ত লিখে রাখা এত জরুরি কেন? Chesterton-এর বেড়া থেকে ADR পর্যন্ত

১৯২৯ সালে ইংরেজ লেখক G. K. Chesterton তাঁর বই The Thing-এ একটি উপমা দেন, যা আজ “Chesterton’s fence” নামে পরিচিত। রাস্তার মাঝখানে একটি বেড়া; একজন সংস্কারক বলেন, “এর কোনো কাজ দেখছি না, সরিয়ে ফেলি।” বুদ্ধিমান জনের উত্তর: কেন বসানো হয়েছিল তা জানার আগে সরানো চলবে না; কারণ জানার পরই ঠিক করা যাবে, সরানো উচিত কি না।

সফটওয়্যারে এমন বেড়া সর্বত্র: অদ্ভুত একটি retry-নিয়ম, দেরিতে বদলানো একটি সংখ্যা, “অকারণে” আলাদা রাখা একটি database. কারণ জানা না থাকলে নতুন প্রকৌশলী “পরিষ্কার” করতে গিয়ে পুরোনো একটি বিভ্রাট আবার ডেকে আনেন। Michael Nygard ২০১১ সালে লক্ষ করেন, বড় নকশা-দলিল কেউ পড়ে না আর তা দ্রুত পুরোনো হয়ে যায়; তাই তিনি প্রস্তাব দেন ছোট, ক্রমিক, পরিবর্তনহীন ADR — কোডের সাথেই। ADR-এ Context থাকে বলে পরে বোঝা যায় সিদ্ধান্তের সময় কী জানা ছিল, আর Consequences থাকে বলে বোঝা যায় কোন অবস্থা বদলালে সিদ্ধান্তটি আবার ভাবতে হবে।

আরও একটি সূক্ষ্ম লাভ আছে। অনিশ্চয়তার মধ্যে নেওয়া একটি ভালো সিদ্ধান্তও কখনো খারাপ ফল দিতে পারে, আর খারাপ সিদ্ধান্তও ভাগ্যক্রমে ভালো ফল দিতে পারে। পরে শুধু ফল দেখে বিচার করলে আমরা ভুল শিক্ষা নিই — মনোবিজ্ঞানে একে বলে hindsight bias. লিখিত Context দেখায়, তখনকার তথ্যে সিদ্ধান্তটি যুক্তিসংগত ছিল কি না। তাই ADR দোষারোপের দলিল নয়, শেখার দলিল।

মূল উপলব্ধি: কোড বলে “কী”, ADR বলে “কেন”. “কেন” হারালে ভবিষ্যতের দল পুরোনো ভুল আবার করে, বা দরকারি বেড়া ভেঙে ফেলে।

অনুশীলন

আগে নিজে খাতায় করুন, তারপর উত্তর মেলান। সংখ্যাগুলো কাল্পনিক অনুমান।

  1. Requirement চিনুন: একটি কাল্পনিক অনলাইন বাস-টিকিট সেবার আটটি বাক্যকে FR, NFR (কোন গুণ), constraint বা assumption হিসেবে চিহ্নিত করুন। (ক) যাত্রী রুট, তারিখ আর আসন বেছে টিকিট কিনতে পারবেন। (খ) আসন বাছাইয়ের পাতা ৯৫% ক্ষেত্রে ৩০০ ms-এর মধ্যে খুলবে। (গ) একই আসন কখনো দুজনের কাছে বিক্রি হবে না। (ঘ) ঈদের আগের সপ্তাহে স্বাভাবিকের কয়েক গুণ ভিড় সামলাতে হবে। (ঙ) পেমেন্ট নিশ্চিত হলে টিকিটের তথ্য কখনো হারাবে না। (চ) দলে ৩ জন ডেভেলপার, ৬ মাসে চালু করতে হবে। (ছ) যাত্রীর শুধু নাম আর ফোন নম্বর রাখা হবে, জাতীয় পরিচয়পত্রের নম্বর নয়। (জ) বেশিরভাগ যাত্রী ধীর মোবাইল নেটওয়ার্কে অ্যাপ চালান।
    • (ক) FR — একটি কাজ; বাদ দিলে সেবাটিই থাকে না।
    • (খ) NFR, performance — p95 latency-র মাপা যায় এমন লক্ষ্য।
    • (গ) FR, সাথে consistency-র NFR — ব্যবসার নিয়ম, তাই functional; কিন্তু রক্ষা করতে আসনের ডেটায় strong consistency লাগবে, তাই দুই জায়গাতেই লিখুন।
    • (ঘ) NFR, scalability — তবে “কয়েক গুণ” মাপা যায় না; আগের বছরের metric থেকে সংখ্যায় বাঁধতে হবে।
    • (ঙ) NFR, durability — নিশ্চিত ডেটা না হারানোর প্রতিশ্রুতি।
    • (চ) Constraint — দলের আকার আর সময়।
    • (ছ) NFR, privacy — data minimisation-এর সরাসরি প্রয়োগ।
    • (জ) Assumption — প্রথম মাসের metric দিয়ে যাচাই করতে হবে; সত্য হলে NFR যোগ হবে: ছোট payload, ধীর নেটওয়ার্কেও কাজ।
  2. Peak write QPS আর আউটপুট অনুমান: একটি কাল্পনিক mobile wallet-এ ধরা যাক ৮০ লাখ DAU; প্রত্যেকে দিনে ৩টি লেনদেন (লেখা) করেন আর ১২ বার balance বা ইতিহাস দেখেন (পড়া); মাসের শুরুর ব্যস্ত সন্ধ্যায় peak factor ৬. (ক) গড় আর peak write QPS কত? (খ) অংশ ২-এর ক্যালকুলেটরে load("E: wallet", 8e6, 15, 4, 6); যোগ করলে কোন লাইন ছাপা হবে — চালানোর আগে লিখুন। (গ) Database-এর একটি primary নিরাপদে প্রতি সেকেন্ডে ১,০০০টি লেখা সামলালে সেটি কি যথেষ্ট?

    (ক) দিনে লেখা = ৮০ লাখ × ৩ = ২ কোটি ৪০ লাখ; ÷ ৮৬,৪০০ ≈ ২৭৮/সেকেন্ড গড়ে; peak-এ × ৬ ≈ ১,৬৬৭/সেকেন্ড.

    (খ) জনপ্রতি অনুরোধ ৩ + ১২ = ১৫, পড়া:লেখা = ১২:৩ = ৪:১, তাই R = ৪. গড় মোট = ৮০ লাখ × ১৫ ÷ ৮৬,৪০০ ≈ ১,৩৮৯; লেখা ≈ ১,৩৮৯ ÷ ৫ ≈ ২৭৮, পড়া ≈ ১,১১১; peak-এ পড়া ≈ ৬,৬৬৭, লেখা ≈ ১,৬৬৭:

    E: wallet                    1,111          278        6,667        1,667

    (গ) না — peak-এ ১,৬৬৭ > ১,০০০. বিকল্প: account ধরে লেখার পথ ভাগ করা (sharding, পর্ব ২৩) — এক transfer দুই ভাগ ছুঁলে distributed transaction লাগবে (পর্ব ২৫); সাময়িকভাবে বড় যন্ত্র; বা queue দিয়ে ভিড় মসৃণ করা, তবে তখন “প্রক্রিয়াধীন” অবস্থা স্পষ্ট দেখাতে হবে আর idempotency key লাগবে। লেনদেনের ইতিহাস পড়া replica বা cache সামলাতে পারে, কিন্তু যে balance দেখে টাকা পাঠানোর অনুমতি দেওয়া হয়, তা leader থেকেই পড়তে হবে (অংশ ৫)।

  3. Trade-off বেছে নিন, প্রস্তাব যাচাই করুন: সেই বাস-টিকিট সেবায় প্রস্তাব এলো: “ঈদের ভিড়ে database বাঁচাতে আসনের তালিকা আর বুকিং — দুটোই ৬০ সেকেন্ডের cache থেকে চলবে।” কোন অংশ ঠিক, কোন অংশ বিপজ্জনক? Consistency, sync বনাম async আর availability নিয়ে যুক্তিসহ সিদ্ধান্ত লিখুন।
    • তালিকা দেখানো: cache চলবে। ভুলের দাম কম — কেউ “খালি” দেখে ক্লিক করে জানবেন আসনটি সদ্য বিক্রি হয়েছে। TTL ছোট রাখুন, পাতায় লিখুন তালিকা কয়েক সেকেন্ড পুরোনো হতে পারে।
    • বুকিং: cache থেকে কখনো নয়। আসন ধরে রাখা হবে মূল উৎসে, শর্তযুক্ত লেখায় — যেমন UPDATE seats SET status = 'held' WHERE id = ? AND status = 'free'; বদলানো সারি ০ হলে আসনটি আর নেই। এটাই strong consistency; দাম — database-এ চাপ, ভিড়ে কিছু অনুরোধ ফেরানো।
    • পেমেন্ট synchronous, idempotency key-সহ (পর্ব ০৮, ৩৪); SMS আর ই-মেইল asynchronous, queue থেকে।
    • ভিড়: অপেক্ষার সারি (virtual waiting room) আর rate limiting (পর্ব ১৮, ৪৯)। “একটু পরে চেষ্টা করুন” বলা দুজনকে একই আসন বিক্রির চেয়ে অনেক ভালো — এখানে availability-র চেয়ে সঠিকতা বড়।

    সংক্ষেপে: পড়ায় consistency ছেড়ে latency কিনুন, লেখায় availability ছেড়ে consistency — আর দুটি সিদ্ধান্তই ADR-এ লিখুন।

  4. একটি ADR লিখুন: সিদ্ধান্ত — “আপলোডের সময় thumbnail synchronous-ভাবে না বানিয়ে queue দিয়ে asynchronous-ভাবে বানানো হবে।” Nygard-এর পাঁচ অংশে, requirement-এর ID-সহ, ADR লিখুন।

    একটি সম্ভাব্য উত্তর (আপনারটি ভিন্ন হতে পারে, কিন্তু পাঁচ অংশ আর মন্দ দিক থাকা চাই):

    # ADR-0007: Thumbnail তৈরি asynchronous, queue দিয়ে
    তারিখ: 2026-11-02 · সিদ্ধান্তদাতা: backend দল
    
    ## Status
    গৃহীত (accepted)
    
    ## Context
    - F1: ছবি আপলোড; N1: উৎসবের সন্ধ্যায় আপলোড গড়ের কয়েক গুণ।
    - এখন আপলোডের অনুরোধেই তিন আকারের thumbnail হয়; ভিড়ে API সার্ভারের
      CPU ভরে গিয়ে feed-ও ধীর হয় (N2 ঝুঁকিতে)।
    - বিবেচিত বিকল্প: (১) sync, বড় সার্ভার; (২) ফোনেই thumbnail;
      (৩) queue আর আলাদা worker.
    
    ## Decision
    আমরা মূল ছবি টেকসই storage-এ জমা নিশ্চিত হলে তবেই "সফল" বলব (N4),
    আর thumbnail-এর কাজ queue-তে পাঠাব; আলাদা worker বানাবে।
    ততক্ষণ অ্যাপ "প্রক্রিয়াধীন" চিহ্ন দেখাবে।
    
    ## Consequences
    + আপলোডের সাড়া দ্রুত; ভিড় queue-তে জমে, API আর feed রক্ষা পায়।
    + Worker আলাদাভাবে বাড়ানো-কমানো যায়।
    − কয়েক সেকেন্ড thumbnail থাকবে না; অ্যাপকে তা সামলাতে হবে।
    − retry-তে একই কাজ দুবার চলতে পারে, তাই worker-কে idempotent হতে হবে।
    − queue-র দৈর্ঘ্য আর ব্যর্থ কাজের (dead letter) জন্য নজরদারি লাগবে।

    লক্ষ করুন: “সফল” বলার শর্ত (durability) স্পষ্ট, বিকল্পগুলো আছে, আর দামগুলো সৎভাবে লেখা। Queue আর worker-এর নির্ভরযোগ্যতা পর্ব ৩১ আর ৩৭-এ।

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

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

ক্রমএকটি অস্পষ্ট অনুরোধ থেকে নকশার সিদ্ধান্ত পর্যন্ত ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. প্রশ্ন করে অনুরোধটি স্পষ্ট করা
  2. FR-এর তালিকা আর “এবার নয়” সীমা লেখা
  3. NFR আর load সংখ্যায় লেখা (QPS, p99, availability)
  4. Constraint আর assumption আলাদা করে লেখা
  5. বিকল্প নকশা তুলনা করে trade-off বেছে নেওয়া
  6. সিদ্ধান্ত আর তার দাম ADR-এ লিখে রাখা

একটি ছবি শেয়ারের অ্যাপের জন্য নিচের কোনটি non-functional requirement?

  • ব্যবহারকারী অন্যকে follow করতে পারবেন
  • ব্যবহারকারী ছবিতে comment লিখতে পারবেন
  • Feed লোডের ৯৯% অনুরোধ ৫০০ ms-এর মধ্যে শেষ হবে
  • ব্যবহারকারী নিজের ছবি মুছে ফেলতে পারবেন

একটি অ্যাপে দৈনিক সক্রিয় ব্যবহারকারী ২০ লাখ; প্রত্যেকে দিনে গড়ে ৫টি লেখা (write) করেন; peak factor ৩. Peak-এ প্রতি সেকেন্ডে আনুমানিক কতটি লেখা সামলাতে হবে?

  • প্রায় ৩৫
  • প্রায় ১১৬
  • প্রায় ৩৪৭
  • প্রায় ৩,৪৭২

একটি সার্ভিস ৪০ মিনিট বন্ধ ছিল; চালু হওয়ার পর দেখা গেল, আগে সফল বলে নিশ্চিত করা সব ডেটা অক্ষত আছে। কোন গুণটি ক্ষতিগ্রস্ত হয়েছে?

  • Durability
  • Availability
  • Consistency
  • Maintainability

ACID-এর “C” আর CAP-এর “C” সম্পর্কে কোন বক্তব্যটি সঠিক?

  • দুটি একই — database ACID হলে replica থেকেও সবসময় সর্বশেষ মান পড়া যায়
  • ACID-এর C মানে transaction-এর পর ডেটার নিয়ম (invariant) রক্ষা; CAP-এর C মানে linearizability — সব কপি মিলে যেন একটিই কপি
  • CAP-এর C মানে শুধু disk-এ লেখা নিশ্চিত হওয়া
  • ACID-এর C মানে network ভাগ হলেও সেবা চালু থাকা

Fan-out on write (push) নকশার একটি feed-এ একজন ব্যবহারকারীর ৫০ লাখ follower. তিনি একটি পোস্ট দিলে কী ঘটে?

  • শুধু একটি লেখা হয়; feed খোলার সময় follower-রা পড়ে নেন
  • পোস্টটি ৫০ লাখ follower-এর আগে-তৈরি feed-এ লিখতে হয় — এক মুহূর্তে বিপুল লেখা, সবার কাছে পৌঁছাতে দেরি
  • Follower-দের feed পড়া ৫০ লাখ গুণ ধীর হয়ে যায়
  • কিছুই আলাদা হয় না, follower সংখ্যার কোনো প্রভাব নেই

একটি দল ADR (Architecture Decision Record) রাখে। এক বছর আগের একটি সিদ্ধান্ত এখন বদলানো হচ্ছে। প্রচলিত চর্চা কোনটি?

  • পুরোনো ADR মুছে একই নম্বরে নতুন সিদ্ধান্ত লেখা
  • পুরোনো ADR-এর লেখা চুপচাপ সম্পাদনা করে নতুন সিদ্ধান্ত বসানো
  • নতুন ADR লেখা, আর পুরোনোটির Status-এ “superseded” লিখে নতুনটির উল্লেখ দেওয়া
  • সিদ্ধান্তটি শুধু দলের চ্যাটে জানিয়ে দেওয়া

শব্দকোষ

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

মনে রাখুন

  1. FR বলে সিস্টেম কী করবে; NFR বলে কতটা ভালোভাবে — আর NFR সবসময় মাপা যায় এমন সংখ্যায় (p99, ৯৯.৯%, QPS).
  2. Load লিখুন DAU, জনপ্রতি অনুরোধ, read:write, peak factor, ডেটার আকার আর fan-out দিয়ে; গড় QPS = DAU × অনুরোধ ÷ ৮৬,৪০০, আর নকশা করুন peak-এর জন্য।
  3. Availability ≠ reliability ≠ durability; আর ACID-এর C (ডেটার নিয়ম) আর CAP-এর C (linearizability) আলাদা জিনিস।
  4. প্রতিটি trade-off-এ লিখুন কী পেলাম, কী দাম দিলাম, কোন requirement-এর জন্য; consistency বাছুন ডেটার প্রতিটি অংশের জন্য আলাদাভাবে।
  5. MoSCoW দিয়ে অগ্রাধিকার, decision matrix দিয়ে তুলনা (weight বদলে যাচাইসহ), আর ADR দিয়ে “কেন”-এর স্থায়ী রেকর্ড।

পরের পর্বে: আজ load-কে সংখ্যায় লিখলেন। পর্ব ০৩-এ সেই সংখ্যা থেকে storage, bandwidth আর সার্ভারের হিসাব — back-of-the-envelope, ধাপে ধাপে।

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

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