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

Reliability প্রকৌশল — chaos engineering, load test আর capacity planning

Reliability Engineering — Chaos Engineering, Load Testing and Capacity Planning

Taught in Bangla, this lesson runs a real chaos experiment by deleting a Pod with steady state, hypothesis, blast radius and abort conditions, then covers latency injection and game days. It compares load test types, closed and open load and coordinated omission, finds the knee in a real run, verifies it with Little's law and utilisation math, and builds a capacity plan with forecast, headroom and zone failure.

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

  • Steady state, hypothesis, blast radius আর abort condition দিয়ে একটি নিরাপদ chaos পরীক্ষা সাজানো
  • পাঁচ ধরনের load test চেনা, আর closed ও open load-এর ফাঁদ এড়ানো
  • আসল load run থেকে knee বের করা, আর Little's law দিয়ে ফল যাচাই করা
  • Utilisation বাড়লে queueing delay কেন লাফিয়ে বাড়ে, সেটি হিসাব করা
  • ঈদের সেলের জন্য forecast, headroom আর zone-ব্যর্থতা ধরে capacity plan বানানো
  • Autoscaling কোথায় থামে, আর বাড়তি ক্ষমতার দাম কত, সেটি বোঝা

এই পর্বের transcript সম্পর্কে: নিচের kubectl আর load test-এর আউটপুট একটি আসল run থেকে নেওয়া: এক node-এর local cluster, Kubernetes ১.৩৪, যেখানে একই সময়ে অন্য কাজও চলছিল। নাম, IP, সময় আর latency আপনার run-এ আলাদা হবে। এক node-এ একাধিক zone নেই, তাই zone-ব্যর্থতার অংশ শুধু হিসাব ও simulator. আসল সংখ্যা শেখার উপকরণ, কোনো পণ্যের benchmark নয়।

১ · অগ্নি-মহড়া — ব্যর্থতাকে আগে থেকে ডেকে আনা

আগের চারটি পর্বে আপনি metric, log, trace আর alert দেখেছেন, আর পর্ব ৬৭-তে incident সামলানো। সবই ব্যর্থতা ঘটার পরের কাজ। এই পর্বের প্রশ্ন উল্টো: ব্যর্থতার আগে কীভাবে জানবেন আপনার সিস্টেম টিকবে কি না?

Reliability নিয়ে আমাদের বেশিরভাগ বিশ্বাসই অনুমান। “তিনটি replica আছে, একটি মরলে কিছুই হবে না।” “Autoscaler আছে, ভিড় সামলে নেবে।” “Backup আছে।” এই বাক্যগুলো হয়তো সত্য। কিন্তু আপনি জানেন না, যতক্ষণ না পরীক্ষা করছেন। এই পর্বে তিনটি হাতিয়ার দিয়ে অনুমানকে মাপে বদলাব:

Chaos engineering

যখন একটি অংশ ভাঙে, তখন কী হয়? ইচ্ছা করে সীমিত ব্যর্থতা ঢুকিয়ে দেখা।

অগ্নি-মহড়া

Load test

চাপ বাড়লে কোথায় ভাঙে? বানানো ভিড় পাঠিয়ে সীমা মাপা।

সেতুতে ওজনের পরীক্ষা

Capacity planning

আগামী ভিড়ের জন্য কত ক্ষমতা লাগবে, আর একটি zone হারালে কী থাকবে?

বিয়েবাড়ির ক্যাটারিং-এর হিসাব

স্কুলের অগ্নি-মহড়ার কথা ভাবুন। ঘণ্টা বাজে, সবাই সিঁড়ি দিয়ে নামে, শিক্ষক গুনে দেখেন কতক্ষণ লাগল। কেউ ক্লাসে আগুন ধরান না। আগুন সাজানো, কিন্তু মাপ আসল। মহড়াতেই হয়তো ধরা পড়ে, পেছনের সিঁড়ির তালা কারও কাছে নেই। Chaos engineering ঠিক এই মহড়া, সফটওয়্যারের জন্য।

Netflix ২০০৮ সালের দিকের একটি বড় database-সমস্যার পরে নিজের data center ছেড়ে AWS-এ যাওয়া শুরু করে। Cloud-এ যেকোনো instance যেকোনো সময় হারাতে পারে, তাই তারা Chaos Monkey বানায়: এটি কাজের সময়ে এলোমেলোভাবে production instance বন্ধ করত। ২০১১ সালে Netflix-এর ব্লগে “Simian Army” লেখায় টুলটি ব্যাপকভাবে পরিচিত হয়, আর ২০১২ সালে এর code উন্মুক্ত হয়। পরে Netflix-এর প্রকৌশলীরা ধারণাটি নিয়ম আকারে সাজান Principles of Chaos Engineering (principlesofchaos.org) নামে।

Principles of Chaos-এর পাঁচটি কথা

  • স্থিতি নিয়ে দাবি: ভেতরের খুঁটিনাটি নয়, বাইরে থেকে মাপা যায় এমন আচরণ নিয়ে hypothesis লেখা।
  • বাস্তব ঘটনা: নষ্ট server, ধীর network, হঠাৎ ভিড়, এমন ঘটনা বেছে নেওয়া।
  • Production-এ: আসল traffic আর আসল config-ই আসল উত্তর দেয়। তবে পৌঁছাতে হয় ধাপে ধাপে।
  • স্বয়ংক্রিয়: হাতে একবার করা পরীক্ষা পরের release-এ পুরোনো হয়ে যায়।
  • Blast radius ছোট রাখা: ক্ষতি যত কম, শেখা তত নিরাপদ।

২ · একটি পরীক্ষার চারটি খুঁটি

“চলুন একটা server বন্ধ করে দেখি” chaos engineering নয়। বৈজ্ঞানিক পরীক্ষার মতো এখানেও আগে থেকে চারটি জিনিস লিখে নিতে হয়।

খুঁটিঅর্থআমাদের পরীক্ষায়
Steady stateস্বাভাবিক অবস্থার মাপা সংখ্যা, ব্যবসার ভাষার কাছাকাছি২০ req/s-এ success ≥ ৯৯%, p99 < ২০০ ms
Hypothesisব্যর্থতা ঢোকালেও সংখ্যা কী থাকবে, সেই আগাম দাবি“একটি Pod মুছলেও ৩০ সেকেন্ডে success ≥ ৯৯% থাকবে”
Blast radiusকতটুকু জায়গা ক্ষতিগ্রস্ত হতে পারেনিজের namespace, একটি Pod, ৩০ সেকেন্ড, বানানো traffic
Abort conditionকোন সীমায় থামবেন, আর কীভাবে ফিরবেনটানা ১০ সেকেন্ড error > ৫০% হলে থামা; Deployment নিজেই Pod ফেরাবে

একটি সূক্ষ্ম কথা: পরীক্ষা থেকে দুই রকম ফল আসতে পারে, দুটোই লাভ। Hypothesis টিকলে আত্মবিশ্বাস বাড়ে। ভাঙলে আপনি একটি দুর্বলতা পেলেন, যা গ্রাহকের আগে আপনি দেখলেন। তাই পরীক্ষা “ব্যর্থ” হওয়া মানে কাজ বিফল নয়।

Observability ছাড়া chaos করবেন না। আপনি যদি steady state মাপতেই না পারেন, তবে পরীক্ষা শুধু ক্ষতি। Error budget ফুরিয়ে থাকলে, বড় release চলার সময়, বা চলমান incident-এর মধ্যেও নয়। আর কখনো অন্যের সিস্টেমে নয়: chaos আর load test কেবল নিজের সিস্টেমে, অনুমতি নিয়ে আর দলকে জানিয়ে।

৩ · আসল পরীক্ষা — Pod মুছে error মাপা

এবার আসল cluster. web নামের একটি ছোট Python service, Service-এর পেছনে চলছে। এতে তিনটি বাস্তব আচরণ বসানো হয়েছে:

  • চালু হওয়ার পরে ৫ সেকেন্ড সে “গরম” হয়, যেন cache ভরছে। এই সময়ে 503 দেয়।
  • /ready path জানায় সে কাজ নিতে তৈরি কি না।
  • DRAIN_S সেট করলে SIGTERM পাওয়ার পরেও সে এতক্ষণ কাজ চালিয়ে যায়, তারপর থামে।

Client Pod ৩০ সেকেন্ড ধরে ২০ req/s পাঠায়। প্রতিটি অনুরোধ নতুন connection, timeout ১ সেকেন্ড। প্রায় ১০ সেকেন্ডে অন্য একটি terminal থেকে একটি Pod মোছা হয়। মোট ৬০০ অনুরোধ।

প্রথম চেষ্টা: একটি replica, কোনো probe নেই।

$ kubectl exec client -- python /scripts/traffic.py http://web/ 20 30 1.0
sent 600 {'ok': 454, 'refused': 13, 'timeout': 34, '503': 99}
errors per second: 0 0 0 0 0 0 0 0 0 12 20 20 20 20 20 20 14 0 0 0 0 0 0 0 0 0 0 0 0 0
success 75.7%  p50 2 ms  p99 3 ms

Hypothesis ভেঙে গেল: success 75.7%, মোট ১৪৬টি ব্যর্থ। টানা ৮ সেকেন্ড error ছিল। প্রথমে refused (পুরোনো Pod নেই), তারপর timeout, শেষে ৯৯টি 503. ওই শেষ অংশটি নতুন Pod-এর গরম হওয়া: ২০ req/s × ৫ s ≈ ১০০. আর দেখুন, p99 3 ms একদম সুন্দর! এই সংখ্যা শুধু সফল অনুরোধের। Latency একা দেখলে এই বিপর্যয় চোখেই পড়ত না, তাই error rate আর latency একসাথে দেখতে হয়।

এবার ধাপে ধাপে ঠিক করি। প্রতিটি সেটআপ আলাদাভাবে চালানো হয়েছে:

সেটআপReplicaReadiness probeDrainব্যর্থ (৬০০-এর মধ্যে)Success
A১নেইনেই১৪৬৭৫.৭%
B১আছেনেই১৫৬, ১৬২৭৪.০%, ৭৩.০%
C৩নেইনেই৪৬, ৩০৯২.৩%, ৯৫.০%
D৩আছে৩ s০, ০, ০, ০১০০% (চারটি run-এই)

এই টেবিল থেকে তিনটি শিক্ষা:

  • B: এক replica-য় probe কিছুই বাঁচায় না। Probe শুধু বলে “এই Pod-এ পাঠাবেন না”। অন্য Pod না থাকলে পাঠানোর জায়গাই নেই। ৯৯টি দ্রুত 503-এর জায়গায় এলো ১৪০-এর বেশি ১-সেকেন্ডের timeout: ফল বরং একটু খারাপ। (এই cluster-এ ready Pod না থাকার সময়ে connect আটকে timeout হয়েছে; অন্য network সেটআপে সঙ্গে সঙ্গে refused আসতে পারে।)
  • C: Replica error কমায়, শেষ করে না। Service গরম না হওয়া নতুন Pod-কেও অনুরোধ দেয়। তিন Pod-এ ভাগ হলে তার ভাগে ২০ ÷ ৩ × ৫ ≈ ৩৩টি 503. মাপে পেয়েছি ৩৭ আর ২৯. বাকি কয়েকটি refused: Pod থেমে গেছে, কিন্তু খবর পুরো ছড়ায়নি।
  • D: তিনটি মিলে গ্রাহকের চোখে ব্যর্থতা শূন্য। Probe গরম Pod-কে বাইরে রাখে। Drain থামার ফাঁকটি ঢাকে। Replica জায়গা দেয়।

D সেটআপের মূল অংশ এই। পুরো manifest kubectl apply --dry-run=server পাস করেছে। মনে রাখবেন, periodSeconds: 1 এখানে দ্রুত পরীক্ষার জন্য; production-এ সাধারণত এত ঘন probe দরকার হয় না।

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: abcl-cc
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: web
        image: python:3.12-alpine
        imagePullPolicy: IfNotPresent
        command: ["python", "-u", "/scripts/app.py"]
        env:
        - {name: WARMUP_S, value: "5"}
        - {name: DRAIN_S, value: "3"}
        - {name: WORK_ROUNDS, value: "0"}
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet: {path: /ready, port: 8080}
          periodSeconds: 1
          failureThreshold: 1
        volumeMounts:
        - {name: s, mountPath: /scripts}
        resources:
          requests: {cpu: 50m, memory: 32Mi}
          limits: {cpu: 200m, memory: 64Mi}
      volumes:
      - name: s
        configMap: {name: cc-scripts}

DRAIN_S: "3" app-এর নিজের সেটিং: SIGTERM পেয়ে ৩ সেকেন্ড পরে থামে। terminationGracePeriodSeconds: 10 তার ওপরে নিরাপদ সীমা। একই কাজ preStop hook দিয়েও করা যায় (পর্ব ৪৩)। Service কীভাবে ready Pod বেছে নেয়, তা পর্ব ৪৫-এ দেখেছেন।

Pod মোছার ঠিক পরের মুহূর্তগুলো D সেটআপে এমন দেখায়:

$ kubectl delete pod web-79bbbc46f5-5cb4g
pod "web-79bbbc46f5-5cb4g" deleted

# 1 second later (AGE column removed)
NAME                   READY   STATUS
web-79bbbc46f5-5cb4g   0/1     Terminating
web-79bbbc46f5-6wkw9   0/1     ContainerCreating
web-79bbbc46f5-7tpxd   1/1     Running
web-79bbbc46f5-bv9p8   1/1     Running

# about 4 seconds later
web-79bbbc46f5-5cb4g   0/1     Completed
web-79bbbc46f5-6wkw9   0/1     Running
web-79bbbc46f5-7tpxd   1/1     Running
web-79bbbc46f5-bv9p8   1/1     Running

# about 8 seconds later
web-79bbbc46f5-6wkw9   1/1     Running
web-79bbbc46f5-7tpxd   1/1     Running
web-79bbbc46f5-bv9p8   1/1     Running

$ kubectl get endpointslices
NAME        ADDRESSTYPE   PORTS   ENDPOINTS                          AGE
web-twrcx   IPv4          8080    10.1.1.215,10.1.1.219,10.1.1.220   15m

# the traffic client, running all this time
sent 600 {'ok': 600}
errors per second: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
success 100.0%  p50 2 ms  p99 3 ms

প্রথম ছবিতে মোছা Pod 0/1: সে আর traffic-এর তালিকায় নেই, তবে drain-এর জন্য জীবিত। নতুন Pod তখন container বানাচ্ছে। দ্বিতীয় ছবিতে পুরোনোটি থেমেছে, আর নতুনটি Running হলেও 0/1: সে গরম হচ্ছে, তাই probe তাকে বাইরে রেখেছে। এই পার্থক্যটিই C সেটআপের ২৯–৩৭টি 503 মুছে দিয়েছে। তৃতীয় ছবিতে আবার তিনটি ready. পুরো সময় দুটি Pod কাজ করেছে, আর client-এর হিসাবে একটি ব্যর্থতাও নেই।

পরীক্ষার অবস্থা
ধাপ
Replica
Probe + drain
Success
Hypothesis
  1. Steady state মাপা। Pod না মুছে আগে স্বাভাবিক সংখ্যা জানা দরকার। ২০ req/s-এ সব সফল, p99 কয়েক ms. এই বেসলাইন ছাড়া পরে তুলনা করার কিছু থাকে না।
  2. Hypothesis লেখা। “একটি Pod মুছলেও ৩০ সেকেন্ডে success ≥ ৯৯%.” দাবিটি মাপা যায়, আর ভুল প্রমাণ করা যায়।
  3. সীমা ঠিক করা। Blast radius: নিজের namespace, একটি Pod, বানানো traffic. Abort: টানা ১০ সেকেন্ড error > ৫০%. এই ছোট পরীক্ষায় থামানোর code রাখা হয়নি; এই run-এ সবচেয়ে লম্বা error-এর ধারা ছিল ৮ সেকেন্ড, সীমা পার হয়নি।
  4. ব্যর্থতা ঢোকানো। প্রায় ১০ সেকেন্ডে kubectl delete pod. শুধু একটি Pod, শুধু একবার।
  5. ফল: hypothesis ভাঙল। Success ৭৫.৭%, ১৪৬টি ব্যর্থ। ভাগ্যিস এটি পরীক্ষা ছিল। প্রতি deploy-এ বা node বদলে একই ঘটনা ঘটত।
  6. সমাধান ১: replica ৩. Success ৯২.৩% আর ৯৫.০%. ভালো, কিন্তু দাবি পূরণ হয়নি। এখন কারণ পরিষ্কার: গরম না হওয়া Pod traffic পাচ্ছে।
  7. সমাধান ২: probe আর drain. একই পরীক্ষা, আগের মতোই Pod মোছা হলো। এবার ব্যর্থতা শূন্য, success ১০০%, hypothesis টিকল। একবার নয়, প্রতি run-এ।
  8. চিরস্থায়ী করা। পরীক্ষাটি pipeline বা নিয়মিত সূচিতে বসান। নতুন release-এ কেউ probe মুছে দিলে পরীক্ষাই ধরবে। Incident থেকে শেখা একটি পরীক্ষা এভাবেই regression test হয়ে যায়।

একটি error-এর দাম মাপা যায়। ২০ req/s-এ ৩০ দিনে মোট অনুরোধ প্রায় ৫.১৮ কোটি। ৯৯.৯% SLO-তে error budget ৫১,৮৪০. A সেটআপে প্রতি মোছায় ১৪৬ ব্যর্থ। মাসে ৩০ বার Pod বদলালে budget-এর ৮.৪% শুধু এতেই শেষ। (অংশ ৭-এর script-এ হিসাবটি আছে।)

৪ · ধীর হওয়াও ব্যর্থতা — latency injection, game day আর টুল

Pod মরে যাওয়া সহজ ব্যর্থতা: Service সেটি বাদ দেয়। কঠিন ব্যর্থতা হলো ধীর dependency: সে আছে, health check পাস করে, কিন্তু উত্তর দেয় দেরিতে। Latency injection এই অবস্থা বানায়।

client আর web-এর মাঝে ৩০ লাইনের একটি ছোট Python proxy বসানো হলো, যা প্রতিটি উত্তর DELAY_MS পরিমাণ আটকে রাখে। Client-এর timeout ২৫০ ms, হার ২০ req/s, সময় ১০ সেকেন্ড। প্রতিবার proxy-র Pod বদল শেষ হওয়ার পরে মাপা হয়েছে।

$ kubectl set env deploy/delayproxy DELAY_MS=0
$ kubectl exec client -- python /scripts/traffic.py http://delayproxy/ 20 10 0.25
sent 200 {'ok': 200}
errors per second: 0 0 0 0 0 0 0 0 0 0
success 100.0%  p50 5 ms  p99 8 ms

$ kubectl set env deploy/delayproxy DELAY_MS=100
$ kubectl exec client -- python /scripts/traffic.py http://delayproxy/ 20 10 0.25
sent 200 {'ok': 200}
errors per second: 0 0 0 0 0 0 0 0 0 0
success 100.0%  p50 106 ms  p99 109 ms

$ kubectl set env deploy/delayproxy DELAY_MS=300
$ kubectl exec client -- python /scripts/traffic.py http://delayproxy/ 20 10 0.25
sent 200 {'timeout': 200}
errors per second: 20 20 20 20 20 20 20 20 20 20
success 0.0%  p50 None ms  p99 None ms

দেরি নেই: p50 5 ms. ১০০ ms ঢোকালে p50 106 ms, কিন্তু success 100.0%: সিস্টেম ধীর, তবে timeout-এর ভেতরে। ৩০০ ms ঢোকালে success 0.0%, timeout ২০০টির ২০০টিই। দেরি বেড়েছে তিন গুণ, আর সেবা ১০০% থেকে শূন্য। কোনো Pod মরেনি, কোনো process crash করেনি, কোনো health check লাল হয়নি। Timeout-এর সীমা পার হতেই ধীরতা হয়ে গেল পুরো ব্যর্থতা। এই ঢালু নয়, খাড়া আচরণটিই পরীক্ষায় ধরা দরকার।

এখানেই ধীরতা বিপজ্জনক। Caller অপেক্ষা করে, তার thread আর connection আটকে থাকে, আর timeout-এর পরে retry করলে চাপ দ্বিগুণ হয়। এভাবে ছোট দেরি retry storm হয়ে পুরো সিস্টেম ফেলে দিতে পারে। সমাধান: কম timeout, exponential backoff-সহ সীমিত retry, circuit breaker আর load shedding. সেগুলোর বিস্তারিত System Design-এর resilience পর্বে।

Game day — মানুষের পরীক্ষা

Chaos টুল সিস্টেম পরীক্ষা করে। Game day পরীক্ষা করে মানুষ আর প্রক্রিয়া: alert কি বাজল, on-call কি runbook খুঁজে পেলেন, access কাজ করল, দলের মধ্যে কথা ঠিকভাবে গেল? পর্ব ২৪-এ DR-এর মহড়া দেখেছেন। একটি ভালো game day-এর ক্রম:

  • আগে: লক্ষ্য, পরিস্থিতি, সময়, দায়িত্ব আর থামার নিয়ম লিখুন। প্রথমবার টেবিলে আলোচনা দিয়ে শুরু করুন।
  • সময়ে: একজন পরিচালক ব্যর্থতা ঢোকান, একজন লেখেন, on-call দল আসল incident-এর মতো সাড়া দেন।
  • পরে: Blameless পর্যালোচনা, আর প্রতিটি শিক্ষার জন্য দায়িত্ব ও তারিখসহ কাজ।

টুল — ধারণায়

টুলকোথায়কী করে
Chaos Mesh, LitmusChaosKubernetes (CNCF প্রকল্প)CRD দিয়ে Pod, network, I/O ত্রুটি ঘোষণা; সূচি আর স্বয়ংক্রিয় যাচাই
AWS Fault Injection ServiceAWSInstance, API, network-এ পরীক্ষা, stop condition হিসেবে alarm
Azure Chaos StudioAzureService বা agent-ভিত্তিক fault injection
Google CloudGoogle Cloudলেখার সময়ে নিজস্ব সুবিধা service-ভেদে আলাদা; অনেক দল Kubernetes-এর টুল বা নিজের proxy ব্যবহার করে
Toxiproxy, Envoy fault filterNetwork proxyআমাদের ছোট proxy-র পরিণত রূপ: দেরি, বিচ্ছিন্নতা, error ঢোকানো

টুলের নাম আর অবস্থা দ্রুত বদলায়, তাই ব্যবহারের আগে নথি দেখে নিন। এগুলো cluster-wide ক্ষমতা নিতে পারে, তাই RBAC আর namespace সীমা (পর্ব ৪৯) আগে ঠিক করুন।

৫ · Load test-এর ধরন আর coordinated omission

নতুন সেতু খোলার আগে প্রকৌশলীরা ভারী ওজন চাপিয়ে তার সহ্যক্ষমতা মেপে নেন। Load test সফটওয়্যারের সেই পরীক্ষা। প্রশ্ন বদলালে পরীক্ষাও বদলায়:

ধরনচাপের আকারযে প্রশ্নের উত্তর দেয়সময়
Smokeখুব কম, কয়েকজনScript আর সিস্টেম মূল পথে চলছে তো?কয়েক মিনিট
Loadপ্রত্যাশিত চূড়া পর্যন্তSLO কি চূড়াতেও টেকে?আধা ঘণ্টা থেকে কয়েক ঘণ্টা
Stressচূড়ার ওপরে, ধাপে ধাপেKnee কোথায়? ভাঙলে কীভাবে ভাঙে?কয়েক ঘণ্টা পর্যন্ত
Soakমাঝারি, টানাMemory leak, ভরে যাওয়া disk, ফুরানো connection?বহু ঘণ্টা বা কয়েক দিন
Spikeহঠাৎ কয়েক গুণ, তারপর নামাধাক্কা সয়ে স্বাভাবিকে ফেরে কি?কয়েক মিনিট

সময়গুলো মোটা দাগের প্রচলিত ধারণা। আসল উত্তর আপনার ঝুঁকি আর traffic ঠিক করে। ফল বিশ্বাসযোগ্য করতে চারটি নিয়ম: production-এর মতো data ও আকার রাখুন, load generator নিজে যেন bottleneck না হয়, গড় নয় percentile দেখুন (tail latency), আর test শুরুর warm-up অংশ আলাদা রাখুন।

Closed বনাম open — দুই রকম ভিড়

ব্যাংকের কাউন্টার ভাবুন। একজন cashier-এর সামনে পাঁচজন নিয়মিত গ্রাহক: একজনের কাজ শেষ হলে তবেই পরের জন এগোন। Cashier ধীর হলে গ্রাহকরাও ধীরে আসেন। এটি closed workload. এবার ঈদের আগের কমলাপুর স্টেশন: কাউন্টার ধীর হলেও মানুষ আসতেই থাকেন, সারি লম্বা হতে থাকে। এটি open workload.

বেশিরভাগ public web service-এর নতুন ব্যবহারকারী প্রায় open: তাঁরা জানেন না আপনার server ধীর কি না। অথচ অনেক জনপ্রিয় load tool-এর সহজ ব্যবহার closed: N-টি worker, প্রতিটি উত্তর পেলে তবে পরেরটি। Closed tool চাপের মধ্যে server ধীর হলে নিজেই পাঠানো কমিয়ে দেয়। Server যেন গোপনে client-কে বলে, “একটু দাঁড়ান”, আর client মেনে নেয়। আসল ব্যবহারকারী মানেন না।

এর ফল coordinated omission, Gil Tene-র দেওয়া নাম। Client ঠিক সেই মুহূর্তের মাপগুলো নেয় না, যখন সিস্টেম সবচেয়ে খারাপ ছিল। নিচের simulator-এ একই server, একই ৫ সেকেন্ডের থেমে যাওয়া, শুধু মাপার ধরন আলাদা।

// Open vs closed load against one server. ASSUMED: 100 req/s for 60 s, service 5 ms,
// one connection, and the server freezes from t = 30 s to t = 35 s.
const RATE = 100, SECS = 60, SERVICE = 5, STALL_FROM = 30000, STALL_TO = 35000;

function finish(start) {                 // when does a request begun at `start` finish?
  const s = start >= STALL_FROM && start < STALL_TO ? STALL_TO : start;
  return s + SERVICE;
}
function closed() {        // next request waits for the previous one to come back
  const out = []; let free = 0;
  for (let i = 0; i < RATE * SECS; i++) {
    const planned = i * 1000 / RATE, start = Math.max(planned, free);
    if (start >= SECS * 1000) break;
    free = finish(start); out.push(free - start);   // clock starts when SENT
  }
  return out;
}
function open() {          // requests leave on schedule, whatever the server is doing
  const out = []; let free = 0;
  for (let i = 0; i < RATE * SECS; i++) {
    const planned = i * 1000 / RATE, start = Math.max(planned, free);
    free = finish(start); out.push(free - planned); // clock starts when PLANNED
  }
  return out;
}
function show(name, a) {
  const s = [...a].sort((x, y) => x - y), p = q => s[Math.floor(s.length * q)];
  const mean = a.reduce((x, y) => x + y, 0) / a.length;
  console.log(name.padEnd(7), String(a.length).padStart(5), mean.toFixed(1).padStart(7),
    String(p(0.5)).padStart(5), String(p(0.99)).padStart(6),
    String(p(0.999)).padStart(6), String(s[s.length - 1]).padStart(6));
}
console.log("model   count  mean ms  p50    p99  p99.9    max");
show("closed", closed());
show("open", open());

আউটপুট:

model   count  mean ms  p50    p99  p99.9    max
closed   6000     5.8     5      5      5   5005
open     6000   422.1     5   4710   4980   5005

দুই সারিতেই সর্বোচ্চ 5005 ms: থেমে যাওয়া দুই পক্ষই দেখেছে। পার্থক্য কতজন দেখেছে। Closed client থামার সময়ে মাত্র একটি অনুরোধ আটকে রেখেছিল, তাই ৬০০০-এর মধ্যে একটি নমুনাই খারাপ। তার p99 5. Open মডেলে থামার ৫ সেকেন্ডে পরিকল্পিত প্রায় ৫০০টি অনুরোধ আটকে যায়, আর সময় গোনা হয় পরিকল্পিত মুহূর্ত থেকে। তাই p99 4710, প্রায় ৯৪২ গুণ। গড়ও 5.8 থেকে 422.1.

নিজে বদলে দেখুন: STALL_TO ৩০৫০০ করলে কী হয়? RATE ২০ করলে open-এর p99 কি বাড়ে না কমে?

ব্যবহারিক সমাধান: Open-model executor ব্যবহার করুন, যেমন k6-এর constant-arrival-rate, wrk2, বা Gatling-এর open injection. আর latency গুনুন পরিকল্পিত সময় থেকে, পাঠানোর সময় থেকে নয়। আমাদের পরের অংশের client ঠিক তাই করে। আসল ব্যবহারের আগে tool-এর নথি দেখে নিন, version-ভেদে নাম বদলায়।

৬ · আসল load run — knee খোঁজা

এবার সত্যিকারের চাপ। Server: একই Python app, একটি replica, প্রতি অনুরোধে কিছু CPU-র কাজ, আর limits.cpu: 300m. Client: একটি ছোট Pod, Python threads, প্রতি ধাপ ৫ সেকেন্ড, মোট এক মিনিটের কম। Open model: অনুরোধ পরিকল্পিত সময়ে ছাড়া হয়, latency গোনা হয় সেই সময় থেকে। Timeout ৩ সেকেন্ড।

আমরা খুঁজছি knee: সেই বাঁক, যার পরে সামান্য বাড়তি চাপেই latency লাফিয়ে বাড়ে। দুইবার চালানো হলো। প্রথমবার সমান দূরত্বে অনুরোধ (even), দ্বিতীয়বার এলোমেলো Poisson আগমনে, যা আসল traffic-এর কাছাকাছি।

$ kubectl exec client -- python /scripts/load.py http://web/ even 5 30 60 75 85 95 110
offered  goodput    p50    p95    p99 failed
     30     30.2      4      8    128      0
     60     60.2      4      5      7      0
     75     75.1      4      5      5      0
     85     85.1      4      5     12      0
     95     89.0    192    370    392      0
    110     88.2    537   1173   1228      0

$ kubectl exec client -- python /scripts/load.py http://web/ poisson 5 30 60 75 85 95 110
offered  goodput    p50    p95    p99 failed
     30     30.9      4    109    334      0
     60     56.6      4      8     42      0
     75     69.9      4     15     56      0
     85     79.3      4     41     64      0
     95     88.1     64    189    217      0
    110     88.5    480   1043   1099      0

কলামগুলো: offered পাঠানোর লক্ষ্য (req/s), goodput সফল অনুরোধ প্রতি সেকেন্ডে, শেষে ms-এ latency. Goodput ~89-এ আটকে গেছে: এটিই এই server-এর ছাদ। ৩০ req/s-এর সারিতে p99 অদ্ভুত বেশি (128, 334): চাপ সবচেয়ে কম, অথচ tail লম্বা। এটি সম্ভবত run-এর শুরুর ঝাঁকুনি বা একই cluster-এর অন্য কাজের প্রভাব। আমরা কারণ মাপিনি, তাই warm-up-এর সারি সিদ্ধান্তে ধরিনি। আর সবসময় সব ধাপ একাধিকবার চালান।

Latency বনাম চাপ — আসল run-এর knee

p95 latency vs offered load, from the runs above

Latency বনাম চাপের লেখচিত্র, knee ৮৯ req/s-এর কাছে নিরাপদ এলাকা ছাদ ≈ ৮৯ 0 600 1200 30 60 90 120 চাপ (req/s) p95 (ms) Poisson even knee এখানে ৩০ req/s-এর শুরুর ঝাঁকুনি লেখচিত্রে রাখা হয়নি
ছবি ৬৮.১ — দুটি রেখা, একই ছাদ। এলোমেলো আগমনে latency আগে উঠতে শুরু করে।

ধাপে ধাপে Poisson run পড়ি। ρ এখানে utilisation, অর্থাৎ পাঠানো চাপ ÷ ৮৯.

Poisson run
Offered (req/s)
Goodput
ρ (÷ ৮৯)
p50 (ms)
p95 (ms)
p99 (ms)
  1. ৩০ req/s, শুরু। p50 মাত্র ৪ ms, কিন্তু p99 ৩৩৪. প্রথম ধাপের এই tail-কে আমরা warm-up ধরে সিদ্ধান্তের বাইরে রাখছি, আর সেটি লিখে রাখছি।
  2. ৬০ req/s, আরাম। ρ ০.৬৭. p95 ৮ ms. Server কাজের ফাঁকে ফাঁকে বিশ্রাম পাচ্ছে, সারি প্রায় নেই।
  3. ৭৫ req/s, এখনো ভালো। ρ ০.৮৪. p95 ১৫ ms, p99 ৫৬ ms. গুচ্ছ বেঁধে আসা অনুরোধ ছোট সারি বানাচ্ছে, কিন্তু সারি দ্রুত ফুরাচ্ছে।
  4. ৮৫ req/s, knee-র গোড়ায়। ρ ০.৯৬. p50 এখনো ৪ ms! গড় মানুষ কিছুই টের পান না। কিন্তু p95 ৪১ ms, ৭৫-এর প্রায় তিন গুণ। Tail আগে সতর্ক করে।
  5. ৯৫ req/s, ছাদ পার। ρ ১-এর ওপরে। Goodput ৮৮.১, অর্থাৎ যত এসেছে তত শেষ হচ্ছে না। p50-ও লাফিয়ে ৬৪ ms. সারি এখন স্থায়ী, সময়ের সাথে বাড়ে।
  6. ১১০ req/s, ডুবে যাওয়া। Goodput সেই ৮৮.৫. চাপ ১৬% বাড়ল, ফল একই, p95 ১,০৪৩ ms. ধাপটি মাত্র ৫ সেকেন্ডের বলে সারি এটুকুতে থেমেছে; টানা চললে timeout পর্যন্ত যেত।
  7. সিদ্ধান্ত। ছাদ ≈ ৮৯ req/s, knee প্রায় ৮৫ থেকে ৯৫-এর মধ্যে। পরিকল্পনার নিরাপদ মান ধরি ৭৫ req/s, যেখানে p99 ৫৬ ms. এটিই capacity plan-এর ইনপুট। ছাদ নয়, ছাদের নিচের নিরাপদ সংখ্যা।

দুটি লুকানো শিক্ষা। এক, ৮৫ req/s-এ even run-এর p95 5 ms, Poisson-এর 41 ms. গড় হার একই, শুধু আগমনের এলোমেলোতা আলাদা। সমান দূরত্বে পাঠানো test তাই সিস্টেমকে বেশি ভালো দেখায়। দুই, ছাদটি CPU limit-এর: এক অনুরোধে প্রায় ৩.৪ ms CPU (০.৩ ÷ ৮৯), আর 300m মানে প্রতি ১০০ ms-এ ৩০ ms. সেটি ফুরালে CPU throttling (পর্ব ৪৮)। তাই limit বদলালে capacity-র সংখ্যাও বদলাবে।

৭ · Little's law আর 1/(1−ρ) — knee-র গণিত

Knee কোনো রহস্য নয়। দুটি ছোট সূত্রেই এর ব্যাখ্যা আছে।

Little's law: L = λW
ভেতরে গড় সংখ্যা = আগমনের হার × ভেতরে গড় সময়

John Little ১৯৬১ সালে এর প্রমাণ প্রকাশ করেন। সূত্রটি আশ্চর্য সাধারণ: আগমন বা সেবার ধরন নিয়ে কোনো শর্ত নেই, শুধু সিস্টেম স্থির থাকতে হবে। লঞ্চঘাটে প্রতি মিনিটে ২ জন আসেন, প্রত্যেকে ১৫ মিনিট থাকেন, তবে ঘাটে গড়ে ৩০ জন।

আমাদের cluster-এ সূত্রটি যাচাই করা যায়। Closed client-এ N-টি worker, তাই ভেতরে থাকা অনুরোধ ঠিক N. আসল run:

$ kubectl exec client -- python /scripts/closed.py http://web/ 6 1 2 4 8 16
    N  X(r/s) W(avg ms)    X*W
    1    90.0      11.2   1.00
    2    90.3      22.1   2.00
    4    89.3      44.8   4.00
    8    89.3      89.9   8.03
   16    87.7     184.0  16.13

শেষ কলাম মাপা X × W. প্রতিটি সারিতে সেটি N-এর সাথে প্রায় হুবহু মেলে (8.03, 16.13). এটি এই মাপের একটি বিশ্বাসযোগ্যতার পরীক্ষাও: না মিললে সন্দেহ করতে হতো tool-কে। দেখুন, worker ১৬ গুণ বাড়লেও throughput 90-এর কাছেই, শুধু W বাড়ল। ছাদে পৌঁছে বাড়তি চাপ মানে শুধু অপেক্ষা। আর closed client ছাদে গিয়েও ভেঙে পড়ে না, কারণ সে নিজে থেমে থাকে: open run-এর ১০০০ ms-এর সাথে তুলনা করুন।

Utilisation আর queueing delay

ρ (রো) হলো ব্যস্ততা: ρ = λ × S, যেখানে λ আগমনের হার, S গড় সেবার সময়। সবচেয়ে সরল সারির মডেল M/M/1-এ একটি অনুরোধের গড় মোট সময়:

W = S ÷ (1 − ρ)

১/(১ − ρ) অংশটিই knee. ρ ০.৫ হলে ২ গুণ, ০.৮ হলে ৫ গুণ, ০.৯ হলে ১০ গুণ, ০.৯৯ হলে ১০০ গুণ। সেবার সময় একই, বাড়ছে শুধু queueing delay. কেন? অনুরোধ এলোমেলোভাবে আসে। গুচ্ছ এলে সারি হয়, আর ফাঁকা সময়ে সেই সারি শোধ হয়। ব্যস্ততা যত বেশি, শোধ করার ফাঁকা সময় তত কম।

ঢাকার রাস্তা মাঝরাতে ৩০% ভরা থাকলে একটি রিকশা থামলেও কিছু হয় না। সকাল ৯টায় ৯৫% ভরা অবস্থায় সেই একই রিকশা থামলে পেছনে আধা কিলোমিটার জট, আর সেটি ছাড়তে লাগে অনেকক্ষণ। ধাক্কা একই, হাতে ফাঁকা জায়গা নেই।

এখন simulator. Seed-দেওয়া random দিয়ে ৩ লাখ অনুরোধ চালিয়ে সূত্রের সাথে মিলিয়ে দেখি। S বা তালিকা বদলে দেখুন।

// M/M/1 queue: latency vs utilisation, by formula and by a seeded simulation.
// ASSUMED: one server, mean service time S = 20 ms (so capacity 50 req/s).
const S = 20, N = 300000;
function mulberry32(a) {
  return () => { a |= 0; 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 exp = (rnd, mean) => -mean * Math.log(1 - rnd());

function simulate(rho) {                 // Lindley: wait(i+1) = max(0, wait(i) + s(i) - gap)
  const rnd = mulberry32(42), meanGap = S / rho, all = [];
  let wait = 0, sum = 0;
  for (let i = 0; i < N; i++) {
    const s = exp(rnd, S), total = wait + s;
    all.push(total); sum += total;
    wait = Math.max(0, total - exp(rnd, meanGap));
  }
  all.sort((a, b) => a - b);
  return { mean: sum / N, p99: all[Math.floor(N * 0.99)] };
}

const pad = (x, w) => String(x).padStart(w);
console.log("  rho  req/s  W=S/(1-rho)  sim mean  p99 formula  sim p99  in system");
for (const rho of [0.3, 0.5, 0.7, 0.8, 0.9, 0.95, 0.98]) {
  const W = S / (1 - rho), L = rho / (1 - rho), r = simulate(rho);
  console.log(pad(rho.toFixed(2), 5), pad(rho * 50, 6), pad(W.toFixed(0), 12),
    pad(r.mean.toFixed(0), 9), pad((W * Math.log(100)).toFixed(0), 12),
    pad(r.p99.toFixed(0), 8), pad(L.toFixed(1), 10));
}

আউটপুট:

  rho  req/s  W=S/(1-rho)  sim mean  p99 formula  sim p99  in system
 0.30     15           29        29          132      131        0.4
 0.50     25           40        40          184      186        1.0
 0.70     35           67        67          307      309        2.3
 0.80     40          100        99          461      458        4.0
 0.90     45          200       192          921      852        9.0
 0.95   47.5          400       368         1842     1678       19.0
 0.98     49         1000       880         4605     3750       49.0

ρ ০.৮ থেকে ০.৯ হলে চাপ বাড়ল মাত্র ১২.৫% (৪০ থেকে ৪৫ req/s), কিন্তু গড় latency 100 থেকে 200 ms, দ্বিগুণ। p99 গড়ের প্রায় ৪.৬ গুণ, কারণ এই মডেলে p99 = W × ln 100. উঁচু ρ-তে simulation সূত্রের চেয়ে কম দেখায় (ρ ০.৯৮-এ 880 বনাম 1000): সারি খুব ধীরে স্থির হয়, তাই ৩ লাখ নমুনাও কম। শেষ কলাম L = ρ ÷ (1 − ρ), যা Little's law থেকেই আসে।

আসল server M/M/1 নয়। আমাদের run-এ সেবার সময় প্রায় সমান ছিল, তাই knee আরও চোখা, ρ ০.৮৪-এও অনেক ভালো। আকৃতি একই, সংখ্যা আপনার সিস্টেম মাপেই পাবেন। সূত্র দিক দেখায়; load test সংখ্যা দেয়।

গণনা করে দেখি

পরীক্ষা আর ইন্টারভিউর ধরনের ছয়টি হিসাব, script দিয়ে করা। প্রতিটি লাইন আপনি বদলাতে পারেন।

// Worked numericals: Little's law, utilisation, and what a Pod delete costs.
const f = (x, d = 1) => x.toFixed(d);

// 1. Little's law: L = lambda * W
const lambda = 4140, W = 0.120;
console.log(`1. L = ${lambda} x ${W} = ${f(lambda * W)} requests inside`);

// 2. Check Little's law on a measured closed-loop row
const X = 89.3, Wm = 0.0899;
console.log(`2. X*W = ${X} x ${Wm} = ${f(X * Wm, 2)} (the run used N = 8)`);

// 3. Size a database pool: 2 queries per request, 5 ms each, keep the pool at 70%
const busy = lambda * 2 * 0.005;
console.log(`3. busy connections ${f(busy)}, pool ${Math.ceil(busy / 0.7)}`);

// 4. M/M/1: S = 20 ms. Traffic +10% from 40 to 44 req/s
const S = 0.020, wait = l => S / (1 - l * S) * 1000;
console.log(`4. rho ${f(40 * S, 2)}: ${f(wait(40), 0)} ms; rho ${f(44 * S, 2)}: ${f(wait(44), 0)} ms; ` +
  `+${f((wait(44) / wait(40) - 1) * 100, 0)}% latency`);

// 5. Largest utilisation that keeps mean latency at most 60 ms, and servers for 1000 req/s
const target = 0.060, rhoMax = 1 - S / target, perServer = rhoMax / S;
console.log(`5. rho max ${f(rhoMax, 3)}, ${f(perServer)} req/s per server, ` +
  `${Math.ceil(+(1000 / perServer).toFixed(6))} servers for 1000 req/s`);

// 6. A failed Pod delete against a 99.9% SLO at 20 req/s for a 30-day month
const budget = 20 * 86400 * 30 * 0.001, lost = 146;
console.log(`6. budget ${f(budget, 0)} failures; ${lost} per delete; ` +
  `30 deletes = ${f(100 * 30 * lost / budget)}% of the budget`);

আউটপুট:

1. L = 4140 x 0.12 = 496.8 requests inside
2. X*W = 89.3 x 0.0899 = 8.03 (the run used N = 8)
3. busy connections 41.4, pool 60
4. rho 0.80: 100 ms; rho 0.88: 167 ms; +67% latency
5. rho max 0.667, 33.3 req/s per server, 30 servers for 1000 req/s
6. budget 51840 failures; 146 per delete; 30 deletes = 8.4% of the budget
  • ১. ৪,১৪০ req/s আর গড় ১২০ ms হলে সিস্টেমে গড়ে প্রায় ৪৯৭টি অনুরোধ। Thread আর connection-এর আকার এই সংখ্যা ঠিক করে।
  • ২. আসল closed run-এর সারি: ৮৯.৩ × ০.০৮৯৯ = ৮.০৩, worker ছিল ৮.
  • ৩. প্রতি অনুরোধে ২টি query, প্রতিটি ৫ ms: গড়ে ৪১.৪টি connection ব্যস্ত। ৭০% ব্যস্ততায় রাখতে pool ৬০. “প্রতি instance ২০টি” এমন অনুমানের চেয়ে Little's law অনেক ভালো (পর্ব ২২)।
  • ৪. ১০% বাড়তি traffic, ৬৭% বাড়তি latency: উঁচু ব্যস্ততার দাম।
  • ৫. লক্ষ্য গড় ৬০ ms হলে ρ সর্বোচ্চ ১ − ২০ ÷ ৬০ = ০.৬৬৭, তাই ১,০০০ req/s-এ ৩০টি server, প্রতিটিতে গড় ঠিক ৬০ ms. এই মডেল প্রতিটি server-কে আলাদা সারি ধরে।
  • ৬. SLO ৯৯.৯% হলে budget ৫১,৮৪০, আর ৩০ বার ১৪৬টি ব্যর্থতায় ৮.৪% খরচ।

৮ · Capacity planning — forecast, headroom আর N+1

বিয়েবাড়ির ক্যাটারিং ভাবুন। গত বছর ২০০ জন খেয়েছিলেন, এবার আত্মীয় বেড়েছে, দাওয়াত ৩০০. বাবুর্চি ৩৩০ জনের রান্না ধরেন, আর একটি চুলা নষ্ট হলেও যেন চলে, সে ব্যবস্থা রাখেন। Capacity plan হুবহু এই চিন্তা, চারটি ধাপে:

  • Demand forecast: আজকের চূড়া × স্বাভাবিক বৃদ্ধি × জানা ঘটনার গুণক। গড় নয়, চূড়া।
  • প্রতি ইউনিটের নিরাপদ ক্ষমতা: বিজ্ঞাপনের সংখ্যা নয়, আপনার load test-এর knee-র নিচের মান।
  • Headroom: forecast-এর ভুল আর হঠাৎ ধাক্কার জন্য margin.
  • N+1: একটি ইউনিট হারালেও চলবে। Cloud-এ বড় ইউনিটটি হলো একটি পুরো zone (পর্ব ০৫)।

Zone-এর হিসাবের মূল সূত্র: z-টি zone থাকলে একটি হারানোর পরে (z − 1)-টিকে পুরো চাপ নিতে হবে। তাই প্রতি zone-এ চাপ ÷ (z − 1) ÷ নিরাপদ ক্ষমতা।

একটি zone হারালে কী থাকে

3 zones, 28 instances each, 4,140 req/s plan

তিন zone-এ চাপের বণ্টন, একটি zone হারানোর আগে ও পরে স্বাভাবিক: প্রতি instance ≈ ৪৯ req/sZone b বিকল: প্রতি instance ≈ ৭৪ req/s Zone a · ২৮Zone b · ২৮Zone c · ২৮Zone a · ২৮Zone c · ২৮ ১,৩৮০ req/s১,৩৮০ req/s১,৩৮০ req/s২,০৭০ req/s২,০৭০ req/s বিকল০ req/s নিরাপদ সীমা ৭৫ req/s, knee ১০০ req/s প্রতি instance
ছবি ৬৮.২ — স্বাভাবিক দিনের “ফাঁকা” ক্ষমতা অপচয় নয়, এটি zone হারানোর বিমা।

এখন একটি কাল্পনিক অনলাইন বাজার অ্যাপের ঈদ সেলের পরিকল্পনা। ধরে নিই load test বলেছে: এক instance-এর knee ১০০ req/s, নিরাপদ ৭৫ req/s. দাম ধরে নিই ঘণ্টায় $০.১০ (কাল্পনিক; আসল দাম provider-এর পাতায় দেখুন)।

পরিকল্পনার খাতা
ধাপ
চাপ (req/s)
Instance
Zone হারালে ρ
৭২ ঘণ্টার খরচ
  1. আজকের চূড়া। Metric থেকে পাওয়া সর্বোচ্চ ১,২০০ req/s. গড় নয়, আর এক মিনিটের গড়ে ঢাকা পড়া চূড়াও নয়।
  2. স্বাভাবিক বৃদ্ধি। সেল পর্যন্ত ১৫% বৃদ্ধি: ১,২০০ × ১.১৫ = ১,৩৮০.
  3. সেলের গুণক। গত ঈদে চূড়া স্বাভাবিকের ২.৫ গুণ ছিল: ১,৩৮০ × ২.৫ = ৩,৪৫০. ব্যবসার দলের সাথে বসে জানুন কোনো বড় ছাড় বা TV বিজ্ঞাপন আছে কি না।
  4. Margin ২০%. Forecast ভুল হতে পারে: ৩,৪৫০ × ১.২ = ৪,১৪০ req/s. এটিই পরিকল্পনার চাপ।
  5. Zone না ভাবলে। ৪,১৪০ ÷ ৭৫ = ৫৫.২, অর্থাৎ ৫৬টি instance, ৭২ ঘণ্টায় $৪০৩.২. ৫৬টি সমান ভাগে ৩ zone-এ থাকলে একটি zone হারালে বাকি ৩৭–৩৮টি প্রায় ১১০ req/s করে নিতে চায়, knee-র ওপরে।
  6. ৩ zone-এ N+1. ৪,১৪০ ÷ ২ ÷ ৭৫ = ২৭.৬, তাই প্রতি zone-এ ২৮, মোট ৮৪. বাড়তি ২৮টি instance-ই zone-বিমার দাম।
  7. সংখ্যা যাচাই। Zone হারালে ৫৬টি নেবে ৪,১৪০: প্রতিটিতে ৭৩.৯ req/s, knee-র ৭৩.৯%. এবার পাশের সীমা দেখুন: ৮৪ × প্রতি instance-এ vCPU কি quota-র ভেতরে? Subnet-এ IP আছে? Database কি এত connection নেবে? সবশেষে এই সংখ্যাতেই load test আর একটি zone বন্ধের game day.
  8. দাম আর সূচি। ৮৪ × $০.১০ × ৭২ = $৬০৪.৮. জানা ভিড়ে আগে থেকে min বাড়িয়ে রাখুন, সেল শুরুর অন্তত boot সময়ের বেশ আগে। সেল শেষে নামান।

এবার নিজে সংখ্যা বদলান: zone-এর সংখ্যা, margin, নিরাপদ ক্ষমতা।

// Capacity plan with zone-failure headroom. Change the numbers and run again.
// ASSUMED: every value is an example; PRICE is a round number, not a real price.
const today = 1200;      // peak req/s today
const growth = 0.15;     // organic growth until the sale
const event = 2.5;       // the sale multiplies the peak by this
const margin = 0.20;     // safety margin on the forecast
const safe = 75;         // req/s one instance serves below the knee (from a load test)
const knee = 100;        // req/s where latency explodes on one instance
const PRICE = 0.10;      // assumed $ per instance-hour
const HOURS = 72;        // length of the sale

const peak = today * (1 + growth) * event, plan = peak * (1 + margin);
const noZone = Math.ceil(plan / safe);
console.log(`forecast ${peak.toFixed(0)} req/s, plan ${plan.toFixed(0)} req/s`);
console.log(`ignoring zone failure: ${noZone} instances, $${(noZone * PRICE * HOURS).toFixed(1)}`);
console.log("zones per-zone total normal/safe lost-zone rho(knee) cost$ extra");
for (const z of [2, 3, 4, 6]) {
  const perZone = Math.ceil(plan / (z - 1) / safe);   // z-1 zones must carry the plan
  const total = perZone * z, left = total - perZone;
  console.log(String(z).padStart(5), String(perZone).padStart(8), String(total).padStart(5),
    `${(100 * plan / (total * safe)).toFixed(1)}%`.padStart(11),
    `${(100 * plan / (left * knee)).toFixed(1)}%`.padStart(19),
    (total * PRICE * HOURS).toFixed(1).padStart(6), `+${total - noZone}`.padStart(5));
}
// over-provisioning: keep the sale fleet all month, or only for the sale?
const quiet = 3 * Math.ceil(today * (1 + margin) / 2 / safe);
const month = 720, big = 3 * Math.ceil(plan / 2 / safe);
const always = big * PRICE * month;
const sched = big * PRICE * HOURS + quiet * PRICE * (month - HOURS);
console.log(`quiet fleet ${quiet}, sale fleet ${big}`);
console.log(`sale fleet all month $${always.toFixed(0)}, scheduled $${sched.toFixed(0)}, ` +
  `saved $${(always - sched).toFixed(0)}`);

আউটপুট:

forecast 3450 req/s, plan 4140 req/s
ignoring zone failure: 56 instances, $403.2
zones per-zone total normal/safe lost-zone rho(knee) cost$ extra
    2       56   112       49.3%               73.9%  806.4   +56
    3       28    84       65.7%               73.9%  604.8   +28
    4       19    76       72.6%               72.6%  547.2   +20
    6       12    72       76.7%               69.0%  518.4   +16
quiet fleet 30, sale fleet 84
sale fleet all month $6048, scheduled $2549, saved $3499

normal/safe স্বাভাবিক দিনে নিরাপদ ক্ষমতার কত অংশ ব্যবহার হচ্ছে; lost-zone একটি zone হারানোর পরে knee-র তুলনায় ব্যস্ততা। ২ zone-এ 112টি, ৩ zone-এ 84, ৬ zone-এ 72: বেশি zone মানে প্রতিটি হারানোর ভাগ ছোট, তাই বাড়তি কম। ২ zone-এ প্রায় অর্ধেক ক্ষমতা অলস থাকে, আর সাধারণ হিসাবে ৩ zone ভালো মাঝামাঝি। তবে zone-পার traffic-এর খরচ, আর provider-এর zone-সংখ্যাও (region-ভেদে আলাদা) সিদ্ধান্তে আসে।

৯ · Autoscaling কোথায় থামে, আর বাড়তি ক্ষমতার দাম

“Cloud তো elastic, plan কেন লাগবে?” পর্ব ১৭-তেই উত্তরের বড় অংশ দেখেছেন। সেটি মনে করিয়ে দিই, এবার এই পর্বের দৃষ্টিতে:

  • প্রতিক্রিয়ার দেরি: Metric পড়া, সিদ্ধান্ত, boot, warm-up মিলিয়ে কয়েক মিনিট। ধাক্কা তার আগেই আসে। পর্ব ১৭-র ঈদ simulator-এ autoscaler ভুল না করেও ২,৩৪,০০০ অনুরোধ হারিয়েছিল।
  • Knee-র কাছে সময় নেই: Target ৯০% রাখলে ১/(১ − ρ) আগেই ১০ গুণে। নতুন instance আসার আগে সারি জমে যায়। তাই target-ও capacity-র প্রশ্ন।
  • সীমা: Provider-এর হাতে ক্ষমতা, quota, subnet-এর IP, max মান। Zone বিকল হলে অনেকেই একসাথে অন্য zone-এ instance চান, তখন সেটি না-ও মিলতে পারে।
  • পেছনের স্তর: Web স্তর বাড়লে database-এর চাপ বাড়ে, আর সে সাধারণত নিজে নিজে horizontally বাড়ে না। Bottleneck শুধু সরে যায়।
  • Scale up নিজেই dependency: Scale-এর পথ যদি সেই নষ্ট অংশের ওপর নির্ভর করে (image registry, config service), তবে দরকারের সময়েই আটকে যায়।

এজন্যই দৃঢ় সিস্টেম ব্যর্থতার সময়ে নতুন কিছু চালু করার ওপর নির্ভর করে না: আগে থেকে যথেষ্ট ক্ষমতা হাতে রাখে। Autoscaling রোজকার ওঠানামা সামলায়; zone-ব্যর্থতা আর জানা ধাক্কা সামলায় পূর্ব পরিকল্পনা।

Over-provisioning-এর দাম

কিন্তু “সব সময় অনেক রাখি” কোনো উত্তর নয়। ওপরের script-এর শেষ লাইন: সেলের ৮৪টি সারা মাস রাখলে $৬,০৪৮. মাসের বাকি সময়ে ৩০টি আর সেলের ৭২ ঘণ্টায় ৮৪টি রাখলে $২,৫৪৯. একই নিরাপত্তা, $৩,৪৯৯ কম। অঙ্কগুলো কাল্পনিক দামে, কিন্তু অনুপাতটি আসল শিক্ষা।

Under-provisioningঠিক মাপেOver-provisioning
ব্যবহারকারীধীর, ব্যর্থ, চলে যাওয়াSLO-র ভেতরেভালো অভিজ্ঞতা, যার মূল্য SLO-র পরে কমে
বিলকম, কিন্তু আয় হারায়ন্যায্যঅলস ক্ষমতার ভাড়া
ঝুঁকিচূড়ায় ভেঙে পড়াপরিকল্পনা নিয়মিত হালনাগাদ লাগেআসল সীমা অজানা থেকে যায়

তাই লক্ষ্য হলো সবচেয়ে কম খরচে SLO. হিসাবের পথ: খরচ ÷ সফল অনুরোধ। Rightsizing, spot আর commitment-এর কৌশল আসবে পর্ব ৭২ আর ৭৩-এ। Scheduled scaling, পরিষ্কার পরিকল্পনা আর নিয়মিত load test মিলিয়েই “ঠিক মাপ” পাওয়া যায়।

আরও গভীরে: Kingman-এর অনুমান (১৯৬১) বলে সারির অপেক্ষা ≈ S × ρ/(1 − ρ) × (Cₐ² + Cₛ²)/2, যেখানে C হলো আগমন আর সেবার সময়ের এলোমেলোতা। তাই even run-এ knee চোখা, Poisson-এ আগে শুরু। আবার একাধিক worker একই সারি থেকে কাজ নিলে (M/M/c, Erlang-এর গণিত) একই ρ-তে অপেক্ষা অনেক কম, এজন্য বড় ভাগ করা pool বেশি ব্যস্ত রাখা যায়। Neil Gunther-এর Universal Scalability Law দেখায়, instance বাড়লে সমন্বয়ের খরচে throughput একসময় উল্টো কমতে পারে। আর Dean ও Barroso-র “The Tail at Scale” (২০১৩) দেখায়, বহু service-এ ছড়ানো একটি অনুরোধের অভিজ্ঞতা p99 দিয়েই ঠিক হয়।

পরীক্ষা ও ইন্টারভিউতে:

  • Chaos পরীক্ষার ধাপ? Steady state, hypothesis, blast radius ও abort, ব্যর্থতা ঢোকানো, মাপ, সমাধান, স্বয়ংক্রিয় করা।
  • Little's law? L = λW; স্থির সিস্টেমে, বণ্টন যাই হোক। ৫০০ req/s, ২০০ ms হলে L = ১০০.
  • M/M/1-এ ρ ০.৮ থেকে ০.৯ হলে? গড় সময় ৫S থেকে ১০S, অর্থাৎ দ্বিগুণ।
  • Coordinated omission? ধীর server client-কে থামায়, তাই খারাপ সময়ের মাপ বাদ পড়ে। সমাধান open load আর পরিকল্পিত সময় থেকে গোনা।
  • ৩ zone-এ N+1 হলে স্বাভাবিক ব্যবহার সর্বোচ্চ? নিরাপদ ক্ষমতার (z − 1) ÷ z = ৬৬.৭%.

১০ · AI-এর যুগে reliability যাচাই

AI খুব দ্রুত k6 script, chaos manifest আর capacity-র হিসাব লিখে দেয়। সমস্যা হলো, এই কাজের ভুল production-এ সরাসরি আঘাত করে, আর ভুল সংখ্যা দেখতে বিশ্বাসযোগ্য লাগে।

AI-এর ভুলবিপদযাচাই
kubectl delete pod --all বা বড় selectorBlast radius পুরো serviceআগে --dry-run=server, নির্দিষ্ট নাম আর namespace
Closed load tool, গড় latency-র রিপোর্টCoordinated omission, ভুল আত্মবিশ্বাসOpen model, p95/p99, error একসাথে
Abort আর rollback নেইপরীক্ষা নিজেই incidentশুরুর আগে থামার শর্ত লেখা
“CPU ৮৫%-এ target” ধরে নেওয়াআপনার knee অন্য জায়গায়নিজের load test
গড় চাপে হিসাব, zone হারানো ভুলে যাওয়াচূড়ায় ঘাটতিচূড়া আর (z − 1) দিয়ে আবার হিসাব
বানানো flag, Chaos CRD-র ভুল field, পুরোনো APIপরীক্ষা চলেই না, বা ভুল জিনিস ভাঙেঅফিসিয়াল নথি, throwaway cluster
Test আর বাড়তি fleet-এর খরচ না ধরাবড় fleet-এর চমকে দেওয়া বিলBudget alert, test শেষে clean-up

AI-কে সিদ্ধান্ত নয়, যাচাইযোগ্য খসড়া চাইতে হবে। লক্ষ্য করুন, prompt-এ আসল মাপ দেওয়া আছে আর গোপন কিছু নেই:

আমার service: ৩টি zone-এ ১২টি instance. Load test-এ এক instance-এর goodput ছাদ ৮৯ req/s, ৭৫ req/s-এ p99 ৫৬ ms. SLO: ৯৯.৯% success, p99 < ২০০ ms. আগামী সেলে চূড়া ধরা হচ্ছে ৩,৪৫০ req/s. ১. একটি zone হারানো ধরে capacity plan করো, প্রতিটি ধাপের সূত্রসহ। ২. আমি কোন অনুমানগুলো যাচাই করিনি, তার তালিকা দাও। ৩. একটি open-model load test-এর খসড়া দাও, ধাপ, সময় আর থামার শর্তসহ। ৪. কোনো destructive কমান্ড দিলে আগে সতর্ক করো। কোনো পণ্যের দাম, quota বা flag নিয়ে নিশ্চিত না হলে "যাচাই দরকার" লেখো। ৫. বাস্তব data ছাড়া কোনো সংখ্যা বানিও না।

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

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর উত্তর খুলুন। এগুলো কোনো একটি টুলের নয়, প্রকৌশলের বিচারবোধের প্রশ্ন।

প্র ০১ইচ্ছা করে production-এ server বন্ধ করা তো পাগলামি মনে হয়। Netflix কেন করত, আর আপনার দল কি করবে?

Netflix-এর যুক্তি ছিল সহজ: AWS-এ যাওয়ার পরে instance হারানো কোনো দূরের ঝুঁকি নয়, স্বাভাবিক ঘটনা। ব্যর্থতা রাত ৩টায় আসবে, নাকি অফিসের সময়ে, প্রকৌশলীরা উপস্থিত থাকতে? Chaos Monkey প্রথমটিকে দ্বিতীয়টিতে বদলাল। আর এর গভীরতর ফল ছিল নকশায়: প্রত্যেক দল জানত তাদের instance যেকোনো দিন মরবে, তাই stateless নকশা আর redundancy আলাদা করে বলতে হতো না।

২০১১ সালের এপ্রিলে AWS-এর US East region-এ বড় EBS সমস্যা হয়। Netflix নিজের ব্লগে লিখেছিল, তারা অনেকটা টিকে যায়, আর তাদের ব্যর্থতা-মুখী নকশার একটি অংশকে কৃতিত্ব দেয়। সব কৃতিত্ব এক টুলের নয়, তবে মানসিকতার প্রমাণ হিসেবে ঘটনাটি প্রায়ই বলা হয়। পরে তারা Chaos Kong দিয়ে পুরো region সরানোর মহড়াও করেছে।

কিন্তু Netflix-এর শেষ ধাপ আপনার প্রথম ধাপ নয়। তাদের ছিল গভীর observability, পরিণত automation আর traffic সরানোর ক্ষমতা। একটি ছোট দলের পথ: আগে staging-এ, এক Pod, এক সেবা। তারপর production-এর এক ক্ষুদ্র অংশে, মানুষের চোখের সামনে, অফিস সময়ে, abort সহ। Payment-এর মতো স্পর্শকাতর পথ শেষে। Principles of Chaos-ও production-কে লক্ষ্য বলে, প্রথম দিনের নির্দেশ নয়।

আর একটি উল্টো দিক আছে: staging-এ পাস করা chaos পরীক্ষা আসল আত্মবিশ্বাস দেয় না, কারণ traffic, data আকার আর config আলাদা। এজন্যই শেষে production-এ যাওয়ার টান থাকে।

মূল উপলব্ধি: Chaos-এর মূল্য ভাঙায় নয়, ব্যর্থতাকে নিজের পছন্দের সময়ে আনায়। আপনার পরিণতির সাথে blast radius বড় করুন।

প্র ০২একটি load test-এর রিপোর্টে p99 সুন্দর, অথচ ব্যবহারকারীরা অভিযোগ করছেন। কীভাবে সম্ভব?

প্রথম সন্দেহ coordinated omission. Gil Tene তাঁর “How NOT to Measure Latency” বক্তৃতাগুলোতে (২০১০-এর দশকের প্রথম দিকে) দেখান, বহু জনপ্রিয় tool সবচেয়ে খারাপ মুহূর্তের নমুনা হারিয়ে ফেলে। তিনি HdrHistogram তৈরি করেন, আর এই ধারণা থেকে wrk2-এর মতো constant-rate tool আসে। Bianca Schroeder, Adam Wierman আর Mor Harchol-Balter-এর ২০০৬ সালের গবেষণাপত্র “Open Versus Closed: A Cautionary Tale” আগেই দেখিয়েছিল, দুই মডেলের ফল সত্যিই আলাদা হতে পারে, আর ভুল মডেল ভুল সিদ্ধান্তে নেয়।

দ্বিতীয় কারণ: শুধু সফল অনুরোধের latency. আমাদের A সেটআপের run-এ p99 3 ms, অথচ ১৪৬টি অনুরোধ ব্যর্থ। ব্যর্থতা দ্রুত হলে latency আরও সুন্দর দেখায়। ব্যর্থ অনুরোধকেও হিসাবের মধ্যে আনতে হবে, অন্তত error rate পাশে রেখে।

তৃতীয় কারণ: load generator নিজে ধীর। তার CPU ফুরালে সে কম পাঠায়, আর রিপোর্ট ভাবে সব ঠিক। চতুর্থ: test-এর ভিড় আসল ভিড়ের মতো নয়। Cache-এ একই কয়েকটি URL, ছোট database, এক region থেকে চাপ, সমান দূরত্বে অনুরোধ: আমাদের even run-এ ৮৫ req/s-এ p95 ৫ ms, Poisson-এ ৪১ ms.

পঞ্চম, সবচেয়ে সরল কারণ: মাপা হচ্ছে server-এ, ব্যবহারকারীর অভিজ্ঞতা client-এ। Mobile network, DNS, TLS, CDN, ভারী JavaScript, এদের কিছুই server-এর সময়ে নেই। Google-এর SRE ধারণাতেও SLI মাপার জায়গা নিয়ে এই সতর্কতা আছে। এজন্য real user monitoring আর synthetic probe দুটোই কাজে লাগে।

সমাধানের রুটিন: open model, পরিকল্পিত সময় থেকে latency, ব্যর্থতা গোনা, generator-এর CPU দেখা, L = λW দিয়ে যাচাই, আর আসল traffic-এর আকার নকল করা।

মূল উপলব্ধি: মাপের যন্ত্রকেও সন্দেহ করুন। সুন্দর সংখ্যা আর আসল অভিজ্ঞতা আলাদা হলে, সংখ্যাটিই প্রথম আসামি।

প্র ০৩“CPU ৮০%-এর ওপরে নেবেন না”, এই পরিচিত নিয়ম কি সবসময় ঠিক?

নিয়মটি উপকারী মোটা দাগের পরামর্শ, কিন্তু প্রাকৃতিক সূত্র নয়। ১/(১ − ρ) আকৃতি সত্য, তবে এর পেছনে শর্ত আছে: এলোমেলো আগমন, এলোমেলো সেবার সময়, একটি সেবক।

আসল সংখ্যা তিনটি জিনিসে বদলায়। প্রথমত এলোমেলোতা: Kingman-এর অনুমান দেখায়, অপেক্ষা (Cₐ² + Cₛ²)/2-এর সাথে বাড়ে। সেবার সময় সমান আর আগমন মসৃণ হলে অনেক উঁচুতেও সারি কম। আমাদের even run ৮৫ req/s-এ, ρ ০.৯৬-এ, p95 মাত্র ৫ ms. দ্বিতীয়ত সেবকের সংখ্যা: ১৯০৯ সাল থেকে Copenhagen-এর টেলিফোন গবেষণায় A. K. Erlang যে গণিত গড়েন, তা বলে একই সারি থেকে অনেক সেবক কাজ নিলে উঁচু ρ-তেও অপেক্ষা কম। তাই ৬৪-core-এর একটি server ৯০%-এও ভালো চলতে পারে, যেখানে একটি core-এর ৯০% মানে জট। তৃতীয়ত latency-র দাম: রাতের batch job-এ ৯৯% CPU-ই ভালো, কারণ ওখানে কেউ অপেক্ষা করছেন না। Google-এর Borg সংক্রান্ত প্রকাশনাও দেখায়, ব্যাচ আর সেবা মিশিয়ে অনেক উঁচু ব্যবহার সম্ভব।

উল্টো দিকেও সমস্যা। ৫০% গড় CPU-তেও tail খারাপ হতে পারে। ৫ মিনিটের গড় ৫০% মানে কয়েক সেকেন্ড ১০০%. Container-এ CPU limit থাকলে node ফাঁকা থেকেও throttling হয়। আমাদের run-এর ছাদ ছিল 300m, মেশিনের নয়। আবার CPU আসল bottleneck না-ও হতে পারে: database lock, connection pool বা thread-এর সীমা আগেই ফুরায়।

তাই সঠিক প্রশ্ন “কত শতাংশ?” নয়, “আমার SLO কোন চাপে ভাঙে, আর সেখানে ρ কত?” Load test করে সেই ρ খুঁজুন, তারপর নিরাপদ দূরত্ব রাখুন। Autoscaling target-ও সেখান থেকে আসবে, বই থেকে নয়।

মূল উপলব্ধি: ৮০% একটি শুরুর অনুমান। আপনার নিজের knee আপনার আসল নিয়ম।

প্র ০৪Cloud-এ autoscaling আছে, তবু capacity planning-এর মতো পুরোনো কাজ কেন? এটি তো data center যুগের অভ্যাস।

Data center যুগে capacity planning মানে ছিল মাস আগে hardware কেনা। ভুল করলে হয় টাকা আটকে, নয় বিপর্যয়। Cloud সেই সময়কে মাস থেকে মিনিটে এনেছে, কাজটি বাদ দেয়নি। পরিকল্পনা বদলে গেছে: এখন প্রশ্ন “কত কিনব” নয়, “কত দ্রুত, কতদূর, আর কোন সীমা পর্যন্ত বাড়তে পারব, আর সেটি কি পরীক্ষিত?”

প্রথমত, মিনিটও অনেক সময়। পর্ব ১৭-র simulator-এ ৩ মিনিটের boot-এ ২,৩৪,০০০ অনুরোধ বাদ। দ্বিতীয়ত, elastic-এর একটি সীমা আছে। Zone বিকল হলে সেই zone-এর সব গ্রাহক অন্য zone-এ ক্ষমতা চান। তখন “on-demand” সত্যিই পাবেন, এমন নিশ্চয়তা নেই। AWS-এর Builders' Library “static stability” ধারণায় এজন্যই বলে: সিস্টেম এমন বানান, যাতে ব্যর্থতার সময়ে নতুন কিছু চালু করার দরকারই না হয়। আগে থেকে ৩ zone-এ চালু ক্ষমতা হলো সেই static stability.

তৃতীয়ত, quota আর dependency নিজে বাড়ে না। Support ticket, database-এর আকার, third-party payment API-র rate limit, এসব পরিকল্পনা ছাড়া আটকায়। পর্ব ১৭-তে দেখেছেন: ৩০ instance × ২০ connection = ৬০০, অথচ database পারে ৫০০.

চতুর্থত, খরচ। Forecast থাকলে commitment-এর ছাড়, scheduled scaling আর spot-এর সঠিক মিশ্রণ বেছে নেওয়া যায়। Forecast ছাড়া হয় অতিরিক্ত বিল, নয় হঠাৎ বিপদ।

Google-এর Site Reliability Engineering বই (২০১৬) capacity planning-কে SRE-র মূল দায়িত্বের মধ্যে রাখে: স্বাভাবিক ও অস্বাভাবিক চাহিদার forecast, আর load test দিয়ে source capacity মাপা। বইটিতে N+2-এর মতো redundancy-র কথাও আছে, কারণ একটি ইউনিট যখন ইচ্ছা করে নামানো (update), তখন অন্যটি ভেঙে যেতে পারে।

মূল উপলব্ধি: Autoscaling হলো রিফ্লেক্স, capacity plan হলো প্রস্তুতি। দৌড়বিদের দুটোই লাগে।

প্র ০৫সিস্টেম ঠিক থাকলেও game day কেন? ফেব্রুয়ারি ২০১৭-র S3 ঘটনা থেকে কী শেখার আছে?

Game day-এর ধারণা ওয়েব জগতে ছড়ায় Amazon-এর Jesse Robbins-এর কাজ থেকে, যিনি অগ্নিনির্বাপক হিসেবেও প্রশিক্ষণ পেয়েছিলেন। মহড়ার চিন্তা তিনি সেখান থেকেই আনেন, আর ২০১২ সালে ACM Queue-এ এ নিয়ে লেখা প্রকাশিত হয়। Google-এর Kripa Krishnan একই বছরে সেখানে DiRT (Disaster Recovery Testing)-এর কথা লেখেন: কর্মীরা বড় ব্যর্থতা সাজিয়ে দেখেন প্রতিষ্ঠান কীভাবে সাড়া দেয়। এই পরীক্ষাগুলো যন্ত্রের চেয়ে মানুষ ও প্রক্রিয়ার দুর্বলতা বেশি ধরে: কার কাছে access, কে সিদ্ধান্ত নেবেন, চ্যাট service বন্ধ থাকলে কথা হবে কোথায়।

২০১৭ সালের ফেব্রুয়ারিতে AWS S3-র একটি region-এ বড় বিভ্রাট হয়। AWS-এর প্রকাশিত সারসংক্ষেপ অনুযায়ী, একটি কমান্ডের ভুল ইনপুট প্রত্যাশার চেয়ে বেশি server সরিয়ে দেয়। তারপর গুরুত্বপূর্ণ subsystem-কে পুরোপুরি restart করতে হয়। আর AWS নিজেই লিখেছে, বড় region-এ এগুলো বহু বছর পুরো restart করা হয়নি, এবং পুনরুদ্ধারে প্রত্যাশার চেয়ে বেশি সময় লাগে।

লক্ষ্য করুন শিক্ষাটি। সিস্টেম এত ভালো চলছিল যে recovery পথটি বহু বছর ব্যবহারই হয়নি, আর এর মধ্যে আকার অনেক বেড়ে গিয়েছিল। যে পথ পরীক্ষা হয় না, সেটি ধীরে ধীরে নষ্ট হয়, ঠিক ফেলে রাখা generator-এর মতো, যা load-shedding-এর রাতে চালু হয় না। AWS-এর সংশোধনের মধ্যেও ছিল টুলে সীমা বসানো, যাতে একবারে বেশি সরানো না যায়, অর্থাৎ blast radius ছোট করা।

একটি ভালো নিয়ম হলো: নিয়মিত পরীক্ষার তালিকায় রাখুন পুরো restart, backup থেকে ফেরা, zone সরানো, secret ঘোরানো, on-call হাতবদল। Incident-এর পরে পাওয়া প্রতিটি শিক্ষা পরের game day-এর বিষয়।

মূল উপলব্ধি: যে recovery পথ অনুশীলন হয় না, ধরে নিন সেটি নষ্ট। মহড়া সেই পথের রক্ষণাবেক্ষণ।

অনুশীলন

হিসাব, নকশা আর ফল আগে থেকে বলা। ঐচ্ছিক হাতে-কলমে কাজটি নিজের local cluster-এ করুন; cloud-এ করলে খরচ হতে পারে।

  1. হিসাব করুন: একটি API-তে চূড়া ২,০০০ req/s, বৃদ্ধি ১০%, margin ২৫%. এক instance-এর নিরাপদ ক্ষমতা ৫০ req/s. ৪ zone-এ একটি zone হারানো ধরে মোট কত instance? স্বাভাবিক দিনে নিরাপদ ক্ষমতার কত অংশ ব্যবহার হবে? Latency গড় ৮০ ms হলে L কত? (capacity_plan.js বদলে মিলিয়ে নিন।)

    পরিকল্পনা = ২,০০০ × ১.১ × ১.২৫ = ২,৭৫০ req/s. প্রতি zone = ⌈২,৭৫০ ÷ ৩ ÷ ৫০⌉ = ⌈১৮.৩৩⌉ = ১৯, মোট ৭৬. স্বাভাবিক ব্যবহার = ২,৭৫০ ÷ (৭৬ × ৫০) = ৭২.৪%. L = ২,৭৫০ × ০.০৮ = ২২০টি অনুরোধ।

  2. আগে বলুন: ৪টি replica, probe নেই, নতুন Pod ৬ সেকেন্ড ধরে 503 দেয়, ৩০ req/s আসছে। একটি Pod মুছলে এবং সে দ্রুত ফিরে এলে আনুমানিক কতগুলো 503 দেখবেন? Readiness probe যোগ করলে এই সংখ্যা কত হবে? আর replica ১ হলে probe দিয়ে সেই ৬ সেকেন্ডে কী ঘটবে?

    ৪ Pod-এর একটির ভাগে ৩০ ÷ ৪ = ৭.৫ req/s, ৬ সেকেন্ডে প্রায় ৪৫টি 503. Probe দিলে প্রায় ০, কারণ Service গরম Pod-কে বাদ রাখে আর বাকি ৩টি সামলায় (ওদের ক্ষমতা থাকলে)। Replica ১ হলে probe 503 সরায় ঠিকই, কিন্তু ready Pod শূন্য, তাই ওই ৬ সেকেন্ডে সব অনুরোধ ব্যর্থ: আমাদের B সেটআপের মতো।

  3. সমালোচনা করুন: একজন রিপোর্ট দিলেন: “ab -n 10000 -c 10 চালিয়ে দেখলাম গড় latency ২০ ms, CPU ৪৫%. তাই আমরা ১০ গুণ traffic নিতে পারব। ঈদে autoscaling আছে, চিন্তা নেই।” এই সিদ্ধান্তের ভুলগুলো ধরুন, আর সঠিক পরীক্ষার নকশা লিখুন।
    • Closed model: ১০টি worker, তাই coordinated omission. আসল ভিড় open.
    • গড়: p95, p99 আর error নেই।
    • রৈখিক অনুমান: CPU ৪৫% থেকে ১০ গুণ অসম্ভব; ৪৫০% হয় না, আর knee অনেক আগে। Database সম্ভবত আগেই ভাঙবে।
    • Autoscaling-এ অন্ধ ভরসা: Boot, quota আর পেছনের স্তর ধরা হয়নি। Zone হারানোও নেই।
    • সঠিক নকশা: Staging-এ production-এর মতো data. Open model-এ ধাপে ধাপে চাপ, প্রতি ধাপ কয়েক মিনিট। p50, p95, p99, error, database-এর metric একসাথে। Knee বের করে নিরাপদ মান ঠিক করা, spike test, একটি zone সরিয়ে আবার মাপ, আর scheduled scaling.
  4. হাতে-কলমে (ঐচ্ছিক, local cluster-এ): নিজের namespace বানিয়ে ৩ replica-র একটি nginx Deployment আর Service চালান। একটি ছোট client Pod থেকে while loop-এ wget চালিয়ে ব্যর্থতা গুনুন, আর একটি Pod মুছুন। Readiness probe যোগ করে আবার দেখুন। Hypothesis, blast radius, abort লিখুন। শেষে clean-up করুন।

    কমান্ডের কাঠামো, নিজের নাম বসিয়ে:

    kubectl create namespace chaos-lab
    kubectl -n chaos-lab create deployment web --image=nginx:1.27-alpine --replicas=3
    kubectl -n chaos-lab expose deployment web --port=80
    kubectl -n chaos-lab run client --image=alpine:3.20 --restart=Never -- sleep 600
    kubectl -n chaos-lab get pods
    kubectl -n chaos-lab delete pod <pod-name>     # exact name only
    kubectl delete namespace chaos-lab               # clean-up

    nginx দ্রুত চালু হয়, তাই error কম দেখতে পারেন, বিশেষ করে client অনুরোধ ধীরে পাঠালে। সেটিও একটি ফল: hypothesis টিকল, কিন্তু পরীক্ষা কতটা শক্ত ছিল? Traffic-এর হার বাড়িয়ে, বা startup-এ দেরি যোগ করে আবার দেখুন।

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

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

ক্রমএকটি chaos পরীক্ষার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Steady state মেপে বেসলাইন নেওয়া
  2. Hypothesis লেখা
  3. Blast radius আর abort condition ঠিক করা
  4. ব্যর্থতা ঢুকিয়ে সংখ্যা মাপা
  5. দুর্বলতা ঠিক করে পরীক্ষাটি স্বয়ংক্রিয় করা

Chaos engineering-এর পরীক্ষায় steady-state hypothesis কোনটি সবচেয়ে ভালোভাবে প্রকাশ করে?

  • “আজ একটি Pod মুছে দেখব”
  • “একটি Pod মুছলেও ৩০ সেকেন্ডে success ≥ ৯৯% থাকবে”
  • “সিস্টেম স্থিতিশীল থাকবে”
  • “Deployment-এ ৩টি replica আছে”

একটি service-এ λ = ৫০০ req/s আর গড় response time ২০০ ms. Little's law অনুযায়ী সিস্টেমে গড়ে কতটি অনুরোধ থাকে?

  • ৫টি
  • ১০টি
  • ১০০টি
  • ২,৫০০টি

M/M/1 মডেলে গড় সেবার সময় ১০ ms. Utilisation ০.৫ থেকে ০.৯ হলে গড় মোট সময় কী হবে?

  • ২০ ms থেকে ৩৬ ms
  • দুই ক্ষেত্রেই ১০ ms
  • ২০ ms থেকে ১১ ms
  • ২০ ms থেকে ১০০ ms

একটি load test-এ client প্রতিটি উত্তর পেয়ে তবেই পরের অনুরোধ পাঠায়। Server ৫ সেকেন্ড থেমে থাকলে p99 খুব ভালো দেখাল। কারণ কী?

  • Coordinated omission: client ধীর সময়ের নমুনা নেয়নি
  • Warm-up-এর অংশ বাদ দেওয়া হয়েছে
  • Server-এর throughput বেড়ে গেছে
  • Little's law এখানে খাটে না

পরিকল্পিত চাপ ৩,০০০ req/s, এক instance-এর নিরাপদ ক্ষমতা ১০০ req/s, ৩টি zone. একটি zone হারিয়েও চলতে হলে সমান ভাগে মোট কত instance লাগবে?

  • ৩০টি
  • ৩৩টি
  • ৪৫টি
  • ৬০টি

একটি Deployment-এ ১টি replica, readiness probe আছে। Pod মুছলে নতুন Pod ready হতে ৫ সেকেন্ড লাগে। এই সময়ে Service-এর ব্যবহারকারীরা কী পাবেন?

  • Probe থাকায় কোনো ব্যর্থতা হবে না
  • Ready Pod না থাকায় অনুরোধ ব্যর্থ হবে
  • Service অনুরোধ সারিতে রেখে পরে পাঠাবে
  • পুরোনো Pod আরও ৫ সেকেন্ড অবশ্যই চলবে

শব্দকোষ

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

মনে রাখুন

  1. Chaos পরীক্ষা = steady state, hypothesis, blast radius, abort, তারপর মাপ, সমাধান, স্বয়ংক্রিয় করা।
  2. Pod মোছায় success বাঁচায় তিনটি মিলে: replica, readiness probe, drain. একটি একা নয়।
  3. Load test-এ open model, পরিকল্পিত সময় থেকে latency, percentile আর error একসাথে। নইলে coordinated omission.
  4. L = λW আর W = S ÷ (1 − ρ). ছাদের কাছে সামান্য চাপে latency লাফায়, তাই পরিকল্পনা knee-র নিচের সংখ্যায়।
  5. Capacity = চূড়ার forecast + headroom + z − 1 zone-এ পুরো চাপ. Autoscaling রোজের জন্য, পরিকল্পনা ধাক্কার জন্য, আর দুটোই পরীক্ষা করে রাখুন।

পরের পর্বে: এখানে ক্ষমতা আমরা নিজেরা হিসাব করেছি। মডিউল ১২-তে সেই দায়িত্বের একটি বড় অংশ provider-এর হাতে যায়: serverless function, যেখানে আপনি শুধু code দেন। কিন্তু সীমা আর cold start সেখানেও থাকে।

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

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