এই পর্বে যা শিখবেন
- latency, throughput আর bandwidth আলাদা করে চেনা — আর বেশি throughput থাকলেও latency কেন খারাপ হতে পারে
- একটি তালিকা থেকে হাতে p50, p90, p99 বের করা (nearest-rank পদ্ধতি), আর গড় কেন বিভ্রান্ত করে
- fan-out-এ tail latency-র অঙ্ক, আর hedged request, timeout, কম fan-out ও cache দিয়ে tail ছোট করা
- Little-এর সূত্রের সহজ প্রমাণ, আর তা দিয়ে thread pool, connection pool ও queue-এর মাপ ঠিক করা
- ব্যস্ততা বাড়লে queue কেন বিস্ফোরিত হয় (M/M/1), bottleneck আর latency budget — আর ঠিকভাবে মাপার নিয়ম
১ · Latency, throughput আর bandwidth — তিনটি আলাদা প্রশ্ন
ঈদের আগের রাত। একটি অনলাইন বাস-টিকিট সেবার (কাল্পনিক) ইঞ্জিনিয়ার রিনা dashboard দেখছেন: সার্ভার প্রতি সেকেন্ডে প্রায় ২,০০০টি অনুরোধ সামলাচ্ছে, গড় response time ১২০ ms — সব সবুজ। অথচ সোশ্যাল মিডিয়ায় অভিযোগ জমছে: “পেমেন্টের পাতা ঘুরছে তো ঘুরছেই।”
কে ঠিক? দুজনেই। গড় ১২০ ms হলেও প্রতি ১০০ জনে কয়েকজন হয়তো পাঁচ সেকেন্ড অপেক্ষা করছেন — গড় সেটি লুকিয়ে রাখে। এই পর্বে শিখবেন কোন সংখ্যা দেখতে হয়, কীভাবে হিসাব করতে হয়, আর ধীরতা আসে কোথা থেকে।
“সিস্টেমটা কত দ্রুত?” — এই প্রশ্নের ভেতরে আসলে তিনটি আলাদা প্রশ্ন আছে:
| মাপ | যে প্রশ্নের উত্তর | একক | উদাহরণ |
|---|---|---|---|
| Latency | একটি অনুরোধে কত সময় লাগে? | ms, সেকেন্ড | “ব্যালান্স দেখুন” চাপার ১২০ ms পরে উত্তর |
| Throughput | প্রতি সেকেন্ডে কতগুলো কাজ শেষ হয়? | অনুরোধ/সেকেন্ড (QPS), MB/s | সার্ভার সেকেন্ডে ২,০০০টি অনুরোধ শেষ করছে |
| Bandwidth | পথ সর্বোচ্চ কতটা বইতে পারে? | bit/s (Mbps, Gbps) | সার্ভারের network card 10 Gbps |
পাটুরিয়া-দৌলতদিয়া ফেরিঘাটের কথা ভাবুন। ধরা যাক একটি ফেরিতে একবারে ৩০টি গাড়ি ওঠে — এটি bandwidth, পথের সর্বোচ্চ ক্ষমতা। ঘণ্টায় আসলে কয়টি গাড়ি পার হলো — throughput. আর আপনার গাড়ির সিরিয়ালে দাঁড়ানো থেকে ওপারে নামা পর্যন্ত সময় — latency. ফেরি যদি পুরো ভরার পরেই ছাড়ে, throughput চমৎকার; অথচ সবার আগে আসা গাড়িটির latency বিশাল — বাকি ২৯টির অপেক্ষা।
এই উদাহরণেই পর্বের প্রথম বড় শিক্ষা: বেশি throughput মানেই কম latency নয়। তিনটি সাধারণ কারণে দুটি আলাদা পথে চলে:
- Batching: অনেক কাজ জমিয়ে একসাথে করলে প্রতিটির স্থির খরচ ভাগ হয়ে যায়, throughput বাড়ে — কিন্তু প্রথম কাজটিকে দল ভরার অপেক্ষা করতে হয়। ডাকঘর দিনে একবার বস্তা পাঠালে খরচ কম, কিন্তু সকালে ফেলা চিঠিও রওনা দেয় বিকেলে।
- পুরো ব্যস্ত সার্ভার: সার্ভার ১০০% ব্যস্ত থাকলে throughput সর্বোচ্চ, কিন্তু প্রতিটি নতুন অনুরোধ লম্বা লাইনে দাঁড়ায় (অংশ ৭)।
- Bandwidth বাড়ালেও দূরত্ব কমে না: এক ট্রাক hard disk ঢাকা থেকে চট্টগ্রামে গেলে কয়েকশো terabyte একবারে পৌঁছায়, কিন্তু প্রথম byte পৌঁছাতেই কয়েক ঘণ্টা। সার্ভার যোগ করে throughput বাড়ানো সহজ; latency কমানো কঠিন, কারণ আলোর গতি আর পরপর ধাপের সংখ্যা কোড দিয়ে বদলায় না।
সংজ্ঞায় সামান্য ভিন্নতা: অনেকে latency আর response time একই অর্থে বলেন; কিছু বই — যেমন Martin Kleppmann-এর Designing Data-Intensive Applications (২০১৭) — পার্থক্য করে: response time হলো client যা দেখে, আর latency হলো অনুরোধটি সেবার অপেক্ষায় “সুপ্ত” (latent) থাকার সময়। এই কোর্সে latency মানে সাধারণত শুরু থেকে শেষের মোট সময়। নেটওয়ার্কের চার রকম delay আছে Computer Networks কোর্সের Delay, loss আর throughput পর্বে; QPS-এর আন্দাজ পর্ব ৩-এ।
২ · একটি অনুরোধের সময় কোথায় যায় — network, queue আর service
একটি কাল্পনিক mobile banking অ্যাপে ব্যবহারকারী “ব্যালান্স দেখুন” চাপলেন। উত্তর আসতে যে সময় লাগল, তাকে তিন ভাগে ভাগ করা যায়:
- Network time: অনুরোধ যাওয়া আর উত্তর ফেরা — দূরত্ব, connection আর TLS-এর handshake, হারানো packet আবার পাঠানো। Fiber-এ আলো প্রতি ১,০০০ km যেতে এক দিকে প্রায় ৫ ms নেয়; এই সীমা কোনো কোড দিয়ে কমে না, কমে শুধু সার্ভারকে ব্যবহারকারীর কাছে আনলে।
- Queueing time (queueing delay): সার্ভার ব্যস্ত থাকলে অনুরোধ লাইনে দাঁড়ায় — load balancer-এ, worker thread-এর জন্য, database connection-এর জন্য, disk-এর জন্য।
- Service time: আসল কাজ — কোড চালানো, cache আর database থেকে পড়া।
রেস্তোরাঁয় ওয়েটার অর্ডার নিয়ে রান্নাঘরে গেলেন — এটুকু network. বাবুর্চি অন্য রান্নায় ব্যস্ত, তাই অর্ডারের স্লিপ হুকে ঝুলে রইল — এটি queue. তারপর রান্না, অর্থাৎ service; আর খাবার টেবিলে আনা আবার network. ভিড়ের রাতে খাবার দেরিতে এলে সাধারণত রান্নার সময় বাড়েনি — বেড়েছে হুকে ঝোলা স্লিপের সারি।
একটি অনুরোধের সময় কোথায় যায়
Response time = network + queueing + service (assumed numbers, for illustration)
ছবির দুটি দণ্ডই মূল কথা: চাপ বাড়লে network আর service প্রায় একই থাকে, লাফিয়ে বাড়ে queue — তাই সার্ভিস ধীর হলে আগে মেপে দেখুন সময় কোন অংশে যাচ্ছে।
কিছু অনুরোধ হঠাৎ অনেক ধীর হয় garbage collection-এর বিরতি, পেছনের কাজ (backup), অন্য প্রোগ্রামের সাথে CPU বা disk ভাগাভাগি, হারানো packet বা cache miss-এর কারণে। বড় সিস্টেমে এগুলো কখনো পুরো দূর হয় না।
৩ · গড় কেন ঠকায় — percentile আর nearest-rank হিসাব
ধরুন ১০০টি অনুরোধের ৯৮টি শেষ হলো ২০ ms-এ, আর ২টি আটকে গেল ২,০০০ ms (২ সেকেন্ড)। গড় = (৯৮ × ২০ + ২ × ২,০০০) ÷ ১০০ = ৫৯.৬ ms. এই সংখ্যাটি কারও অভিজ্ঞতা নয়: ৯৮ জন পেয়েছেন ২০ ms, আর ২ জন পেয়েছেন ২ সেকেন্ড। গড় দুই দলকেই ভুলভাবে বর্ণনা করে।
তাই latency মাপা হয় percentile দিয়ে। p99 = ৩০০ ms মানে ৯৯% অনুরোধ ৩০০ ms বা তার কম সময়ে শেষ হয় — প্রতি ১০০টিতে প্রায় ১টি এর চেয়ে ধীর। প্রচলিত কয়েকটি:
- p50 (median): অর্ধেক অনুরোধ এর চেয়ে দ্রুত — “সাধারণ” অভিজ্ঞতা।
- p90, p95: প্রতি ১০ বা ২০টিতে একটি এর চেয়ে ধীর — ভিড়ের সময়ের অভিজ্ঞতা।
- p99, p99.9: প্রতি ১০০ বা ১,০০০টিতে একটি — বণ্টনের লম্বা লেজ, ইংরেজিতে tail.
প্রতি ১,০০০টিতে একটি নিয়ে মাথাব্যথা কেন? প্রথমত, একজন ব্যবহারকারী অনেক অনুরোধ করেন: একটি পাতা খুলতে ৩০টি অনুরোধ লাগলে আর প্রতিটি ১% সময় ধীর হলে, পাতাটি অন্তত একটিতে আটকে যাওয়ার সম্ভাবনা ১ − ০.৯৯৩০ ≈ ২৬% (অঙ্কটি অংশ ৪-এ)। দ্বিতীয়ত, ধীর অনুরোধ প্রায়ই সবচেয়ে মূল্যবান গ্রাহকের, যাঁর ডেটা বেশি; তাই Amazon-এর Dynamo paper (২০০৭) অনুযায়ী তাদের service-এর প্রতিশ্রুতি বা SLA মাপা হতো 99.9th percentile-এ (ভাবনার প্রশ্ন ১)।
Nearest-rank পদ্ধতিতে N-টি মাপ থেকে p-তম percentile:
- সব মাপ ছোট থেকে বড় ক্রমে সাজান।
- rank = ⌈p/100 × N⌉ হিসাব করুন (⌈ ⌉ মানে ওপরের দিকের পূর্ণসংখ্যা, যেমন ⌈১৯.৮⌉ = ২০)।
- সাজানো তালিকায় সেই rank-এর মানটিই উত্তর।
এটি একমাত্র সংজ্ঞা নয়: অনেক টুল পাশের দুই মানের মাঝে interpolation করে (Hyndman ও Fan, ১৯৯৬, এমন নয়টি সংজ্ঞা তুলনা করেছেন) — তাই রিপোর্টে পদ্ধতি আর নমুনা-সংখ্যা লিখুন।
নিচের stepper-এ ২০টি মাপ থেকে হাতে গড়, p50, p90 আর p99 বের করা হলো; প্রতিটি ঘরের নিচে লেখা তার rank.
| গড় (mean) | |
| p50 · rank 10 | |
| p90 · rank 18 | |
| p99 · rank 20 |
- ২০টি মাপ, যে ক্রমে এসেছে। একটি API-র ২০টি response time, ms-এ। বেশিরভাগ ১৫ থেকে ৩০-এর মধ্যে, কিন্তু দুটি অনেক বড় — ২৪০ আর ৯৬০।
- ছোট থেকে বড় সাজানো। প্রতিটি মানের নিচে তার rank: সবচেয়ে ছোট ১৫ হলো #১, সবচেয়ে বড় ৯৬০ হলো #২০। প্রায় সব পদ্ধতির প্রথম ধাপ এটাই।
- গড় = ১,৫৯৬ ÷ ২০ = ৭৯.৮ ms. লাল দাগের দুটি মানই শুধু গড়ের চেয়ে বড় — বাকি ১৮টি (৯০%) গড়ের চেয়ে দ্রুত! একটিমাত্র ৯৬০ ms গড়কে টেনে তুলেছে।
- p50: rank = ⌈০.৫০ × ২০⌉ = ১০. দশম মান ২০ ms — এটিই median: অর্ধেক অনুরোধ ২০ ms বা তার কম সময়ে শেষ।
- p90: rank = ⌈০.৯০ × ২০⌉ = ১৮. ১৮তম মান ৪৫ ms. খেয়াল করুন, গড় (৭৯.৮) p90-এর চেয়েও বড় — tail কতটা ভারী, তার প্রমাণ।
- p99: rank = ⌈০.৯৯ × ২০⌉ = ⌈১৯.৮⌉ = ২০. অর্থাৎ সবচেয়ে বড়টি — ৯৬০ ms; ২০টি নমুনায় p99 আর সর্বোচ্চ মান একই। NumPy-র ডিফল্ট পদ্ধতি (রৈখিক interpolation) একই তালিকায় দেয় p99 = ৮২৩.২ আর p90 = ৬৪.৫ ms — ছোট নমুনায় পদ্ধতিভেদে এত পার্থক্য।
সত্যিকারের ছবি পেতে লাখো মাপ লাগে। নিচের প্রোগ্রাম একটি নির্দিষ্ট seed থেকে ১,০০,০০০টি কৃত্রিম latency বানায়, তিনটি পথের মিশ্রণে: ৯৬.৫% স্বাভাবিক (median ২০ ms), ৩% ধীর মুহূর্তে — queue-র ভিড় বা ব্যস্ত disk (median ১২০ ms), আর ০.৫% খুব ধীর — GC-র বিরতি বা হারানো packet (median ৮০০ ms). প্রতিটি পথের সময় আসে lognormal বণ্টন থেকে, যার ডান দিকে লম্বা লেজ — বাস্তব latency-র খুব পরিচিত আকার।
// 100,000 simulated request latencies with a long tail; nearest-rank percentiles.
function rng(seed) { // mulberry32: small seeded PRNG
return () => {
seed = (seed + 0x6D2B79F5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const rand = rng(42);
const normal = () => Math.sqrt(-2 * Math.log(1 - rand())) * Math.cos(2 * Math.PI * rand());
const logn = (median, spread) => median * Math.exp(spread * normal());
function latency() { // one request, in ms
const u = rand();
if (u < 0.005) return logn(800, 0.4); // 0.5%: GC pause, lost packet + retry
if (u < 0.035) return logn(120, 0.5); // 3%: queue spike, busy disk
return logn(20, 0.3); // 96.5%: the normal fast path
}
const n = 100000;
const xs = Float64Array.from({ length: n }, latency).sort(); // ascending
const mean = xs.reduce((s, x) => s + x, 0) / n;
const pct = (q) => xs[Math.ceil(q * n) - 1]; // nearest-rank: value at rank ceil(q*n)
console.log("samples: " + n);
console.log("mean = " + mean.toFixed(1) + " ms");
for (const [name, q] of [["p50", 0.5], ["p90", 0.9], ["p99", 0.99], ["p99.9", 0.999]])
console.log(name.padEnd(6) + " = " + pct(q).toFixed(1) + " ms");
console.log("max = " + xs[n - 1].toFixed(1) + " ms");
const slower = xs.filter((x) => x > mean).length;
console.log("requests slower than the mean: " + (100 * slower / n).toFixed(1) + "%");আউটপুট:
samples: 100000
mean = 28.9 ms
p50 = 20.3 ms
p90 = 31.4 ms
p99 = 199.7 ms
p99.9 = 1115.0 ms
max = 2452.5 ms
requests slower than the mean: 14.4%
গড় ২৮.৯ ms দেখে মনে হয় সব ঠিক আছে — p50 (২০.৩) আর p90 (৩১.৪)-এর মাঝামাঝি। কিন্তু p99 ১৯৯.৭ ms, গড়ের প্রায় ৭ গুণ; আর p99.9 এক সেকেন্ড ছাড়িয়ে ১,১১৫ ms — গড়ের প্রায় ৩৯ গুণ। শুধু গড় দেখলে জানতেই পারতেন না যে প্রতি হাজারে একজন এক সেকেন্ডের বেশি অপেক্ষা করছেন। আর মাত্র ১৪.৪% অনুরোধ গড়ের চেয়ে ধীর — গড় “মাঝের” মান নয়। u < 0.005-কে u < 0.02 করলে গড় হয় ৩৯.৭ ms, কিন্তু p99 লাফিয়ে ৮০৫.২ ms.
৪ · Tail latency আর fan-out — সবচেয়ে ধীরজনই সময় ঠিক করে
বড় সিস্টেমে একটি অনুরোধ প্রায়ই একা চলে না: search-এর প্রশ্ন একসাথে যায় ডেটার প্রতিটি ভাগে বা shard-এ, হোম পাতা বানাতে একসাথে ডাকা হয় প্রোফাইল, অফার আর সুপারিশের সার্ভিস। একটি অনুরোধ থেকে এভাবে অনেক অনুরোধ ছড়িয়ে পড়াকে বলে fan-out. সবার উত্তর মিলিয়ে তবেই ফল দিলে মোট সময় = সবচেয়ে ধীর উত্তরের সময়। Latency বণ্টনের এই ধীর প্রান্তের নাম tail latency.
পিকনিকের বাস ছাড়বে সবাই এলে। একজন দেরি করলে পুরো বাস দাঁড়িয়ে থাকে। যাত্রী পাঁচজন হলে কারও দেরি হওয়া মাঝে মাঝে ঘটে; পঞ্চাশজন হলে প্রায় প্রতিবারই কেউ না কেউ দেরি করেন — যদিও আলাদাভাবে প্রত্যেকে বেশ সময়নিষ্ঠ।
| সময় t | |
| উত্তর হাতে | |
| ব্যবহারকারী |
- t = ০: একটি অনুরোধ, পাঁচটি shard. একটি অনলাইন বাজারের (কাল্পনিক) search অনুরোধ এল; পণ্যের তালিকা পাঁচ ভাগে রাখা, তাই aggregator একসাথে পাঁচটি shard-কেই জিজ্ঞেস করে। সব উত্তর মিলিয়ে তবেই ফল।
- t = ৯ ms: S3 উত্তর দিল। সবচেয়ে দ্রুত shard শেষ, কিন্তু একটি উত্তরে ফল সাজানো যায় না।
- t = ১৫ ms: চারটি উত্তর হাতে। S1, S4, S2-ও শেষ — ৮০% উত্তর এসে গেছে, তবু ব্যবহারকারী কিছুই দেখেননি। S5-এ এখন garbage collection চলছে।
- t = ১৮০ ms: শেষ উত্তর, তারপর ফল। মোট সময় = max(১২, ১৫, ৯, ১৪, ১৮০) = ১৮০ ms; বাকি চারটির গড় ১২.৫ ms হলেও কিছু যায় আসে না।
- এমন কত ঘন ঘন হবে? ধরুন প্রতিটি shard স্বাধীনভাবে ১% সময় এমন ধীর। পাঁচটির অন্তত একটি ধীর হওয়ার সম্ভাবনা ১ − ০.৯৯৫ ≈ ৪.৯%; shard ১০০টি হলে প্রায় ৬৩.৪% — তখন ধীর হওয়াটাই স্বাভাবিক ঘটনা।
- Hedged request: ২০ ms-এ একটি কপি। ধরা যাক ২০ ms হলো shard-গুলোর p95; তখনও S5 চুপ। Aggregator একই প্রশ্ন পাঠাল S5-এর ডেটার কপি রাখা সার্ভার S5′-কে, যে ১৩ ms-এ উত্তর দিল — t = ৩৩ ms. মোট সময় ১৮০ থেকে ৩৩ ms; দাম একটি বাড়তি অনুরোধ, আর S5-এর অনুরোধটি বাতিল।
ধরুন প্রতিটি backend স্বাধীনভাবে p সম্ভাবনায় ধীর। স্বাধীন ঘটনার সম্ভাবনা গুণ হয়, তাই N-টির সবগুলো দ্রুত হওয়ার সম্ভাবনা (1 − p)N. তাহলে
N × p ছোট হলে এটি প্রায় N × p — fan-out যত গুণ, ধীর হওয়ার সুযোগও মোটামুটি তত গুণ।
| Backend-এর সংখ্যা N | প্রতিটি ১% সময় ধীর | প্রতিটি ০.১% সময় ধীর |
|---|---|---|
| ১ | ১% | ০.১% |
| ১০ | ৯.৬% | ১.০% |
| ১০০ | ৬৩.৪% | ৯.৫% |
হিসাবটি বিখ্যাত হয় Google-এর Jeffrey Dean ও Luiz André Barroso-র লেখা “The Tail at Scale” (Communications of the ACM, ২০১৩) থেকে। তাঁদের উদাহরণ: একটি সার্ভারের p99 এক সেকেন্ড হলে, আর একটি অনুরোধকে এমন ১০০টি সার্ভারের উত্তরের অপেক্ষা করতে হলে, প্রায় ৬৩% অনুরোধ এক সেকেন্ডের বেশি সময় নেবে। যে ধীরতা একটি সার্ভারে “বিরল”, fan-out-এ সেটিই সাধারণ।
উল্টো দিকের প্রশ্ন: পুরো অনুরোধের p99 ঠিক রাখতে প্রতিটি backend-কে কতটা ভালো হতে হবে? (1 − p)N = ০.৯৯ হলে প্রতিটির দ্রুত হওয়ার সম্ভাবনা ০.৯৯1/N; N = ১০০-এ সেটি প্রায় ০.৯৯৯৯ — অর্থাৎ backend-এর p99.99-ই ঠিক করে পুরো অনুরোধের p99. নিচের প্রোগ্রাম দুটোই হিসাব করে, আর simulation দিয়ে সূত্রটি যাচাই করে।
// Fan-out: one user request waits for ALL N backends (parallel calls).
// Each backend is "slow" with probability p, independently of the others.
// P(user request sees at least one slow reply) = 1 - (1 - p)^N
const slowAny = (p, N) => 1 - Math.pow(1 - p, N);
const pc = (x, d) => (100 * x).toFixed(d) + "%";
console.log("N p=1% p=0.1% each backend needs");
for (const N of [1, 2, 5, 10, 20, 50, 100, 200]) {
const need = Math.pow(0.99, 1 / N); // for the whole request's p99
console.log(String(N).padEnd(6) + pc(slowAny(0.01, N), 1).padEnd(8) +
pc(slowAny(0.001, N), 2).padEnd(9) + "p" + (100 * need).toFixed(3));
}
// Check the formula by simulation (seeded, so the same every run)
function rng(seed) { // mulberry32
return () => {
seed = (seed + 0x6D2B79F5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const rand = rng(2013), users = 20000, N = 100, p = 0.01;
let hit = 0;
for (let u = 0; u < users; u++) {
let slow = false;
for (let b = 0; b < N; b++) if (rand() < p) slow = true;
if (slow) hit++;
}
console.log("simulated: N = 100, p = 1% -> " + pc(hit / users, 1) +
" of " + users + " user requests slow");আউটপুট:
N p=1% p=0.1% each backend needs
1 1.0% 0.10% p99.000
2 2.0% 0.20% p99.499
5 4.9% 0.50% p99.799
10 9.6% 1.00% p99.900
20 18.2% 1.98% p99.950
50 39.5% 4.88% p99.980
100 63.4% 9.52% p99.990
200 86.6% 18.14% p99.995
simulated: N = 100, p = 1% -> 62.9% of 20000 user requests slow
N = ১০ আর ১০০-এর সারি ওপরের টেবিলের সাথে মেলে। শেষ কলাম বলে, ১০০টি backend থাকলে প্রত্যেককে p99.990 পর্যন্ত দ্রুত থাকতে হবে। Simulation পেয়েছে ৬২.৯% — সূত্রের ৬৩.৪%-এর কাছেই; পার্থক্যটুকু নমুনার স্বাভাবিক ওঠানামা। সূত্রের বড় অনুমান স্বাধীনতা: বাস্তবে ধীরতা প্রায়ই একসাথে আসে (একই ব্যস্ত switch, একই সময়ে deploy) — তবু প্রথম আন্দাজ হিসেবে এটিই কাজের।
৫ · Tail ছোট করার কৌশল — hedged request, timeout, কম fan-out আর cache
সব ধীরতা দূর করা যায় না, কিন্তু নকশা এমন করা যায় যাতে কিছু অংশ ধীর হলেও ব্যবহারকারী কম টের পান — Dean ও Barroso-র ভাষায় tail-tolerant নকশা। প্রধান চারটি কৌশল:
⏱️ Hedged request
উত্তর p95-এর মধ্যে না এলে একই অনুরোধ আরেকটি replica-তে পাঠান; যে আগে উত্তর দেয় সেটি নিন, অন্যটি বাতিল করুন। প্রায় ৫% বাড়তি অনুরোধে tail অনেক ছোট হয়।
শুধু নিরাপদে পুনরাবৃত্তিযোগ্য অনুরোধে⌛ Timeout আর deadline
অসীম অপেক্ষা নয়: সময় ফুরালে আংশিক ফল বা নিরাপদ বিকল্প উত্তর দিন।
বিস্তারিত: পর্ব ৪৮✂️ কম fan-out
শুধু প্রাসঙ্গিক shard-কে জিজ্ঞেস করুন, ভাগের সংখ্যা কমান, দরকারে বেশিরভাগ shard-এর উত্তরেই “যথেষ্ট ভালো” ফল দেখান।
sharding: পর্ব ২৩🗄️ Cache
জনপ্রিয় প্রশ্নের তৈরি উত্তর cache-এ থাকলে fan-out-ই লাগে না। যত কম অনুরোধ ধীর অংশ পর্যন্ত যায়, tail তত ছোট।
বিস্তারিত: পর্ব ২৮–৩০খুব ছোট timeout দিলে স্বাভাবিক ধীর উত্তরও ব্যর্থ ধরা হয় আর retry বাড়ে; খুব বড় দিলে thread আর connection আটকে থাকে। একটি ভালো শুরু: নিচের সার্ভিসের p99.9-এর একটু ওপরে, কিন্তু পুরো অনুরোধের বাজেটের ভেতরে (অংশ ৮)। সবচেয়ে কাজের অভ্যাস deadline propagation — “আর ১৮০ ms বাকি” তথ্যটি প্রতিটি call-এর সাথে নিচে পাঠানো, যাতে সময় ফুরালে নিচের সার্ভিসগুলোও অকারণ খাটুনি থামায়।
Hedging কতটা কাজ করে, নিচের simulation-এ দেখুন। এখানে অংশ ৩-এর একই latency মডেল; একটি call তার p95-এর মধ্যে উত্তর না দিলে দ্বিতীয় replica-তে কপি যায়, আর যে উত্তর আগে আসে সেটিই নেওয়া হয়।
// Hedged request: if no reply after d ms, send a copy to a second replica and
// use whichever reply comes first. Same long-tail latency model as before.
function rng(seed) { // mulberry32
return () => {
seed = (seed + 0x6D2B79F5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const rand = rng(42);
const normal = () => Math.sqrt(-2 * Math.log(1 - rand())) * Math.cos(2 * Math.PI * rand());
const logn = (median, spread) => median * Math.exp(spread * normal());
function latency() { // one call to one replica, in ms
const u = rand();
if (u < 0.005) return logn(800, 0.4); // 0.5%: GC pause, lost packet + retry
if (u < 0.035) return logn(120, 0.5); // 3%: queue spike, busy disk
return logn(20, 0.3); // 96.5%: the normal fast path
}
const n = 100000;
const plain = Float64Array.from({ length: n }, latency).sort();
const pct = (xs, q) => xs[Math.ceil(q * n) - 1].toFixed(1).padStart(8);
const d = plain[Math.ceil(0.95 * n) - 1]; // hedge delay = p95 of a single call
let extra = 0;
const hedged = Float64Array.from({ length: n }, () => {
const first = latency();
if (first <= d) return first; // answered in time: no copy sent
extra++;
return Math.min(first, d + latency()); // copy sent at time d
}).sort();
console.log("hedge after d = " + d.toFixed(1) + " ms (p95 of one call)");
console.log(" p50 p99 p99.9 (ms)");
console.log("plain " + pct(plain, 0.5) + " " + pct(plain, 0.99) + " " + pct(plain, 0.999));
console.log("hedged " + pct(hedged, 0.5) + " " + pct(hedged, 0.99) + " " + pct(hedged, 0.999));
console.log("extra requests sent: " + (100 * extra / n).toFixed(1) + "%");আউটপুট:
hedge after d = 38.3 ms (p95 of one call)
p50 p99 p99.9 (ms)
plain 20.3 199.7 1115.0
hedged 20.3 61.7 82.7
extra requests sent: 4.9%
p50 বদলায়নি — ৯৫% অনুরোধ কপি ছাড়াই শেষ। কিন্তু p99 ১৯৯.৭ থেকে ৬১.৭ ms, p99.9 ১,১১৫ থেকে ৮২.৭ ms; দাম মাত্র ৪.৯% বাড়তি অনুরোধ (কপি রওনা দেয় d = ৩৮.৩ ms-এ, তাই নতুন p99 প্রায় ৩৮ ms আর একটি স্বাভাবিক call-এর যোগফল)। সতর্কতা: দুই replica-র ধীরতা এখানে স্বাধীন ধরা — একই কারণে দুটোই ধীর হলে লাভ কম; বাড়তি ৫% load-এর প্রভাব simulation ধরেনি; আর কপি পাঠানো নিরাপদ শুধু idempotent অনুরোধে — টাকা পাঠানোর অনুরোধ দুবার কার্যকর হলে বিপদ।
বাড়তি অনুরোধ বিপদও আনতে পারে। সিস্টেম অতিরিক্ত চাপে ধীর হলে hedge আর retry আরও চাপ যোগ করে — ধীরতা বাড়ে, আরও retry, শেষে পুরো সেবা বসে যায়; একে বলে retry storm. তাই বাড়তি অনুরোধের সীমা রাখুন (যেমন মোটের ৫%), হেরে যাওয়া কপি বাতিল করুন, চাপ বেশি হলে hedge বন্ধ রাখুন। চাপ সামলানো আছে পর্ব ১৮-এ (Overload সামলানো), আর retry-backoff পর্ব ৪৮-এ।
৬ · Little-এর সূত্র — L = λW
এবার queue-র সবচেয়ে কাজের সূত্রটি। যেকোনো স্থিতিশীল সিস্টেমে — একটি ব্যাংকের শাখা, একটি web server, একটি database connection pool — তিনটি গড়ের মধ্যে একটি সরল সম্পর্ক থাকে। একে বলে Little-এর সূত্র:
- L — গড়ে একসাথে ভেতরে কতগুলো (অনুরোধ, গ্রাহক, message)
- λ (lambda) — গড় আগমনের হার; স্থিতিশীল অবস্থায় যত আসে তত বেরোয়, তাই এটিই throughput
- W — প্রতিটির ভেতরে থাকার গড় সময়, অর্থাৎ latency
একটি ব্যাংকের শাখায় ঘণ্টায় গড়ে ৩০ জন গ্রাহক আসেন, আর প্রত্যেকে টোকেন নেওয়া থেকে বেরিয়ে যাওয়া পর্যন্ত গড়ে ২০ মিনিট থাকেন। তাহলে যেকোনো মুহূর্তে ভেতরে গড়ে L = ৩০ × (২০ ÷ ৬০) = ১০ জন। বসার চেয়ার ১০টির কম হলে কাউকে দাঁড়িয়ে থাকতে হবে — capacity planning ঠিক এটাই।
১৯৬১ সালে John D. C. Little Operations Research জার্নালে সূত্রটির সাধারণ প্রমাণ প্রকাশ করেন: বণ্টন বা সেবার ক্রম যা-ই হোক, স্থিতিশীল সিস্টেমে দীর্ঘ সময়ের গড়ে সূত্রটি খাটে। মূল ভাবনা — একই ক্ষেত্রফল দুইভাবে গোনা — নিচে চারজন গ্রাহক দিয়ে দেখুন।
| A (ক্ষেত্রফল) | |
| L = A/T | |
| W = A/n | |
| λ = n/T | |
| λ × W |
- চারজন গ্রাহক, ১০ মিনিটের জানালা। A থাকেন ০ থেকে ৪ মিনিট, B ১ থেকে ৩, C ২ থেকে ৭, D ৫ থেকে ৮। দণ্ডের দৈর্ঘ্য = ভেতরে থাকার সময়।
- N(t) — প্রতি মুহূর্তে ভেতরে কতজন। যেকোনো সময়ে যতগুলো দণ্ড সেই সময়রেখা ছুঁয়েছে, N(t) ততো; ২ থেকে ৩ মিনিটে তিনজন, তাই সিঁড়ি সেখানে সবচেয়ে উঁচু।
- ক্ষেত্রফল কলাম ধরে গোনা। প্রতি মিনিটের N(t): ১ + ২ + ৩ + ২ + ১ + ২ + ২ + ১ = ১৪ “গ্রাহক-মিনিট”; তাই গড়ে ভেতরে L = ১৪ ÷ ১০ = ১.৪ জন।
- একই ক্ষেত্রফল সারি ধরে গোনা। প্রতিটি দণ্ড ক্ষেত্রফলে ঠিক তার দৈর্ঘ্যের সমান অংশ যোগ করে: ৪ + ২ + ৫ + ৩ = ১৪; তাই গড় সময় W = ১৪ ÷ ৪ = ৩.৫ মিনিট।
- দুই হিসাব মেলানো। λ = ৪ জন ÷ ১০ মিনিট = মিনিটে ০.৪ জন; λ × W = ০.৪ × ৩.৫ = ১.৪ — ঠিক L-এর সমান। দুটোই একই ক্ষেত্রফল থেকে এসেছে; কে কখন এলেন বা কে আগে সেবা পেলেন, তার কোনো ভূমিকা নেই।
T সময়ের একটি জানালায় n জন এলেন। N(t)-এর নিচের ক্ষেত্রফল A হলো সবার ভেতরে থাকার সময়ের যোগফল (একই ক্ষেত্রফল, কলাম আর সারি ধরে গোনা)। সংজ্ঞা অনুযায়ী L = A/T, W = A/n আর λ = n/T. তাই L = A/T = (n/T) × (A/n) = λW. জানালার শুরু বা শেষে যাঁরা আংশিক ভেতরে, তাঁদের জন্য সামান্য গরমিল হয়; সিস্টেম স্থিতিশীল হলে T বাড়ার সাথে সেই গরমিল শূন্যের দিকে যায়।
সূত্রটির শক্তি হলো, তিনটির যেকোনো দুটি জানলে তৃতীয়টি পাওয়া যায় — কোনো বণ্টন না জেনেই। System design-এ চারটি প্রচলিত প্রয়োগ:
| প্রশ্ন | জানা আছে | হিসাব | ফল |
|---|---|---|---|
| একসাথে কতগুলো অনুরোধ চলছে (concurrency)? | λ = সেকেন্ডে ২,০০০; W = ১৫০ ms | ২,০০০ × ০.১৫ | গড়ে ৩০০টি; অনুরোধ-প্রতি একটি thread হলে অন্তত ৩০০টি thread |
| Database connection pool কত বড়? | সেকেন্ডে ১,২০০ query; প্রতিটি connection ধরে রাখে ৫ ms | ১,২০০ × ০.০০৫ | গড়ে মাত্র ৬টি connection ব্যস্ত; ১২–২০-এর pool যথেষ্ট, ২০০ অপচয় |
| Queue-তে একটি message কত সময় অপেক্ষা করবে? | L = ৩০,০০০ message জমা; λ = সেকেন্ডে ১,৫০০ | W = L ÷ λ | প্রায় ২০ সেকেন্ড |
| সর্বোচ্চ throughput কত? | ২০০টি worker thread (thread pool); প্রতিটি অনুরোধ ৫০ ms | λ = L ÷ W = ২০০ ÷ ০.০৫ | সেকেন্ডে ৪,০০০; W ১০০ ms হলে নেমে ২,০০০ |
শেষ সারিটির শিক্ষা: concurrency-র সীমা থাকলে throughput ≤ concurrency ÷ latency. Database ধীর হয়ে W দ্বিগুণ হলে একই thread-সংখ্যায় throughput অর্ধেক, আর বাকি অনুরোধ জমে queue লম্বা হয় — একটি ধীর dependency এভাবেই পুরো সার্ভিস বসিয়ে দেয়।
সূত্রটি গড়ের কথা বলে: pool-এর মাপ শুধু গড় দিয়ে ঠিক করলে হঠাৎ ভিড়ে টান পড়বে, তাই বাড়তি জায়গা বা headroom রাখুন। আর তিনটি সংখ্যা একই সীমানায় মাপুন — λ load balancer-এ আর W app server-এর ভেতরে মাপলে মাঝের queue বাদ পড়ে।
৭ · Utilisation আর queue — ব্যস্ততা বাড়লে অপেক্ষা কেন বিস্ফোরিত হয়
একটি সার্ভার গড়ে S সময়ে একটি অনুরোধ শেষ করে; তাহলে সে সেকেন্ডে সর্বোচ্চ μ = 1/S টি অনুরোধ শেষ করতে পারে — μ-কে বলে service rate. অনুরোধ আসে সেকেন্ডে λ-টি। সার্ভার মোট সময়ের যত ভাগ ব্যস্ত থাকে, তা-ই তার utilisation:
যেমন S = ১০ ms হলে μ = সেকেন্ডে ১০০; সেকেন্ডে ৮০টি অনুরোধ এলে ρ = ০.৮, অর্থাৎ সার্ভার ৮০% সময় ব্যস্ত।
গড়ে সামলানো গেলেও অপেক্ষা কেন? কারণ অনুরোধ সমান তালে আসে না — কখনো পরপর পাঁচটি, কখনো কিছুক্ষণ একটিও না। ভিড় জমলে তা সরে কেবল বাড়তি ক্ষমতা μ − λ দিয়ে। ρ যত ১-এর কাছে যায়, বাড়তি ক্ষমতা তত ছোট, আর ছোট একটি ভিড়ও তত দীর্ঘ সময় টিকে থাকে। নিচে একটি বাস কাউন্টারে দেখুন।
| আগমন λ (জন/মিনিট) | |
| ρ = λ ÷ μ | |
| গড় সময় W = 1/(μ − λ) | |
| গড়ে ভেতরে L = ρ/(1 − ρ) | |
| ২০ জনের হঠাৎ ভিড় সরতে |
- মিনিটে ৫ জন, ρ = ০.৫। কর্মী মিনিটে ১০ জনকে টিকিট দিতে পারেন (একজনে গড়ে ৬ সেকেন্ড), তাই অর্ধেক সময় অলস। গড়ে ১২ সেকেন্ডে কাজ শেষ — ৬ সেকেন্ড টিকিটে, গড়ে ৬ সেকেন্ড লাইনে। হঠাৎ ২০ জনের দল এলে বাড়তি ক্ষমতা μ − λ = মিনিটে ৫ জন দিয়ে ৪ মিনিটে ভিড় সরে।
- মিনিটে ৮ জন, ρ = ০.৮। চাপ ৬০% বাড়ল, গড় সময় ১২ থেকে ৩০ সেকেন্ড — আড়াই গুণ; গড়ে ৪ জন ভেতরে। বাড়তি ক্ষমতা মিনিটে মাত্র ২ জন, তাই ২০ জনের ভিড় সরতে ১০ মিনিট।
- মিনিটে ৯ জন, ρ = ০.৯। গড় সময় ১ মিনিট — service time-এর ১০ গুণ; গড়ে ৯ জন ভেতরে। কর্মীর অলস ১০% সময়ই ভিড় সরানোর একমাত্র উপায়: ২০ জন সরতে ২০ মিনিট।
- মিনিটে ৯.৫ জন, ρ = ০.৯৫। চাপ মাত্র ৫.৬% বাড়ল, অথচ গড় সময় দ্বিগুণ — ২ মিনিট; গড়ে ১৯ জন। বক্ররেখা এখানে প্রায় খাড়া।
- মিনিটে ৯.৯ জন, ρ = ০.৯৯। গড় সময় ১০ মিনিট — service time-এর ১০০ গুণ, গড়ে ৯৯ জন! খাতায় কাউন্টার “৯৯% দক্ষ”, অথচ যাত্রীর অভিজ্ঞতা ভয়াবহ।
- মিনিটে ১০.৫ জন — ক্ষমতার বেশি। প্রতি মিনিটে গড়ে আধা জন জমে, ঘণ্টায় প্রায় ৩০ জন; কোনো স্থির গড় নেই, লাইন বাড়তেই থাকে। সার্ভারে এর মানে memory ভরা আর একের পর এক timeout — সামলানোর উপায় (load shedding, backpressure) পর্ব ১৮-এ।
M/M/1 — সবচেয়ে সরল queue-র সূত্র
Stepper-এর সংখ্যাগুলো এসেছে M/M/1 মডেল থেকে (David Kendall-এর ১৯৫৩ সালের নামকরণে প্রথম M = স্মৃতিহীন এলোমেলো আগমন, দ্বিতীয় M = এলোমেলো service, 1 = একজন সেবাদাতা)। অনুমানগুলো: আগমন এলোমেলো আর একে অন্যের ওপর নির্ভরহীন (Poisson আগমন), service time-ও এলোমেলো (exponential বণ্টন), যে আগে আসে সে আগে সেবা পায়, আর লাইনের দৈর্ঘ্যের সীমা নেই। ফল:
L = ρ/(1 − ρ)
- একটি নতুন অনুরোধ এসে গড়ে L-টি অনুরোধ ভেতরে দেখে। (Poisson আগমনের একটি গুণ: আগত অনুরোধ সময়ের গড় অবস্থাই দেখে — সংক্ষেপে PASTA, Poisson Arrivals See Time Averages.)
- Exponential service “স্মৃতিহীন”: যে অনুরোধ এখন সেবা পাচ্ছে, তারও বাকি সময় গড়ে S. তাই আগের সবার জন্য গড় অপেক্ষা L × S, তারপর নিজের S: W = S + L·S.
- Little-এর সূত্রে L = λW. বসালে W = S + λS·W = S + ρW, অর্থাৎ W(1 − ρ) = S, তাই W = S/(1 − ρ) = 1/(μ − λ).
S = ১০ ms ধরে টেবিলটি দেখুন। শেষ কলাম p99: M/M/1-এ response time-এর বণ্টন exponential, তাই p99 = ln(100) × W ≈ ৪.৬ × W.
| ρ (ব্যস্ততা) | গড় সময় W | গড়ে ভেতরে L | p99 (≈ ৪.৬ × W) |
|---|---|---|---|
| ০.৫ | ২০ ms | ১ | ৯২ ms |
| ০.৬ | ২৫ ms | ১.৫ | ১১৫ ms |
| ০.৭ | ৩৩.৩ ms | ২.৩৩ | ১৫৪ ms |
| ০.৮ | ৫০ ms | ৪ | ২৩০ ms |
| ০.৯ | ১০০ ms | ৯ | ৪৬১ ms |
| ০.৯৫ | ২০০ ms | ১৯ | ৯২১ ms |
| ০.৯৯ | ১,০০০ ms | ৯৯ | ৪,৬০৫ ms |
তত্ত্ব কি সত্যিই খাটে? নিচের প্রোগ্রাম একটি M/M/1 queue চালায় — প্রতিটি অনুরোধের আগমন, অপেক্ষা আর বিদায় ধরে ধরে (discrete-event simulation), নির্দিষ্ট seed-এ। Little-এর সূত্রও এখানে যাচাই হয়: λ গোনা হয় দরজায়, W প্রতিটি অনুরোধের stopwatch-এ, আর L আলাদাভাবে — ১,০০,০০০টি সমান ফাঁকের মুহূর্তে ভেতরে কতজন, গুনে।
// M/M/1: one server, first-come-first-served, random (Poisson) arrivals and
// random (exponential) service times. Mean service time S = 10 ms.
function rng(seed) { // mulberry32: seeded, repeatable
return () => {
seed = (seed + 0x6D2B79F5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const S = 10, mu = 1 / S, N = 200000; // mu = 0.1 requests per ms
console.log("rho W sim 1/(mu-lam) L counted lam*W");
for (const rho of [0.5, 0.8, 0.9, 0.95]) {
const rand = rng(2026), lam = rho * mu;
const gap = (rate) => -Math.log(1 - rand()) / rate; // exponential
const arr = new Float64Array(N), dep = new Float64Array(N);
let t = 0, free = 0; // free = when the server is next idle
for (let i = 0; i < N; i++) { // event by event: arrive, wait, leave
t += gap(lam); arr[i] = t;
free = Math.max(free, t) + gap(mu); dep[i] = free;
}
let sumW = 0;
for (let i = 0; i < N; i++) sumW += dep[i] - arr[i];
const W = sumW / N, lamHat = N / arr[N - 1]; // measured mean time, arrival rate
let a = 0, d = 0, inside = 0, K = 100000; // head count: how many are inside
for (let k = 1; k <= K; k++) { // at K evenly spaced moments
const when = arr[N - 1] * k / K;
while (a < N && arr[a] <= when) a++;
while (d < N && dep[d] <= when) d++;
inside += a - d;
}
console.log(rho.toFixed(2) + (W.toFixed(1) + " ms").padStart(11) +
((1 / (mu - lam)).toFixed(1) + " ms").padStart(12) +
(inside / K).toFixed(2).padStart(11) + (lamHat * W).toFixed(2).padStart(7));
}আউটপুট:
rho W sim 1/(mu-lam) L counted lam*W
0.50 20.0 ms 20.0 ms 1.00 1.00
0.80 49.7 ms 50.0 ms 3.99 3.99
0.90 103.0 ms 100.0 ms 9.29 9.29
0.95 219.7 ms 200.0 ms 20.91 20.91
ρ = ০.৫, ০.৮ আর ০.৯-এ simulation সূত্রের ৩%-এর মধ্যে। ০.৯৫-এ পার্থক্য প্রায় ১০% (২১৯.৭ বনাম ২০০): ρ ১-এর কাছে জমা ভিড় অনেকক্ষণ থাকে বলে ২,০০,০০০টি অনুরোধেও স্বাধীন নমুনা কম — ১২টি seed-এ এই সারি প্রায় ১৯০ থেকে ২৪৫ ms-এ ঘুরেছে। শেষ দুই কলাম হুবহু মিলেছে: আলাদাভাবে গোনা L আর λ × W একই — Little-এর সূত্র। rho-র তালিকায় 0.7 যোগ করলে কী আসবে, আগে অনুমান করুন (অনুশীলন ৪)।
“৭০%-এর নিয়ম” — অভিজ্ঞতার নিয়ম, সূত্র নয়। অনেক দল latency-সংবেদনশীল সার্ভিস মোটামুটি ৬০–৭০%-এর নিচে ব্যস্ত রাখে; সংখ্যাটি কোনো সূত্র থেকে আসেনি, এসেছে এই যুক্তিগুলো থেকে:
- ঢাল: ρ = ০.৭-এ W ≈ ৩.৩S, আর বক্ররেখা তখনও সহনীয়; ০.৯-এর পর সামান্য বাড়তি চাপেই সময় দ্বিগুণ-তিনগুণ হয়।
- একটি সার্ভার বিকল হলে: তিনটি সার্ভার ৬০% করে ব্যস্ত; একটি পড়ে গেলে বাকি দুটির প্রত্যেকে ৯০% — টেবিলের বিপজ্জনক সারি। (Redundancy-র হিসাব পর্ব ৫-এ।)
- গড়ের ভেতরের ভিড়: পাঁচ মিনিটের গড় ৭০% মানে কোনো কোনো সেকেন্ডে ১০০%; আর নতুন সার্ভার চালু হতে কয়েক মিনিট লাগতে পারে (autoscaling, পর্ব ১৩)।
উল্টো দিকও আছে: batch কাজে latency-লক্ষ্য নেই বলে ৯০%-এর বেশিও চলে, আর বড় pool একই ρ-তে অনেক কম অপেক্ষা করায় (ভাবনার প্রশ্ন ৪)। Service time আরও এলোমেলো হলে অপেক্ষা আরও বাড়ে (Kingman-এর সূত্র, ১৯৬১)।
৮ · Bottleneck আর latency budget — সবচেয়ে সরু ধাপ আর সময়ের বাজেট
একটি অনুরোধ যখন পরপর কয়েকটি ধাপ পার হয়, তখন দুটি সরল নিয়ম খাটে: latency যোগ হয় — প্রতিটি ধাপের সময় মিলিয়ে মোট; আর throughput হয় সবচেয়ে ছোটটি — যে ধাপ সবচেয়ে কম পারে, পুরো ব্যবস্থা তার বেশি পারে না। সেই ধাপের নাম bottleneck.
ঢাকার একটি চার লেনের রাস্তা গিয়ে মিশেছে এক লেনের একটি ব্রিজে। রাস্তার বাকি অংশ যত চওড়াই হোক, ঘণ্টায় কতগুলো গাড়ি পার হবে তা ঠিক করে ব্রিজ। রাস্তা আরও চওড়া করলে শুধু ব্রিজের মুখের জট বড় হয়।
ধরা যাক একটি কাল্পনিক অনলাইন দোকানের checkout-এ প্রতিটি অনুরোধে লাগে app server-এর একবার কাজ, cache-এ ৩টি পড়া আর database-এ ২টি লেখা। প্রতিটি ধাপের ক্ষমতা নিচে ধরে নেওয়া হলো:
| ধাপ | ক্ষমতা (ধরা যাক) | প্রতি checkout-এ লাগে | সর্বোচ্চ checkout/সেকেন্ড |
|---|---|---|---|
| Load balancer | সেকেন্ডে ৫০,০০০ অনুরোধ | ১টি অনুরোধ | ৫০,০০০ |
| App server (৮টি × ৬০০) | সেকেন্ডে ৪,৮০০ অনুরোধ | ১টি অনুরোধ | ৪,৮০০ |
| Cache | সেকেন্ডে ১,০০,০০০ operation | ৩টি পড়া | ৩৩,৩৩৩ |
| Database (primary) | সেকেন্ডে ৩,০০০ লেখা | ২টি লেখা | ১,৫০০ ← bottleneck |
পুরো ব্যবস্থা সেকেন্ডে ১,৫০০-এর বেশি checkout পারবে না — app server দ্বিগুণ করলেও না। Database-এর কাজ কমালে বা database ভাগ করলে (পর্ব ২৩) সীমা ওঠে, আর তখন সামনে আসে পরের সরু ধাপ — app tier-এর ৪,৮০০; bottleneck শুধু জায়গা বদলায়।
Latency budget — সময়ও বাজেট করে খরচ করতে হয়
Throughput-এর যেমন সীমা, সময়েরও তেমনি বাজেট। ধরা যাক একটি কাল্পনিক mobile banking সেবার লক্ষ্য: “ব্যালান্স দেখুন”-এর p99 থাকবে ৩০০ ms-এর মধ্যে (এমন লক্ষ্য আসে non-functional requirement থেকে — পর্ব ২)। এমন মাপযোগ্য লক্ষ্যকে বলে SLO; SLI, SLO আর error budget-এর বিস্তারিত পর্ব ৫০-এ। লক্ষ্যটি স্তরগুলোর মধ্যে ভাগ করে দেওয়াই latency budget:
| স্তর | বাজেট (p99) | মন্তব্য |
|---|---|---|
| Mobile network আর internet, যাওয়া-আসা | ১০০ ms | নিয়ন্ত্রণ সবচেয়ে কম |
| TLS আর load balancer | ১০ ms | connection আবার ব্যবহার করলে কম |
| API gateway (login token যাচাই) | ১৫ ms | token স্থানীয়ভাবে যাচাই |
| Balance সার্ভিসের নিজের কাজ | ২০ ms | — |
| Cache, না পেলে database | ৪০ ms | cache-এ পেলে প্রায় ১ ms; বাজেট না-পাওয়ার জন্য |
| শেষ ৫টি লেনদেনের সার্ভিস | ৫০ ms | timeout ৫০ ms; দেরি হলে আগে ব্যালান্স, তালিকা পরে |
| সংরক্ষিত (slack) | ৬৫ ms | অপ্রত্যাশিত দেরির জন্য |
| মোট | ৩০০ ms | — |
বাজেট নিয়ে দুটি সতর্কতা:
- Percentile সরাসরি যোগ হয় না। স্তরগুলোর p99 যোগ করলে মোটের p99-এর একটি রক্ষণশীল আন্দাজ মেলে (সাধারণত আসলের চেয়ে বেশি), নিশ্চিত হিসাব নয় — সব স্তর একই অনুরোধে একসাথে ধীর হয় না।
- Timeout আসে বাজেট থেকে। লেনদেন-সার্ভিসের বাজেট ৫০ ms হলে timeout-ও মোটামুটি সেখানে, আর বাকি সময় (deadline) নিচের call-এ যায় (অংশ ৫)।
৯ · ঠিকভাবে মাপা — histogram, load test আর warm-up
ভুল মাপ থেকে ভুল নকশা আসে। Latency মাপার তিনটি নিয়ম:
১. গড় নয়, histogram রাখুন
প্রতিটি অনুরোধের সময় আলাদা জমা রাখা ব্যয়বহুল, তাই সার্ভিসগুলো সময়কে খোপে গোনে — ০–৫ ms-এ কতটি, ৫–১০-এ কতটি …। এই গণনাই histogram. খোপ থেকে percentile আন্দাজ করা যায়, আর দুই সার্ভারের খোপগুলো যোগ করলেই পুরো সার্ভিসের histogram. ১,০০০টি নমুনায় p99.9-এর ওপরে মাত্র একটি মান থাকে — tail দেখতে লাখের ঘরের নমুনা লাগে।
Percentile-এর গড় নেবেন না। সার্ভার A সামলায় ৯,০০০ অনুরোধ, p99 ৫০ ms; সার্ভার B সামলায় ১,০০০, যার ৩০০টি আটকে যায় ২ সেকেন্ডে, তাই B-র p99 ২,০০০ ms. দুই p99-এর গড় ১,০২৫ ms; অনুরোধ-সংখ্যায় ভারযুক্ত গড় ০.৯ × ৫০ + ০.১ × ২,০০০ = ২৪৫ ms. আসলে মোট ১০,০০০ অনুরোধের ধীরতম ৩০০টি (৩%) সবই ২ সেকেন্ড, তাই পুরো সার্ভিসের p99 = ২,০০০ ms — দুটো গড়ই ভুল। Histogram বা কাঁচা মাপ একত্র করে তারপর percentile বের করুন।
২. কোথায় মাপছেন, জানুন
সার্ভারের ভেতরে মাপা সময়ে network আর দরজার বাইরের queue বাদ পড়ে; ব্যবহারকারীর ফোনে মাপা সময় (real user monitoring) আসল অভিজ্ঞতা দেখায়, কিন্তু দোষ কোন অংশের, তা বলা কঠিন। দুটোই রাখুন।
৩. Load test — বাস্তবের মতো করে
Load test-এ কৃত্রিম অনুরোধ পাঠিয়ে দেখা হয় কোন চাপে latency কেমন থাকে। ফল বিশ্বাসযোগ্য করতে:
- বাস্তবের মতো অনুরোধের মিশ্রণ আর ডেটার আকার রাখুন — শুধু একটি URL বারবার নয়।
- Warm-up বাদ দিন: শুরুর কয়েক মিনিট cache খালি, JIT compile চলছে, connection নতুন — সেই মাপ স্থির অবস্থার নয়।
- যথেষ্ট লম্বা সময় চালান, যাতে GC, পর্যায়ক্রমিক কাজ আর log rotation ধরা পড়ে।
- চাপ ধাপে ধাপে বাড়ান আর দেখুন কোথায় p99 হঠাৎ খাড়া হয়ে ওঠে — সেই “হাঁটু” (knee)-ই বাস্তব ক্ষমতা, গড় CPU নয়।
- বাস্তবের ব্যবহারকারী অন্যদের উত্তরের অপেক্ষা না করেই আসেন (open loop); অনেক টুল একটি উত্তর পেলে তবে পরের অনুরোধ পাঠায় (closed loop), তাই সার্ভার ধীর হলে টুল নিজেই কম পাঠায়।
আরও গভীরে: শেষ ফাঁদটির নাম coordinated omission; নামটি জনপ্রিয় করেছেন Gil Tene (Azul Systems), তাঁর “How NOT to Measure Latency” আলোচনায় (২০১০-এর দশকে)। সার্ভার দুই সেকেন্ড আটকে থাকলে closed-loop টুল ওই সময়ের শত শত অনুরোধ পাঠায়ই না — লেখা হয় একটিমাত্র ধীর মাপ, তাই p99 বাস্তবের চেয়ে অনেক ভালো দেখায়। প্রতিকার: নির্দিষ্ট হারে অনুরোধ পাঠানো আর যখন পাঠানোর কথা ছিল সেই সময় থেকে latency গোনা; Tene-এর HdrHistogram আর wrk2 এই ভাবনায় তৈরি (খুঁটিনাটি সংস্করণভেদে আলাদা)।
পরীক্ষা ও ইন্টারভিউতে — বিশ্ববিদ্যালয়, চাকরি বা system design ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন আসতে পারে:
- Latency আর throughput-এর পার্থক্য কী? একটি বাড়লে অন্যটি কি বাড়ে? Latency একটি কাজের সময়, throughput প্রতি একক সময়ে শেষ হওয়া কাজ। Batching-এ throughput বাড়ে কিন্তু latency বাড়তে পারে; concurrency সীমিত হলে latency বাড়লে throughput কমে, কারণ λ = L/W.
- Little-এর সূত্র লিখে একটি প্রয়োগ দিন। L = λW; সেকেন্ডে ১,০০০ অনুরোধ × ০.২ সেকেন্ড = গড়ে ২০০টি একসাথে চলছে — thread বা connection-এর মাপ এভাবেই।
- ১০০টি backend-এ fan-out, প্রতিটি ১% সময় ধীর — কত শতাংশ অনুরোধ ধীর? 1 − 0.99100 ≈ ৬৩%; প্রতিকার হিসেবে hedged request, কম fan-out, cache আর timeout ব্যবহার করা হয়।
১০ · AI-এর যুগে — latency আর capacity-র হিসাবে AI-কে যাচাই
“আমাদের সার্ভিসে কয়টি সার্ভার লাগবে?” — AI কয়েক সেকেন্ডে গোছানো উত্তর দেয়, প্রায়ই সাথে “Redis বসান, Kafka যোগ করুন, microservice-এ ভাগ করুন”। এই বিষয়ে তার ভুলগুলো খুব নির্দিষ্ট:
| AI-এর ভুল | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
| কয়েকটি সার্ভারের p99-এর গড়কে “সিস্টেমের p99” বলা | Percentile-এর গড় অর্থহীন | Histogram একত্র করে হিসাব (অংশ ৯) |
| “৯০% CPU-তে চালালে খরচ বাঁচে” — latency-র কথা না বলে | W = S/(1 − ρ): ৯০%-এ ১০ গুণ | M/M/1 টেবিল বা simulator |
| এককের ভুল: ms, সেকেন্ড আর মিনিট গুলিয়ে ফেলা | উত্তর ৬০ বা ১,০০০ গুণ ভুল | প্রতিটি সংখ্যার পাশে একক লিখে আবার হিসাব |
| সংখ্যা ছাড়াই “cache বা queue বসান” | Bottleneck কোথায়, জানা নেই | আগে মাপুন সময় কোথায় যায় (অংশ ২ আর ৮) |
| পণ্যের সীমা বা দাম বানিয়ে বলা | সংস্করণ, hardware আর workload-ভেদে আলাদা | অফিসিয়াল documentation আর নিজের load test |
| “প্রতিটি shard-এর p99 ভালো, তাই পুরোটাও ভালো” | Fan-out-এ 1 − (1 − p)N | অংশ ৪-এর অঙ্ক |
| ব্যর্থতার কথা বাদ দেওয়া | Retry storm, pool ফুরিয়ে যাওয়া | একটি অংশ ধীর বা বিকল ধরে failure-mode পর্যালোচনা |
প্রতিটি অনুমান আর হিসাবের ধাপ খোলা রাখতে বলুন, যাতে নিজে মেলাতে পারেন:
AI-এর capacity plan হাতে পেলে — যাচাইয়ের চেকলিস্ট
অঙ্ক নিজে মেলান, simulator চালান, ব্যর্থতার পথ ভাবুন।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। সংখ্যা মুখস্থ নয়, কারণগুলো বোঝাই আসল লক্ষ্য।
প্র ০১Amazon কেন গড় বা median নয়, 99.9th percentile দিয়ে SLA লিখত — আর 99.99 কেন নয়?
Amazon-এর ইঞ্জিনিয়ারদের লেখা Dynamo paper (DeCandia ও সহকর্মীরা, SOSP ২০০৭) এই প্রশ্নের একটি বিরল প্রকাশ্য উত্তর দেয়। Paper অনুযায়ী তাদের প্ল্যাটফর্মে service-গুলোর প্রতিশ্রুতি বা SLA লেখা আর মাপা হতো বণ্টনের 99.9th percentile-এ। Paper-এর একটি উদাহরণ-SLA: সেকেন্ডে ৫০০টি অনুরোধের peak চাপে ৯৯.৯% অনুরোধের উত্তর ৩০০ ms-এর মধ্যে।
গড় বা median কেন নয়? Paper-এর যুক্তি: লক্ষ্য যদি হয় সব গ্রাহকের ভালো অভিজ্ঞতা, শুধু বেশিরভাগের নয়, তবে গড় আর median যথেষ্ট নয়। যেমন ব্যক্তিগত সুপারিশ বানাতে যাঁর কেনাকাটার ইতিহাস লম্বা, তাঁর জন্য বেশি হিসাব লাগে — তাঁর অনুরোধ পড়ে tail-এ। অর্থাৎ সবচেয়ে ধীর অভিজ্ঞতা প্রায়ই সবচেয়ে সক্রিয় গ্রাহকের। আরেকটি কারণ fan-out: paper-এ বলা হয়েছে, একটি পাতা বানাতে প্রায়ই দেড়শোরও বেশি service-এর কাছে অনুরোধ যায়। প্রতিটির tail ছোট না হলে পাতার tail বিশাল হয় (অংশ ৪)।
99.99 নয় কেন? Paper জানায়, আরও ওপরের percentile বেছে না নেওয়ার কারণ খরচ-লাভের হিসাব: অত দূর পর্যন্ত performance উন্নত করতে খরচ উল্লেখযোগ্যভাবে বেড়ে যায়। প্রতিটি বাড়তি “৯” মানে আরও replica, আরও headroom, আরও জটিল hedging আর পর্যবেক্ষণ।
এখান থেকে তিনটি সাধারণ শিক্ষা। এক, একটি percentile একা অর্থহীন — সাথে থাকতে হয় কোন চাপে (যেমন সেকেন্ডে ৫০০) আর কোন সময়-জানালায় মাপা। দুই, কোন percentile বাছবেন, সেটি প্রযুক্তির চেয়ে বেশি ব্যবসার সিদ্ধান্ত। তিন, নিচের service-এর লক্ষ্য ওপরের service-এর চেয়ে কঠোর হতে হয়, কারণ fan-out tail-কে বড় করে।
মূল উপলব্ধি: ভালো SLA-তে থাকে percentile, চাপ আর সময়-জানালা — আর কোন percentile বেছে নেবেন, তা ঠিক হয় ব্যবহারকারীর অভিজ্ঞতার মূল্য আর খরচের দর-কষাকষিতে।
প্র ০২সব ধীর সার্ভার খুঁজে সরিয়ে দিলেই কি tail latency চলে যায়? “The Tail at Scale” কী সমাধান দেয়?
না — আর এটিই Jeffrey Dean ও Luiz André Barroso-র লেখা “The Tail at Scale” (Communications of the ACM, ২০১৩)-এর মূল কথা। বড় সিস্টেমে ধীরতার উৎস কোনো একটি “খারাপ সার্ভার” নয়; তা ছড়িয়ে আছে সবখানে: একই মেশিনে অনেক কাজের মধ্যে CPU, cache আর network-এর ভাগাভাগি, পেছনে চলা daemon আর রক্ষণাবেক্ষণের কাজ, বিভিন্ন স্তরের queue, garbage collection, এমনকি তাপ নিয়ন্ত্রণ বা বিদ্যুৎ বাঁচাতে CPU-র গতি কমানো। হাজারো মেশিনে এগুলো পুরোপুরি দূর করা অসম্ভব, আর fan-out সামান্য ধীরতাকেও বড় করে তোলে।
তাই লেখকেরা প্রস্তাব করেন tail-tolerant নকশা — যেমন fault-tolerant নকশা ধরে নেয় hardware বিকল হবেই। কৌশলগুলো দুই দলে পড়ে:
- একটি অনুরোধের ভেতরে: hedged request — p95-এর পরও উত্তর না এলে দ্বিতীয় কপি, যাতে বাড়তি load প্রায় ৫%-এ সীমিত থাকে; আর tied request — দুই সার্ভারের queue-তে একসাথে রাখা, যে আগে কাজ শুরু করে সে অন্যটিকে বাতিলের বার্তা পাঠায়। লেখকদের একটি benchmark-এ hedging-এ মাত্র প্রায় ২% বাড়তি অনুরোধে 99.9th percentile বহুগুণ কমেছিল।
- অনেক অনুরোধ জুড়ে: micro-partition (প্রতি মেশিনে অনেক ছোট ভাগ, যাতে চাপ সূক্ষ্মভাবে সরানো যায়), জনপ্রিয় ডেটার বাড়তি replica, ধীর হয়ে পড়া মেশিনকে সাময়িকভাবে বাদ রাখা (latency-induced probation), আর “যথেষ্ট ভালো” ফল — সব shard-এর অপেক্ষা না করে বেশিরভাগের উত্তরেই ফল দেখানো।
আরেকটি সূক্ষ্ম কৌশল canary request: বিশাল fan-out-এর আগে অনুরোধটি এক-দুটি সার্ভারে পাঠিয়ে দেখা, যাতে কোনো বিপজ্জনক query হাজার সার্ভার একসাথে বিকল করতে না পারে। লক্ষ করুন, প্রতিটি কৌশলের দাম আছে — বাড়তি load, বাড়তি জটিলতা, কখনো একটু অসম্পূর্ণ ফল। কোনটি কতটা নেবেন, ঠিক করে SLO.
মূল উপলব্ধি: ধীরতা বড় সিস্টেমের স্বভাব, ব্যতিক্রম নয় — লক্ষ্য তাকে নির্মূল করা নয়, তার প্রভাব ব্যবহারকারী পর্যন্ত পৌঁছাতে না দেওয়া।
প্র ০৩Little-এর সূত্র কোনো বণ্টন না জেনেও কীভাবে খাটে — আর কোথায় এর ভুল ব্যবহার হয়?
রহস্যটি প্রমাণের ভেতরেই। L আর W দুটিই একই জিনিস মাপে — “কে কতক্ষণ ভেতরে ছিল”-এর ক্ষেত্রফল — শুধু দুই দিক থেকে: সময় ধরে (প্রতি মুহূর্তে কতজন) আর মানুষ ধরে (প্রতিজন কতক্ষণ)। এখানে কে কখন এলেন, কতক্ষণ লাগল, কে আগে সেবা পেলেন — কোনো অনুমানই লাগে না। শর্ত শুধু দুটি: সিস্টেম স্থিতিশীল (লম্বা সময়ে যত আসে তত বেরোয়), আর তিনটি সংখ্যা একই সীমানায় মাপা।
এই সাধারণতার কারণে সূত্রটি সবখানে দেখা যায়, অনেক সময় অন্য নামে:
- TCP: পথে থাকা byte = throughput × RTT. Computer Networks কোর্সের bandwidth-delay product আসলে Little-এর সূত্র।
- CPU আর memory: একসাথে অপেক্ষমাণ cache miss = memory bandwidth × latency; বেশি bandwidth পেতে বেশি miss একসাথে চালু রাখতে হয়।
- কারখানা: গার্মেন্টসের লাইনে আধা-তৈরি মাল (work in progress) = ঘণ্টায় উৎপাদন × একটি পোশাক তৈরির সময়। Lean উৎপাদনে আধা-তৈরি মাল কমিয়ে সময় কমানোর ভাবনা এখান থেকেই।
ভুল ব্যবহার কোথায়? এক, অস্থির সময়ে: হঠাৎ ভিড়ের মিনিটে queue বাড়ছে — তখন আগমন আর বিদায় সমান নয়, মুহূর্তের সংখ্যায় সূত্র খাটে না। দুই, সীমানা গুলিয়ে: λ গোনা হলো load balancer-এ, W মাপা হলো app-এর ভেতরে — মাঝের queue বাদ। তিন, গড় দিয়ে peak-এর মাপ ঠিক করা: গড়ে ৬টি connection ব্যস্ত মানে ৬টির pool যথেষ্ট নয়। চার, এককের গোলমাল — সেকেন্ডের হারের সাথে মিলিসেকেন্ডের সময় গুণ করা।
সূত্রটি ১৯৬১ সালে John D. C. Little সাধারণভাবে প্রমাণ করেন। পঞ্চাশ বছর পূর্তিতে (২০১১) তিনি নিজেই এর ইতিহাস আর প্রয়োগ নিয়ে একটি পর্যালোচনা প্রকাশ করেন — operations research-এ এত দীর্ঘজীবী আর এত কাজের ফল খুব কম।
মূল উপলব্ধি: তিনটির দুটি জানলেই তৃতীয়টি পাওয়া যায় — কিন্তু সীমানা, একক আর স্থিতিশীলতা ঠিক থাকলে তবেই।
প্র ০৪সার্ভার ৯৫% ব্যস্ত রাখলে খরচ বাঁচে — তাহলে ৭০%-এর পরামর্শ কেন? আর বড় cluster কি বেশি ব্যস্ত রাখা যায়?
খরচের দিক থেকে যুক্তিটি সোজা: ৯৫% ব্যস্ত মানে প্রায় কোনো ক্ষমতা অলস নেই। কিন্তু M/M/1-এর টেবিল বলে, S = ১০ ms হলে ৭০%-এ গড় সময় প্রায় ৩৩ ms, আর ৯৫%-এ ২০০ ms — p99 প্রায় ৯২১ ms. তার ওপর ৯৫%-এ থাকা সিস্টেমের কোনো সহনশীলতা নেই: চাপ মাত্র প্রায় ৫% বাড়লেই ρ = ১-এর দেয়াল, আর একটি সার্ভার বিকল হলেই বাকিরা ক্ষমতার সীমায় বা তার ওপরে পৌঁছে যায়। তাই latency-সংবেদনশীল সার্ভিসে কম ব্যস্ততা আসলে একটি বীমা।
তবে “৭০%” সবখানে এক নয়। এর সবচেয়ে সুন্দর ব্যাখ্যা ব্যাংকের টোকেন ব্যবস্থা। আগে প্রতিটি কাউন্টারের সামনে আলাদা লাইন হতো; যে লাইনে একজন ধীর গ্রাহক, সেখানে সবাই আটকে, অথচ পাশের কাউন্টার হয়তো অলস। টোকেন ব্যবস্থায় একটিই সাধারণ লাইন, আর যে কাউন্টার খালি হয় সে-ই পরের জনকে ডাকে। Queueing-এর ভাষায়: দশটি আলাদা M/M/1 বনাম একটি M/M/10.
সংখ্যায় পার্থক্য নাটকীয়। S = ১০ ms, প্রত্যেক সেবাদাতা ৮০% ব্যস্ত: আলাদা লাইনে গড় সময় ৫০ ms, সাধারণ লাইনে মাত্র প্রায় ১২ ms. ৯০%-এ: ১০০ ms বনাম প্রায় ১৭ ms. ৫০ জন সেবাদাতা হলে ৯০%-এও গড় সময় প্রায় ১০.৭ ms — অপেক্ষা প্রায় নেই। হিসাবটি Erlang C সূত্রের: ডেনমার্কের প্রকৌশলী A. K. Erlang ১৯০৯ সাল থেকে টেলিফোন এক্সচেঞ্জে কতগুলো লাইন লাগবে তা হিসাব করতে গিয়ে queueing-এর এই গণিত গড়ে তোলেন, আর আজও কল সেন্টারের কর্মী-সংখ্যা এভাবেই ঠিক করা হয়।
তাই বড় pool, যেখানে load balancer খালি সার্ভারে অনুরোধ পাঠাতে পারে (পর্ব ১৪), নিরাপদে বেশি ব্যস্ত চালানো যায়; ছোট সার্ভিস বা একটিমাত্র database তা পারে না। আর রাতের batch কাজের latency-লক্ষ্যই নেই — সেখানে ৯০%-এর বেশি চালানোই বুদ্ধিমানের কাজ।
মূল উপলব্ধি: “৭০%” শুরু করার একটি সংখ্যা মাত্র; সঠিক লক্ষ্য আসে SLO, pool-এর আকার, চাপের ওঠানামা আর একটি সার্ভার বিকল হলে কী হবে — এই চারটির হিসাব থেকে।
প্র ০৫Batching throughput বাড়ায় — তাহলে Nagle-এর algorithm আর delayed ACK কেন latency-র কুখ্যাত উদাহরণ?
১৯৮৪ সালে John Nagle RFC 896-এ একটি বাস্তব সমস্যা বর্ণনা করেন: telnet-এর মতো প্রোগ্রাম প্রতিটি অক্ষর আলাদা packet-এ পাঠাত — এক byte তথ্যের সাথে প্রায় ৪০ byte header. ধীর লাইনে এমন অসংখ্য ছোট packet জট বাধাত। তাঁর সমাধান, Nagle's algorithm, আসলে একটি batching: আগে পাঠানো ডেটার স্বীকৃতি (ACK) না আসা পর্যন্ত নতুন ছোট লেখাগুলো জমিয়ে রাখা, ACK এলে একসাথে পাঠানো।
অন্য দিকে ACK-এর সংখ্যা কমাতে প্রাপকেরা delayed ACK চালু করে: কিছুক্ষণ অপেক্ষা করা, যাতে ফেরত ডেটার সাথে ACK জুড়ে দেওয়া যায় (RFC 1122, ১৯৮৯; দেরি আধা সেকেন্ডের কম হতে হবে)। দুটি ভালো ধারণা একসাথে হলে বিপদ: প্রেরক অপেক্ষা করছে ACK-এর, প্রাপক অপেক্ষা করছে আরও ডেটার। অনুরোধ-উত্তর ধরনের প্রোটোকলে এতে প্রতিটি অনুরোধে কয়েক দশ থেকে কয়েকশো ms-এর অকারণ বিরতি আসতে পারে (operating system-ভেদে আলাদা)। তাই latency-সংবেদনশীল প্রোগ্রাম প্রায়ই TCP_NODELAY দিয়ে Nagle বন্ধ রাখে; অনেক RPC framework আর database driver এটি ডিফল্টেই করে।
শিক্ষাটি TCP ছাড়িয়ে সর্বজনীন। Batching প্রতিটি কাজের স্থির খরচ (header, system call, disk-এ flush) ভাগ করে throughput বাড়ায়; দাম হলো দল ভরার অপেক্ষা। Database-এর group commit অনেক transaction-এর log একবারে disk-এ লেখে; Kafka-র মতো message broker-এর producer কয়েক মিলিসেকেন্ড অপেক্ষা করে message জমিয়ে পাঠাতে পারে (linger.ms আর batch.size-এর মতো setting; ডিফল্ট মান সংস্করণভেদে আলাদা); AI মডেলের inference সার্ভার কয়েকটি অনুরোধ একসাথে GPU-তে চালায়।
একটি চতুর মধ্যপথ হলো “স্বাভাবিক batching”: আলাদা করে অপেক্ষা না করে, আগের কাজ চলার সময় যা জমেছে শুধু সেটুকু একসাথে পাঠানো। চাপ কম থাকলে কাউকে অপেক্ষা করতে হয় না; চাপ বাড়লে দল নিজে থেকেই বড় হয়, আর throughput বাড়ে ঠিক যখন দরকার।
মূল উপলব্ধি: Batching হলো latency দিয়ে throughput কেনা — কতটা দাম দেবেন, তা ঠিক করে আপনার SLO, অভ্যাস নয়।
অনুশীলন
কাগজ-কলমে হিসাব করুন, তারপর উত্তর মেলান। শেষ প্রশ্নে আগে অনুমান, তারপর simulator চালিয়ে যাচাই।
-
হাতে percentile: একটি API-র ২০টি response time (ms):
35, 42, 38, 51, 47, 39, 44, 610, 41, 36, 40, 49, 43, 37, 120, 45, 46, 52, 48, 40. Nearest-rank পদ্ধতিতে গড়, p50, p90, p95 আর p99 বের করুন। Dashboard-এ কোন সংখ্যাগুলো রাখবেন? আর p99.9 নির্ভরযোগ্যভাবে জানতে মোটামুটি কত নমুনা লাগবে?সাজানো তালিকা: 35, 36, 37, 38, 39, 40, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 120, 610. যোগফল ১,৫০৩, তাই গড় = ৭৫.১৫ ms.
- p50: rank ⌈০.৫ × ২০⌉ = ১০ → ৪৩ ms
- p90: rank ⌈০.৯ × ২০⌉ = ১৮ → ৫২ ms
- p95: rank ⌈০.৯৫ × ২০⌉ = ১৯ → ১২০ ms
- p99: rank ⌈১৯.৮⌉ = ২০ → ৬১০ ms (২০টি নমুনায় এটিই সর্বোচ্চ মান)
গড় ৭৫.১৫ ms, অথচ ২০টির ১৮টি অনুরোধ (৯০%) গড়ের চেয়ে দ্রুত — গড় p90-এর চেয়েও বড়। Dashboard-এ রাখুন p50, p95 আর p99, সাথে নমুনা-সংখ্যা আর সময়-জানালা। একটিমাত্র ৬১০ ms দেখে এখনই সিদ্ধান্ত নয় — আরও নমুনা নিয়ে দেখুন এটি নিয়মিত ঘটে কি না। p99.9-এর ওপরে অন্তত একটি মান পেতেই ন্যূনতম ১,০০০টি নমুনা লাগে; ভরসাযোগ্য হতে লাখের ঘরে, যাতে p99.9-এর ওপরে অন্তত কয়েক ডজন মান থাকে।
-
Little দিয়ে pool-এর মাপ (হিসাব আর নকশা): একটি কাল্পনিক payment API-তে peak-এ সেকেন্ডে ৯০০টি অনুরোধ আসে, ৬টি app server-এ সমানভাবে ভাগ হয়ে। প্রতিটি অনুরোধ গড়ে ১২০ ms সময় নেয়, যার মধ্যে গড়ে ৮ ms একটি database connection ধরে রাখে। Database সর্বোচ্চ ১০০টি connection নিতে পারে। (ক) প্রতিটি সার্ভারে গড়ে কতগুলো অনুরোধ একসাথে চলে? (খ) প্রতিটি সার্ভারে গড়ে কতগুলো connection ব্যস্ত থাকে? (গ) একজন সহকর্মী প্রস্তাব দিলেন “প্রতিটি সার্ভারে pool হোক ১০০” — সমালোচনা করুন আর আপনার প্রস্তাব দিন। (ঘ) Database ধীর হয়ে connection ধরে রাখার সময় ৪০ ms হলে কী ঘটবে?
প্রতিটি সার্ভারে λ = ৯০০ ÷ ৬ = সেকেন্ডে ১৫০টি।
- (ক) L = λW = ১৫০ × ০.১২ = ১৮টি; অনুরোধ-প্রতি একটি thread হলে thread pool এর ২–৩ গুণ (৪০–৫০) রাখা যুক্তিসংগত।
- (খ) L = ১৫০ × ০.০০৮ = ১.২টি connection; ছয় সার্ভারে মিলিয়ে মোট ৭.২টি।
- (গ) ৬ × ১০০ = ৬০০টি connection — database-এর সীমার ছয় গুণ; ভিড়ে database নতুন connection ফিরিয়ে দেবে বা নিজেই ধীর হবে। গড় প্রয়োজন মাত্র ১.২টি, তাই প্রতি সার্ভারে ৫–৮টির pool (মোট ৩০–৪৮) যথেষ্ট headroom দেয়, সীমার ভেতরেও থাকে।
- (ঘ) এখন প্রতি সার্ভারে ব্যস্ত connection = ১৫০ × ০.০৪ = ৬টি (মোট ৩৬)। Pool ৫ হলে চাহিদা ক্ষমতার বেশি: অনুরোধ connection-এর লাইনে দাঁড়াবে, W বাড়বে, Little অনুযায়ী একসাথে চলা thread বাড়বে, শেষে thread pool ভরে নতুন অনুরোধ ফিরিয়ে দিতে হবে — ধীর dependency থেকে পুরো সার্ভিস বসে যাওয়ার পথ এটিই (প্রতিকার timeout আর load shedding, পর্ব ১৮)। Pool ৮ হলে টিকে যায় (ব্যস্ততা ০.৭৫), কিন্তু headroom প্রায় শেষ।
-
Fan-out-এর যুক্তি সমালোচনা করুন: একটি search পাতা একসাথে ৫০টি shard-কে জিজ্ঞেস করে আর সবার উত্তরের অপেক্ষা করে। প্রতিটি shard স্বাধীনভাবে ০.৫% সময় ২০০ ms-এর বেশি নেয়। দলের একজন বলছেন, “প্রতিটি shard-এর p99 ২০০ ms-এর নিচে, তাই পাতার p99-ও ২০০ ms-এর নিচে।” (ক) কত শতাংশ পাতা ২০০ ms ছাড়াবে? (খ) যুক্তিটির ভুল কোথায়, আর পাতার p99 ২০০ ms-এর মধ্যে রাখতে প্রতিটি shard-এর কোন percentile ২০০ ms-এর নিচে থাকতে হবে? (গ) দুটি প্রতিকার প্রস্তাব করুন।
- (ক) 1 − 0.99550 ≈ ০.২২২ — প্রায় ২২% পাতা ২০০ ms ছাড়াবে, অর্থাৎ মোটামুটি প্রতি পাঁচটির একটি। পাতার p99 তো দূরের কথা, p80-ও ২০০ ms-এর ওপরে।
- (খ) যুক্তিটি ধরে নিয়েছে পাতার সময় একটি shard-এর সময়ের মতোই; আসলে পাতার সময় = ৫০টির মধ্যে সবচেয়ে ধীরটি। পাতার ৯৯% দ্রুত হতে হলে প্রতিটি shard-কে 0.991/50 ≈ ০.৯৯৯৮ সম্ভাবনায় দ্রুত হতে হবে — অর্থাৎ shard-এর প্রায় p99.98 থাকতে হবে ২০০ ms-এর নিচে।
- (গ) (১) Fan-out কমানো: ১০টি বড় shard হলে 1 − 0.99510 ≈ ৪.৯%; আরও ভালো, শুধু প্রাসঙ্গিক shard-কে জিজ্ঞেস করা। (২) Hedged request: ধীর shard-এর replica-তে p95-এর পর কপি পাঠানো। এছাড়া “যথেষ্ট ভালো” ফল (৪৯টি shard-এর উত্তরেই পাতা দেখানো, বাকিটা পরে) আর জনপ্রিয় প্রশ্নের ফল cache-এ রাখা।
-
আগে অনুমান, তারপর চালান: (ক) অংশ ৭-এর M/M/1 simulator-এ
rho-র তালিকায়0.7যোগ করলে1/(mu-lam)কলামে কী ছাপা হবে, আরL countedমোটামুটি কত? অন্য সারিগুলো কি বদলাবে? (খ) একটি API সার্ভারের গড় service time ২০ ms (একজন worker, M/M/1 ধরে নিন)। সেকেন্ডে ২৫, ৪০ আর ৪৫টি অনুরোধে গড় response time, L আর p99 কত? (গ) খরচ কমাতে কেউ প্রস্তাব দিলেন সার্ভার কমিয়ে প্রতিটির ব্যস্ততা ৫০% থেকে ৯০%-এ তোলা হোক। হিসাব দিয়ে মত দিন।- (ক) μ = ০.১ আর λ = ০.০৭ প্রতি ms, তাই 1/(0.1 − 0.07) = ৩৩.৩ — কলামে
33.3 ms; আর L ≈ ρ/(1 − ρ) = ০.৭ ÷ ০.৩ ≈ ২.৩৩. আমাদের চালানোয় নতুন সারিটি এসেছে নিচের মতো। অন্য সারিগুলো একটুও বদলায় না, কারণ প্রতিটি সারি একই seed থেকে নতুন করে random সংখ্যা শুরু করে।
0.70 33.3 ms 33.3 ms 2.33 2.33- (খ) μ = সেকেন্ডে ৫০টি। λ = ২৫: ρ = ০.৫, W = 1 ÷ (৫০ − ২৫) সেকেন্ড = ৪০ ms, L = ১, p99 ≈ ৪.৬ × ৪০ ≈ ১৮৪ ms. λ = ৪০: ρ = ০.৮, W = ১০০ ms, L = ৪, p99 ≈ ৪৬১ ms. λ = ৪৫: ρ = ০.৯, W = ২০০ ms, L = ৯, p99 ≈ ৯২১ ms.
- (গ) ৫০% থেকে ৯০%-এ গেলে গড় সময় ৪০ থেকে ২০০ ms, p99 ১৮৪ থেকে ৯২১ ms. সার্ভার কমবে প্রায় ৪৪% (প্রয়োজন 1/ρ-এর অনুপাতে, ২ থেকে ১.১১), কিন্তু p99 লক্ষ্য ৩০০ ms হলে তা ভাঙবে, আর একটি সার্ভার বিকল হলে বাকিদের ব্যস্ততা ১-এর আরও কাছে চলে যাবে। ভালো পথ SLO থেকে উল্টো হিসাব: ৪.৬ × ২০ ÷ (1 − ρ) ≤ ৩০০, অর্থাৎ ρ ≤ প্রায় ০.৬৯; তারপর বিকল-সার্ভারের headroom যোগ করা।
- (ক) μ = ০.১ আর λ = ০.০৭ প্রতি ms, তাই 1/(0.1 − 0.07) = ৩৩.৩ — কলামে
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমNearest-rank পদ্ধতিতে p99 বের করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📥 পর্যাপ্ত সংখ্যক latency মাপ সংগ্রহ (warm-up বাদ দিয়ে)
- 🔢 মাপগুলো ছোট থেকে বড় ক্রমে সাজানো
- 🧮 rank = ⌈0.99 × N⌉ হিসাব করা
- 📍 সাজানো তালিকায় সেই rank-এর মানটি নেওয়া
- 📝 ফলের সাথে নমুনা-সংখ্যা, সময়-জানালা আর পদ্ধতি লিখে রিপোর্ট করা
একটি অনুরোধ পরপর তিনটি ধাপ পার হয়: API সার্ভার (সেকেন্ডে সর্বোচ্চ ৫,০০০), payment সার্ভিস (সর্বোচ্চ ১,২০০) আর database (সর্বোচ্চ ৩,০০০)। প্রতিটি অনুরোধে প্রতিটি ধাপ একবার করে লাগে। পুরো ব্যবস্থার সর্বোচ্চ throughput কত?
- সেকেন্ডে ৯,২০০
- সেকেন্ডে প্রায় ৩,০৬৭
- সেকেন্ডে ১,২০০
- সেকেন্ডে ৫,০০০
২০০টি latency মাপ ছোট থেকে বড় ক্রমে সাজানো আছে। Nearest-rank পদ্ধতিতে p99 কোন অবস্থানের মান?
- ১৯৮তম
- ১৯৯তম
- ২০০তম (সবচেয়ে বড়)
- ৯৯তম
একটি অনুরোধ একসাথে ১০০টি backend-এ যায় আর সবার উত্তরের অপেক্ষা করে। প্রতিটি backend স্বাধীনভাবে ১% সময় ধীর হয়। অনুরোধটি অন্তত একটি ধীর উত্তরের অপেক্ষায় পড়ার সম্ভাবনা প্রায় কত?
- ১%
- ১০%
- ৬৩%
- ১০০%
একটি সার্ভিসে প্রতি সেকেন্ডে গড়ে ৪০০টি অনুরোধ আসে, আর প্রতিটি গড়ে ৫০ ms সার্ভিসের ভেতরে থাকে। Little-এর সূত্র অনুযায়ী গড়ে একসাথে কতগুলো অনুরোধ চলছে?
- ৮টি
- ২০টি
- ৪৫০টি
- ২০,০০০টি
M/M/1 মডেলে একটি সার্ভারের গড় service time ১০ ms. ব্যস্ততা (utilisation) ৫০% থেকে বেড়ে ৯০% হলে গড় response time কত গুণ হয়?
- ১.৮ গুণ
- ২ গুণ
- ৫ গুণ
- ১০ গুণ
দুটি সার্ভার একই সার্ভিস চালায়। সার্ভার A-র p99 ৫০ ms, সার্ভার B-র p99 ২,০০০ ms. পুরো সার্ভিসের p99 জানার সঠিক উপায় কোনটি?
- দুই p99-এর গড় নেওয়া
- অনুরোধের সংখ্যা দিয়ে ভারযুক্ত গড় নেওয়া
- দুই সার্ভারের histogram (বা সব মাপ) একত্র করে তারপর p99 বের করা
- বড়টি (২,০০০ ms) নেওয়া — সেটিই সবসময় ঠিক
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Latency = একটি কাজের সময়, throughput = প্রতি সেকেন্ডে শেষ হওয়া কাজ, bandwidth = পথের সর্বোচ্চ ক্ষমতা; response time = network + queue + service.
- গড় নয়, percentile: nearest-rank-এ rank = ⌈p/100 × N⌉; পদ্ধতি আর নমুনা-সংখ্যা লিখুন, আর percentile-এর গড় না নিয়ে histogram মেলান।
- Fan-out-এ অন্তত একটি ধীর উত্তরের সম্ভাবনা 1 − (1 − p)N; প্রতিকার hedged request, timeout, কম fan-out আর cache.
- L = λW যেকোনো স্থিতিশীল সিস্টেমে খাটে; pool, thread আর queue-এর মাপ এখান থেকে, আর throughput ≤ concurrency ÷ latency.
- M/M/1-এ W = S/(1 − ρ) — ব্যস্ততা ১-এর কাছে গেলে অপেক্ষা বিস্ফোরিত হয়; সবচেয়ে সরু ধাপই bottleneck, আর মাপা হয় warm-up বাদ দিয়ে, বাস্তবের মতো load-এ।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ০৫ · Availability আর reliability — nines, redundancy, single point of failure আর MTTRপরবর্তী পর্বএকটি সিস্টেম কতটা সময় চালু থাকবে, আর তার হিসাব।
- পর্ব ০৩ · Back-of-the-envelope হিসাব — QPS, storage, bandwidth আর latency-র সংখ্যাআগের পর্বQPS আর latency-র মোটামুটি সংখ্যা দিয়ে দ্রুত আন্দাজ।
- সিস্টেম ডিজাইন · কোর্স হোম৬৬টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Computer Networks · Delay, loss আর throughputপাশের ট্র্যাকনেটওয়ার্কের চার রকম delay, bottleneck link আর traffic intensity.
- Operating Systems · পারফরম্যান্স আর observabilityগভীরে যানএকটি ধীর সার্ভারে আসলে কোথায় সময় যাচ্ছে, তা খোঁজার পদ্ধতি আর টুল।