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

Serverless function — trigger, cold start, concurrency আর সীমা

Serverless Functions — Triggers, Cold Starts, Concurrency and Limits

Taught in Bangla, this lesson reads a function through handler, event and context, compares four trigger types, and follows the environment through init, invoke, freeze, reuse and shutdown to explain cold starts. It calculates billing in requests and GB-seconds, finds the cheapest memory size, uses Little's law for concurrency and throttling, and covers limits, database connections, packaging, tracing, local tests and least privilege.

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

  • Handler, event আর context দিয়ে একটি function পড়া ও লেখা
  • চার ধরনের trigger আর সীমায় পড়লে কার কী হয়
  • Environment-এর init, invoke, freeze, reuse আর shutdown, আর cold start-এর কারণ
  • Request আর GB-second দিয়ে বিল হাতে হিসাব, আর সবচেয়ে সস্তা memory খোঁজা
  • Little's law দিয়ে concurrency মাপা, ভিড়ের throttling আগেই ধরা
  • Database connection, package, log, trace, local test আর least privilege সামলানো

১ · Function কী — handler, event আর context

পর্ব ১৯-এ VM, container, PaaS আর FaaS-এর তুলনা দেখেছেন: ছোট, অনিয়মিত, event-চালিত কাজে FaaS মানায়। সেই তুলনা এখানে আর নয়। এই মডিউলে আমরা ভেতরে ঢুকব: function কীভাবে চলে, কোথায় আটকায়, আর কীভাবে চালাবেন।

পাড়ার ইলেকট্রিশিয়ানের কথা ভাবুন। তিনি আপনার বেতনভুক্ত কর্মচারী নন। ফ্যান নষ্ট হলে ফোন করেন, তিনি এসে কাজ করেন, কাজের ফি নেন, চলে যান। যে মাসে কিছু নষ্ট হয় না, সে মাসে খরচ শূন্য। Function ঠিক এমন। স্লিপে লেখা থাকে কী নষ্ট আর কোন ঘরে (event). মিস্ত্রির হাতে থাকে কাজের নিয়ম (handler). আর সাথে একটি ঘড়ি, কতক্ষণ বাকি (context).

একটি function-এর তিনটি অংশ চিনে নিন:

  • Handler: আপনার লেখা যে ফাংশনটি platform ডাকে। Provider জানে কোন ফাইলের কোন নাম, যেমন app.handler.
  • Event: কী ঘটেছে তার তথ্য, সাধারণত JSON আকারে। HTTP হলে path আর body, storage হলে bucket আর object-এর নাম।
  • Context: এই call-এর তথ্য: request id, কত সময় বাকি, function-এর নাম। Provider-ভেদে নাম আলাদা।

একবার handler চালানোকে বলে invocation, বা সংক্ষেপে invoke. নিচের কোডে কোনো cloud নেই: আমরা নিজেই platform সেজে handler-কে তিনটি নমুনা event দিয়ে ডাকছি। পাতার ভেতরেই চালানো যায়, কোনো account লাগে না।

# A handler called locally with sample events. No cloud, no account needed.
import json

PRICE = {"tea": 15, "samosa": 10}   # outside the handler: runs once, during init
calls = 0                           # lives as long as this environment lives

class Context:                      # a stand-in for what the platform passes in
    request_id = "local-0001"
    remaining_ms = 2900

def order_total(items):             # pure logic: no cloud, easy to unit test
    return sum(PRICE[i["name"]] * i["qty"] for i in items)

def handler(event, context):
    global calls
    calls += 1
    try:
        total = order_total(json.loads(event["body"])["items"])
        status, out = 200, {"total": total}
    except (KeyError, ValueError) as e:
        status, out = 400, {"error": type(e).__name__}
    print(json.dumps({"id": context.request_id, "call": calls, "status": status}))
    return {"statusCode": status, "body": json.dumps(out)}

samples = [
    {"body": '{"items": [{"name": "tea", "qty": 2}, {"name": "samosa", "qty": 3}]}'},
    {"body": '{"items": [{"name": "biryani", "qty": 1}]}'},
    {"body": "not json"},
]
for event in samples:
    print(handler(event, Context()))
{"id": "local-0001", "call": 1, "status": 200}
{'statusCode': 200, 'body': '{"total": 60}'}
{"id": "local-0001", "call": 2, "status": 400}
{'statusCode': 400, 'body': '{"error": "KeyError"}'}
{"id": "local-0001", "call": 3, "status": 400}
{'statusCode': 400, 'body': '{"error": "JSONDecodeError"}'}

প্রথম event-এ ২ × ১৫ + ৩ × ১০ = 60. দ্বিতীয়টিতে biryani তালিকায় নেই, তাই KeyError ধরে 400 ফেরত গেল। তৃতীয়টি JSON-ই নয়। খেয়াল করুন, call ১, ২, ৩ হয়ে বাড়ছে: calls handler-এর বাইরে থাকে, তাই call-এর মাঝেও টিকে থাকে। কেন, তা অংশ ৩-এ দেখবেন। আসল হিসাব order_total-এ আলাদা করা, তাই cloud ছাড়াই পরীক্ষা করা যায়। প্রতিটি লাইনের log JSON আকারে, যা structured log.

নামের গোলমাল: Serverless শব্দটি FaaS-এর চেয়ে বড়। FaaS মানে ডাকলে চলা function. Serverless বলতে অনেকে এমন সব সেবা বোঝান, যেখানে server-এর সংখ্যা ভাবতে হয় না আর বিল ব্যবহার অনুযায়ী, যেমন object storage বা managed queue. কেউ আবার শব্দটি শুধু FaaS অর্থেই বলেন। Server অবশ্যই আছে, শুধু আপনি সামলান না। আর scale to zero সব “serverless” সেবায় থাকে না।

AWS ২০১৪ সালের নভেম্বরে Lambda ঘোষণা করে, শুরুতে S3 আর কিছু event-এ সাড়া দেওয়ার জন্য। Microsoft ২০১৬ সালে Azure Functions আনে। Google-এর Cloud Functions ২০১৭ সালে beta-তে সবার সামনে আসে, আর ২০২৪ সালে নাম হয় Cloud Run functions. নিচে নামগুলো উদাহরণ মাত্র। ধারণা সবখানে কাছাকাছি, তবে সীমা আর বিলের নিয়ম আলাদা, আর সময়ের সাথে বদলায়।

২ · Trigger — function-কে কে ডাকে

Function নিজে থেকে চলে না। যে জিনিসটি তাকে ডাকে, তাকে বলে trigger, আর event-এর উৎসকে event source. চারটি ধরন প্রায় সব কাজ ঢেকে দেয়। প্রতিটির ডাকার ধরন আলাদা, আর সেটিই পরে সীমায় পড়লে ফল ঠিক করে।

Triggerকখনডাকার ধরনসীমায় পড়লে সাধারণত
HTTPকেউ URL-এ request পাঠালে, প্রায়ই API gateway-র পেছনেSynchronous: ডাকা পক্ষ উত্তরের অপেক্ষায়ডাকা পক্ষ error পায়, যেমন 429
Storage eventBucket-এ ফাইল এলে (event notification)Asynchronous: event জমা দিয়ে ডাকা পক্ষ চলে যায়Platform পরে কয়েকবার চেষ্টা করে, শেষে বাদ বা dead letter queue
Scheduleনির্দিষ্ট সময়ে, cron-এর মতোসাধারণত asynchronousProvider-ভেদে আবার চেষ্টা বা বাদ
QueueQueue-তে বার্তা এলেPoll: platform নিজে queue পড়ে batch আকারে আনেবার্তা queue-তেই থাকে, পরে আসে
ধরনAWSAzureGoogle Cloud
HTTPAPI Gateway, Function URLHTTP triggerHTTPS endpoint
StorageS3 eventBlob trigger, Event GridCloud Storage, Eventarc হয়ে
ScheduleEventBridge SchedulerTimer triggerCloud Scheduler
QueueSQSQueue, Service Bus triggerPub/Sub

Event source থেকে downstream পর্যন্ত

Event sources → function environments → downstream services

Event source, function environment আর downstream HTTP · API gateway Queue Storage event Schedule event Function platform (provider চালান) Env ১চলছে Env ২frozen Env ৩init প্রতিটিতে handler, memory আর একটি role Concurrency সীমা পার হলে throttling network call Database Object storage আরেকটি queue Downstream: function যাদের ডাকে, এদেরও সীমা আছে
ছবি ৬৯.১ — Platform স্কেল করে, কিন্তু পেছনের database একই থাকে। এই পর্বের বেশিরভাগ বিপদ এই ছবির নিচের তীরে।

Asynchronous আর queue-র ক্ষেত্রে ডেলিভারি সাধারণত অন্তত একবার। অর্থাৎ একই event দুবারও আসতে পারে, তাই handler-কে idempotent রাখতে হয়। বিস্তারিত আসবে পর্ব ৭০ আর ৭১-এ।

নিজেকে ডাকার ফাঁদ: ধরুন uploads bucket-এ ফাইল এলে function চলে, আর সে ছোট ছবিটি একই bucket-এ লেখে। নতুন ফাইল আবার function ডাকে, যা আবার ফাইল লেখে। এই recursive loop রাতভর চললে বিল লাফ দেয়। আলাদা bucket বা prefix ব্যবহার করুন, event filter দিন, concurrency-র ছাদ রাখুন, আর budget alert চালু রাখুন। কিছু provider এমন loop ধরতে পারে, তবে সেটির ওপর ভরসা করবেন না।

৩ · Environment-এর জীবন — init, invoke, freeze, reuse, shutdown

প্রতি call-এ কি নতুন করে সব তৈরি হয়? না। Platform একটি execution environment বানায়: আপনার কোড, runtime আর একটু memory-সহ একটি আলাদা ঘর, যা প্রায়ই একটি microVM বা sandbox-এ থাকে। সেই ঘর পরের call-গুলোতেও কাজে লাগে।

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

একটি environment-এর জীবনরেখা

Execution environment lifecycle (widths are not to scale)

Environment-এর জীবনরেখা ১ম event (cold) ২য় event (warm) Init Invoke Freeze Invoke Freeze Shutdown Freeze-এ CPU থামানো, memory আগের মতোই Provider ঠিক করেন কতক্ষণ পরে shutdown
ছবি ৬৯.২ — Init একবার, invoke অনেকবার। দুই invoke-এর মাঝে environment ঘুমায়, মরে না।
  • Init: Environment তৈরি, runtime চালু, আর আপনার handler-এর বাইরের কোড চলা: import, config পড়া, client তৈরি।
  • Invoke: Handler একটি event নিয়ে চলে, উত্তর দেয়।
  • Freeze: কাজ শেষে platform environment থামিয়ে রাখে। Memory থাকে, কিন্তু CPU পায় না। তাই পেছনের thread বা timer-ও থেমে যায়। ফিরে আসা সাধারণত খুব দ্রুত।
  • Reuse: পরের event এলে থামানো environment জেগে ওঠে। একে অনেকে warm start বলেন।
  • Shutdown: অনেকক্ষণ অলস থাকলে, বা platform-এর নিজের কারণে, environment মুছে যায়। কতক্ষণ পরে, তার কোনো নিশ্চয়তা নেই।

নিচের stepper-এ একই function-এ তিনটি request. সময়গুলো অনুমিত। calls আর database connection-এর ঘর দুটি খেয়াল করুন।

এই মুহূর্তের অবস্থা
Environment
Request-এর ঘড়ি
calls
DB connection
  1. Event এল। ফাঁকা কোনো environment নেই, তাই request অপেক্ষা করে। এই অপেক্ষাই cold start.
  2. ঘর তৈরি। Platform একটি নতুন environment বানায়, আপনার কোড আনে। ১২০ ms.
  3. Runtime ওঠে। Python বা Node.js-এর মতো runtime চালু হয়। ঘড়ি ৩০০ ms.
  4. আপনার init. Handler-এর বাইরের কোড চলে: library load, database connection. সবচেয়ে বড় অংশ, ৪৫০ ms, আর এই অংশটিই আপনার হাতে। calls এখন ০।
  5. Handler চলল। মাত্র ৬০ ms, calls হলো ১। ব্যবহারকারী অপেক্ষা করলেন মোট ৮১০ ms, যার ৯২.৬% ছিল init.
  6. Freeze. কাজ শেষ, environment থামল। Memory-র calls আর connection দুটোই আছে, কিন্তু কোনো কোড চলছে না।
  7. Warm call. ৩০ সেকেন্ড পরে দ্বিতীয় request. Environment জাগল, handler চলল। মোট ৬০ ms, calls হলো ২, আর connection নতুন করে খুলতে হলো না। অলস connection database ততক্ষণে বন্ধ করে না দিয়ে থাকলে তবেই।
  8. Shutdown. ৩০ মিনিট কেউ আসেনি (এই উদাহরণে)। Environment মুছে গেল, সাথে calls-ও।
  9. আবার cold. তৃতীয় request-এ পুরো পথ আবার, ৮১০ ms. calls আবার ১, ৩ নয়। তাই environment-এর ভেতরের কিছুই সত্যি হিসাবের জন্য নির্ভরযোগ্য নয়।
Stateless-এর নিয়ম

Function-কে stateless ভাবুন: Environment-এ যা থাকে, তা শুধু গতির জন্য, সত্যের জন্য নয়। Module-এর ভেরিয়েবল আর temporary disk (যেমন /tmp) cache হিসেবে চমৎকার। কিন্তু ব্যবহারকারীর session, অর্ডারের হিসাব বা গণনা database বা object storage-এ রাখুন। একই মুহূর্তে দশটি environment চললে দশটি আলাদা calls থাকে, আর কেউ অন্যটি দেখতে পায় না।

৪ · Cold start — সময় কোথায় যায়, আর কতবার হয়

Cold start নিয়ে দুটি আলাদা প্রশ্ন আছে। একটি cold start কত লম্বা, আর কত request-এ কতবার হয়। প্রথমটি কমাতে নিচের টেবিল।

কারণকেন প্রভাব ফেলেকী করা যায়
RuntimeJVM বা .NET-এর শুরু সাধারণত Python, Node.js-এর চেয়ে ভারী; Go, Rust-এর binary হালকাLatency-সংবেদী কাজে হালকা runtime, বা snapshot
Package আকারবেশি কোড মানে বেশি ফাইল আনা আর load করাঅদরকারি library বাদ, যা লাগে শুধু তা import
Init কোডHandler-এর বাইরের সব কাজ এখানেইভারী কাজ দরকারের মুহূর্তে করা (lazy loading)
MemoryMemory-র সাথে CPU বাড়ে, তাই init-ও দ্রুত হয়মেপে দেখা, অংশ ৫-এ
VPCPrivate network-এ জোড়ার ধাপ। আগে Lambda-তে অনেক সেকেন্ড লাগত, ২০১৯-এর দিকে নকশা বদলে অনেক কমেছেদরকার থাকলেই VPC, আর provider-এর এখনকার নথি দেখা

কারণ কমানোর পরেও দুটি বড় কৌশল আছে, আর দুটিরই দাম আছে।

Snapshot থেকে শুরু

Init একবার চালিয়ে পুরো memory-র ছবি রেখে দেওয়া হয়। পরের environment সেই ছবি থেকে জেগে ওঠে, অনেকটা ল্যাপটপ hibernate-এর মতো। সাবধান: ছবিতে আটকে থাকা random seed, সময় আর খোলা connection সব কপিতে একই থাকে, তাই জাগার পরে নতুন করে বানাতে হয়।

উদাহরণ: Lambda SnapStart (runtime-ভেদে)

Provisioned concurrency

নির্দিষ্ট সংখ্যক environment আগে থেকে init করে রাখা। ওই সংখ্যা পর্যন্ত cold start প্রায় থাকে না। কিন্তু অলস সময়েও বিল চলে, তাই scale to zero-র সুবিধা ওই অংশে হারায়।

Azure: always-ready instance · Google: minimum instance

একসময় অনেকে প্রতি কয়েক মিনিটে নকল “ping” পাঠিয়ে function জাগিয়ে রাখতেন। এটি এক environment-কেই জাগায়, ভিড়ের সময় দ্বিতীয়টিকে নয়, তাই ভরসা করার মতো কৌশল নয়।

এবার দ্বিতীয় প্রশ্ন: কতবার? এটি নির্ভর করে traffic আর provider কতক্ষণ অলস environment রাখেন তার ওপর। নিচের সিমুলেটরে প্রতিটি সারি ২০,০০০ request-এর একটি আলাদা run. অলস সীমা ১০ মিনিট আর environment-এর আয়ু ২ ঘণ্টা ধরা হয়েছে। দুটিই অনুমিত, provider এসব সংখ্যা সাধারণত নিশ্চিত করে বলেন না।

// Cold-start share vs traffic. ASSUMED: idle timeout 600 s, environment life 2 h.
const IDLE = 600, LIFE = 7200, RUN = 0.2, COLD_MS = 800, WARM_MS = 15, N = 20000;
function mulberry32(a) { return () => { 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; }; }
function simulate(rate) {                  // rate = requests per second
  const rnd = mulberry32(42);
  let t = 0, cold = 0, peak = 0, free = [], busy = [];  // each env: [freeAt, bornAt]
  for (let i = 0; i < N; i++) {
    t += -Math.log(1 - rnd()) / rate;      // random gaps between requests
    busy = busy.filter((e) => (e[0] > t ? true : (free.push(e), false)));
    free = free.filter(([at, born]) => t - at < IDLE && t - born < LIFE);
    const env = free.pop();                // newest idle environment, if any
    if (!env) cold++;
    busy.push([t + RUN + (env ? 0 : COLD_MS / 1000), env ? env[1] : t]);
    peak = Math.max(peak, busy.length);
  }
  return { share: cold / N, peak };
}
console.log("traffic  req/hour  cold share  e^(-rate*idle)  max envs  p99 ms");
const rows = [["1/hour", 1 / 3600], ["6/hour", 1 / 600], ["1/min", 1 / 60],
              ["10/min", 1 / 6], ["1/s", 1], ["10/s", 10], ["100/s", 100]];
for (const [name, r] of rows) {
  const { share, peak } = simulate(r);
  const pct = (share * 100).toFixed(share < 0.01 ? 3 : 1) + "%";
  const p99 = share >= 0.01 ? COLD_MS + WARM_MS : WARM_MS; // 1% slow requests set p99
  console.log(name.padEnd(7), String(Math.round(r * 3600)).padStart(9), pct.padStart(11),
    Math.exp(-r * IDLE).toFixed(4).padStart(15), String(peak).padStart(9), String(p99).padStart(7));
}
traffic  req/hour  cold share  e^(-rate*idle)  max envs  p99 ms
1/hour          1       84.8%          0.8465         2     815
6/hour          6       36.9%          0.3679         2     815
1/min          60      0.990%          0.0000         2      15
10/min        600      0.260%          0.0000         3      15
1/s          3600      0.105%          0.0000         5      15
10/s        36000      0.065%          0.0000        12      15
100/s      360000      0.535%          0.0000       107      15

1/hour সারিতে প্রায় ৮৫% request cold. 6/hour-এ ৩৬.৯%, যা e^(-rate*idle)-এর ০.৩৬৭৯-এর সাথে মিলে যায়। সূত্রটি বলে, পরের request অলস সীমার পরে আসার সম্ভাবনা কত। তাই কম traffic-এ cold start-ই স্বাভাবিক ঘটনা, ব্যতিক্রম নয়।

1/min সারিতে সূত্র প্রায় ০, অথচ ০.৯৯০%. কারণ environment-এর আয়ু: প্রতি ১২০ request-এ একটি নতুন environment লাগছে, যা ০.৮৩%. 100/s-এর ০.৫৩৫% এসেছে শুরুর ভিড় থেকে: প্রথম প্রায় এক সেকেন্ডে শূন্য থেকে ১০৭টি environment উঠেছে। এই run মাত্র ২০০ সেকেন্ডের, তাই একটি বড় ভাগ দেখাচ্ছে। লম্বা সময়ে ভাগটি কমবে।

শেষ কলামটি সবচেয়ে কাজের। Cold ভাগ ১%-এর ওপরে গেলেই p99 হঠাৎ ১৫ ms থেকে ৮১৫ ms-এ লাফ দেয়, যদিও ১% ধীর request গড়কে সামান্যই নড়ায়। এজন্যই tail latency দেখতে হয়। ১%-এর সামান্য নিচে হলে p99 ভালো দেখায়, কিন্তু p99.9 দেখায় না। IDLE বা COLD_MS বদলে আবার চালান।

তাই সিদ্ধান্তটি সহজ। রাতের রিপোর্ট বা ছবি ছোট করার কাজে ব্যবহারকারী অপেক্ষা করেন না, সেখানে cold start নিয়ে ভাবার দরকার নেই। ব্যবহারকারী-মুখী API-তে আগে মাপুন, তারপর init ছোট করুন, শেষে snapshot বা provisioned concurrency ভাবুন।

৫ · বিল — request, GB-second, আর memory যে CPU-ও কেনে

বিদ্যুৎ বিলে ওয়াট আর ঘণ্টা গুণ করে ইউনিট হয়: বড় হিটার অল্পক্ষণ, বা ছোট বাতি অনেকক্ষণ, বিল একই হতে পারে। FaaS-এ একই কাজ করে GB-second: memory × সময়। এর সাথে যোগ হয় প্রতি call-এর একটি ছোট ফি।

বিল = call সংখ্যা × call-এর দাম + call সংখ্যা × memory (GB) × সময় (s) × GB-second-এর দাম
এই পর্বের ধরে নেওয়া দাম

সব দাম অনুমিত গোল সংখ্যা, পর্ব ১৯-এর সাথে মিল রেখে, কোনো provider-এর আসল দাম নয়। Free tier ধরা হয়নি। প্রতি মিলিয়ন call-এ $০.২০, প্রতি GB-second-এ $০.০০০০২, HTTP gateway প্রতি মিলিয়নে $১, log প্রতি GB-তে $০.৫০, provisioned environment-এর অলস দাম প্রতি GB-second-এ $০.০০০০০৪। মাস ৩০ দিনের, অর্থাৎ ২৫,৯২,০০০ সেকেন্ড। ৫১২ MB-কে ০.৫ GB ধরা হলো। আসল দাম provider-এর pricing পাতায় দেখুন।

বিল এই ধাপ পর্যন্ত
Call ফি
Compute
Gateway + log
Init / provisioned
মোট
হিসাবঅনুমিত দাম

            
  1. কাজের মাপ। একটি API function, মাসে ৩ কোটি call, ৫১২ MB memory, গড়ে ১২০ ms চলে।
  2. Call ফি। ৩০ মিলিয়ন × $০.২০ = $৬।
  3. GB-second গোনা। ৩,০০,০০,০০০ × ০.৫ × ০.১২ = ১৮ লাখ GB-second. এখনো টাকায় আনা হয়নি।
  4. Compute খরচ। ১৮,০০,০০০ × $০.০০০০২ = $৩৬। মোট $৪২। অনেকে এখানেই থেমে যান।
  5. Gateway. HTTP-র সামনের gateway আলাদা বিল: $৩০। এখানে সেটি function-এর compute-এর প্রায় সমান।
  6. Log. প্রতি call-এ ১ KB log মানে ৩০ GB, $১৫। মোট $৮৭। Function-এর বিল $৪২, বাকিটা তার চারপাশের।
  7. Init-এর বিল। ১% call cold, প্রতিটিতে ০.৭৫ s init. Init বিলে ধরলে $২.২৫ বাড়ে, মোট $৮৯.২৫। Lambda ২০২৫ সালের আগস্ট থেকে managed runtime-এর zip function-এও init বিলে ধরে, আগে ধরত না। বিলের চেয়ে cold start-এর আসল দাম ব্যবহারকারীর অপেক্ষা। নিয়ম provider-ভেদে আলাদা, আর বদলায়।
  8. Provisioned হলে? ১০টি environment সারা মাস রাখলে $৫১.৮৪, আর init-এর সারি উঠে যায়। মোট $১৩৮.৮৪। অথচ গড় concurrency মাত্র ১১.৫৭ × ০.১২ ≈ ১.৩৯। ১০টি রাখা তাই সম্ভবত অপচয়। আগে পরের অংশের হিসাব করুন।

সময় কত ছোট এককে গোল করা হয়, সেটিও বিল বদলায়। Lambda একসময় ১০০ ms-এর ধাপে গোল করত, ২০২০ সালে ১ ms-এ নামে। ১২০ ms-এর function তখন ২০০ ms-এর বিল পেত: ওপরের $৩৬ হতো $৬০।

Memory বাড়ালে CPU-ও বাড়ে

অনেক platform-এ আপনি CPU আলাদা বাছেন না। Memory বাছলে তার অনুপাতে CPU আসে, যেমন মোবাইল প্যাকেজে ডেটা কিনলে সাথে মিনিটও আসে। লেখার সময়ে Lambda-র নথিতে আছে, প্রায় ১,৭৬৯ MB-তে একটি পূর্ণ vCPU-র সমান ক্ষমতা। তাই একটি ভুল ধারণা ভাঙে: “কম memory মানে কম বিল” সবসময় সত্য নয়। Memory দ্বিগুণ করলে দাম দ্বিগুণ, কিন্তু CPU-নির্ভর কাজের সময় অর্ধেক হলে বিল একই থাকে, আর উত্তর আসে দ্বিগুণ দ্রুত।

নিচের মডেলে দুই ধরনের কাজ। একটি CPU-তে ব্যস্ত ছবি ছোট করার কাজ, একক thread-এ, ৪০০ MB-র কাছাকাছি memory চায়। আরেকটি API, যা বেশিরভাগ সময় database-এর উত্তরের অপেক্ষায়। সংখ্যাগুলো একটি অনুমিত মডেল, আপনার function-এর মাপ নয়।

// Memory setting vs duration and cost. ALL numbers are ASSUMED round values.
const GB_S = 0.00002, PER_REQ = 0.0000002, FULL_CPU_MB = 1769, TIMEOUT = 30;
const SIZES = [128, 256, 512, 768, 1024, 1536, 1769, 2048, 3008];
const jobs = [
  { name: "image resize (CPU heavy)", cpu: 1.2, wait: 0.05, needMB: 400 },
  { name: "API call (mostly waiting)", cpu: 0.02, wait: 0.15, needMB: 100 },
];
function run(job, mb) {
  const share = Math.min(1, mb / FULL_CPU_MB);   // CPU grows with memory, up to one core
  const pressure = 1 + (job.needMB / mb) ** 2;   // tight memory means more garbage collection
  const sec = (job.cpu / share) * pressure + job.wait;
  const usd = (mb / 1024) * sec * GB_S + PER_REQ;
  return { mb, sec, perM: usd * 1e6 };
}
for (const job of jobs) {
  console.log(job.name + "\n    MB  duration s  $ per million");
  const rows = SIZES.map((mb) => run(job, mb));
  const ok = rows.filter((r) => r.sec <= TIMEOUT);
  const cheapest = ok.reduce((a, b) => (b.perM < a.perM ? b : a));
  for (const r of rows) {
    const mark = r === cheapest ? "  <- cheapest" : r.sec > TIMEOUT ? "  timeout" : "";
    console.log(String(r.mb).padStart(6), r.sec.toFixed(3).padStart(11),
      r.perM.toFixed(2).padStart(14) + mark);
  }
  const near = ok.filter((r) => r.perM <= cheapest.perM * 1.1);
  const fast = near.reduce((a, b) => (b.sec < a.sec ? b : a));
  console.log(`  fastest within 10% of cheapest: ${fast.mb} MB, ${fast.sec.toFixed(3)} s\n`);
}
image resize (CPU heavy)
    MB  duration s  $ per million
   128     178.591         446.68  timeout
   256      28.587         143.13
   512       6.727          67.47
   768       3.564          53.66
  1024       2.439          48.99
  1536       1.526          45.97
  1769       1.311          45.51  <- cheapest
  2048       1.296          52.03
  3008       1.271          74.88
  fastest within 10% of cheapest: 1769 MB, 1.311 s

API call (mostly waiting)
    MB  duration s  $ per million
   128       0.595           1.69  <- cheapest
   256       0.309           1.75
   512       0.222           2.42
   768       0.197           3.15
  1024       0.185           3.90
  1536       0.173           5.39
  1769       0.170           6.08
  2048       0.170           7.00
  3008       0.170          10.19
  fastest within 10% of cheapest: 256 MB, 0.309 s

ছবির কাজে ১২৮ MB-তে ৩০ সেকেন্ডের সীমাই পার (timeout). ২৫৬ MB-র বিল প্রতি মিলিয়নে $১৪৩.১৩, আর ১,৭৬৯ MB-র $৪৫.৫১: প্রায় সাত গুণ memory, বিল তিন ভাগের এক ভাগ, সময় প্রায় ২২ গুণ কম। ১,৭৬৯-এর পরে সময় প্রায় আর কমে না, কারণ একক thread একটির বেশি core কাজে লাগাতে পারে না, তাই বিল শুধু বাড়ে। এটিই U-আকৃতির খরচের রেখা।

অপেক্ষানির্ভর API-তে ছবি উল্টো। অপেক্ষার সময়ে CPU দরকার নেই, তাই ১২৮ MB সবচেয়ে সস্তা, $১.৬৯। তবে ২৫৬ MB-তে বিল মাত্র $১.৭৫, আর সময় ০.৫৯৫ থেকে ০.৩০৯ s. ব্যবহারকারী-মুখী কাজে শেষ লাইনের পছন্দটিই ভালো। Multi-thread কোড হলে ১,৭৬৯-এর পরেও লাভ হতে পারে, যা এই মডেলে নেই।

বাস্তবে এই কাজটি হাতে করতে হয় না। নিজের function-কে কয়েকটি memory-তে চালিয়ে মেপে নিন। AWS-এর জন্য AWS Lambda Power Tuning নামে একটি open source টুল আছে, যা ঠিক এই রেখা আঁকে।

৬ · Concurrency — ভিড় কখন ফেরত যায়

পর্ব ৬৮-এর Little's law এখানেও খাটে: একসাথে কত call চলছে, অর্থাৎ concurrency = rate × duration. সেকেন্ডে ১০০ call, প্রতিটি ০.৪ s হলে ৪০টি। Lambda-র সাধারণ মডেলে এক environment একসাথে একটি call-ই নেয়, তাই ৪০টি environment লাগে। Azure Functions আর Cloud Run functions-এ এক instance সেটিং অনুযায়ী একসাথে অনেক request নিতে পারে, তাই সেখানে instance কম লাগে, তবে কোডকে একসাথে অনেক কাজ সামলানোর উপযোগী হতে হয়।

সীমা তিনটি স্তরে আসে:

  • Account-এর সীমা: একটি region-এ মোট concurrency-র quota. লেখার সময়ে Lambda-র ডিফল্ট প্রায়ই ১,০০০, তবে নতুন account-এ কম হতে পারে, আর আবেদন করে বাড়ানো যায়। ঈদের আগের দিন আবেদন করবেন না।
  • Reserved concurrency: এক function-এর জন্য একটি সংখ্যা ঠিক করা। এটি দুই কাজ করে: ওই function-এর জন্য জায়গা নিশ্চিত রাখে, আবার সেটিই তার ছাদ। বিয়েবাড়িতে বরপক্ষের জন্য ২০টি টেবিল আলাদা রাখার মতো: অন্যরা বসবে না, কিন্তু বরপক্ষও ২১তম টেবিল পাবে না। Lambda-তে মান শূন্য করলে function পুরো থেমে যায়, যা জরুরি অবস্থায় কাজের। Azure আর Google-এ ছাদের কাজটি করে maximum instance-এর সেটিং।
  • Scale-up গতি: ছাদ অনেক ওপরে হলেও নতুন environment একসাথে অসীম ওঠে না। Provider-ভেদে গতি আলাদা, আর এই সংখ্যা বদলেছেও: লেখার সময়ে Lambda-র নথিতে প্রতি function প্রতি ১০ সেকেন্ডে ১,০০০ পর্যন্ত বাড়ার কথা আছে। এই পর্বে আমরা ছোট অনুমিত সংখ্যা নিচ্ছি।

সীমা পার হলে platform বাড়তি call ফিরিয়ে দেয়, যাকে বলে throttling. নিচে ছোট সংখ্যায় ঈদের টিকিট কাউন্টারের মতো একটি ভিড়: ছাদ ৬, প্রতি call ঠিক ১ s, শুরুতে ২টি warm environment, প্রতি সেকেন্ডে সর্বোচ্চ ২টি নতুন environment. Cold start-এর দেরি এই হিসাবে বাদ।

প্রতি সেকেন্ডের হিসাব
সেকেন্ড
Call এল
দরকার (rate × 1 s)
Environment
সেবা পেল
Throttled
  1. শুরুর অবস্থা। ■ মানে চলছে, □ মানে ছাদের ভেতরের ফাঁকা জায়গা, ✗ মানে ফেরত যাওয়া call. ২টি environment warm.
  2. সেকেন্ড ০: শান্ত। ২টি call, ২টি environment. সব ঠিক।
  3. সেকেন্ড ১: ভিড় শুরু। ৮টি call, দরকার ৮। ছাদ এখনো দূরে, কিন্তু এই সেকেন্ডে মাত্র ২টি নতুন environment ওঠে। সেবা পেল ৪, ফেরত ৪। এখানে বাধা scale-up-এর গতি।
  4. সেকেন্ড ২: ছাদে পৌঁছাল। আরও ২টি যোগে ৬। সেবা ৬, ফেরত ২। এবার বাধা ছাদ।
  5. সেকেন্ড ৩: টানা ভিড়। দরকার ৮, ছাদ ৬। আর কিছু ওঠার নেই, প্রতি সেকেন্ডে ২টি করে ফেরত যাবে।
  6. সেকেন্ড ৪: একই। এই চাপ যতক্ষণ চলবে, ক্ষতিও ততক্ষণ চলবে।
  7. সেকেন্ড ৫: ভিড় শেষ। ২টি call. ৪টি environment অলস, পরে shutdown হবে।
  8. হিসাব। ৩৬টি call-এর ২৬টি সেবা পেল, ১০টি throttled (৪ + ২ + ২ + ২)। এই ১০টির কী হবে? HTTP হলে client error পাবে। Storage event হলে platform পরে আবার চেষ্টা করবে। Queue হলে বার্তা সারিতেই অপেক্ষা করবে। Trigger-ই ঠিক করে, throttling মানে ক্ষতি, না শুধু দেরি।

এবার বড় আকারে, seeded PRNG দিয়ে: স্বাভাবিক সময়ে সেকেন্ডে ৫০ call, ১০ থেকে ৩০ সেকেন্ডে ১,০০০ call, প্রতিটি ০.৪ s. নতুন environment সেকেন্ডে সর্বোচ্চ ১০০টি, আর তার cold start ১ s. তিনটি নীতির তুলনা হয়েছে।

// A spike against a concurrency limit. Tick 0.1 s, seeded PRNG, all numbers ASSUMED.
const RUN = 0.4, COLD = 1.0, TICK = 0.1, RAMP = 100;   // RAMP: new environments per second
function mulberry32(a) { return () => { 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 rateAt = (s) => (s >= 10 && s < 30 ? 1000 : 50); // the spike lasts 20 s
function sim(limit, useQueue, show) {
  const rnd = mulberry32(7);
  let free = 25, busy = [], wait = 0, peak = 0, ok = 0, cold = 0, lost = 0, w = [0, 0, 0];
  for (let k = 0; k < 500; k++) {
    const s = k * TICK;
    busy = busy.filter((d) => (d > s + 1e-9 ? true : (free++, false)));
    const fresh = Math.round(rateAt(s) * TICK * (0.8 + 0.4 * rnd()));
    const n = fresh + wait; let budget = RAMP * TICK; wait = 0; w[0] += fresh;
    for (let i = 0; i < n; i++) {
      if (free > 0) { free--; busy.push(s + RUN); ok++; w[1]++; }
      else if (busy.length < limit && budget-- >= 1) {
        busy.push(s + COLD + RUN); ok++; cold++; w[1]++; }
      else if (useQueue) wait++;
      else { lost++; w[2]++; }
    }
    peak = Math.max(peak, wait);
    if (show && k % 50 === 49) {
      console.log(`${(k - 49) / 10}-${(k + 1) / 10}`.padStart(6),
        ...w.map((v) => String(v).padStart(8)), String(busy.length).padStart(8));
      w = [0, 0, 0]; }
  }
  return [ok, cold, lost, peak];
}
console.log("  secs  arrived  started  refused  running");
sim(300, false, true);
console.log("\npolicy              served  cold  refused  peak queue");
for (const [name, lim, q] of [["limit 300", 300, false], ["limit 1000", 1000, false], ["limit 300 + queue", 300, true]])
  console.log(name.padEnd(17), ...sim(lim, q, false).map((v, i) => String(v).padStart([8, 5, 8, 11][i])));
  secs  arrived  started  refused  running
   0-5      247      247        0       21
  5-10      255      255        0       19
 10-15     5045     2255     2790      300
 15-20     5158     3750     1408      300
 20-25     5160     3750     1410      300
 25-30     4890     3750     1140      300
 30-35      251      251        0       20
 35-40      250      250        0       22
 40-45      251      251        0       19
 45-50      251      251        0       18

policy              served  cold  refused  peak queue
limit 300            15010   275     6748           0
limit 1000           19258   451     2500           0
limit 300 + queue    21758   275        0        6748

ভিড়ের সময় দরকার ১,০০০ × ০.৪ = ৪০০। ছাদ ৩০০ হলে সর্বোচ্চ সেবা ৩০০ / ০.৪ = সেকেন্ডে ৭৫০, তাই ৫ সেকেন্ডে 3750, ঠিক টেবিলের মতো। 10-15 সারিতে ফেরত সবচেয়ে বেশি, 2790, কারণ তখন environment উঠছে।

limit 1000-এ ছাদ সমস্যা নয়, তবু 2500 call গেল। এগুলো ভিড়ের প্রথম কয়েক সেকেন্ডের, যখন RAMP পিছিয়ে ছিল। অর্থাৎ ছাদ বাড়ালেও হঠাৎ ভিড়ের শুরুর ধাক্কা থাকে। কিন্তু এই নীতিতে পেছনের database ৪০০ connection দেখবে।

limit 300 + queue-এ একটি call-ও হারায়নি, refused শূন্য। সারি উঠেছে 6748-এ, আর ভিড় শেষে সেকেন্ডে প্রায় ৭০০ করে কমে প্রায় ১০ সেকেন্ডে শেষ হবে। দাম হলো দেরি। ব্যবহারকারী অপেক্ষায় থাকলে এটি চলে না, পেছনের কাজে চমৎকার। বাস্তবে সারির সামনেও একটি সীমা ভাবতে হয়, যা পর্ব ৭০-এ আসবে।

৭ · সীমা, timeout আর database connection

প্রতিটি platform-এ কিছু শক্ত সীমা আছে। নিচের সংখ্যাগুলো লেখার সময়ের উদাহরণ, মুখস্থ করার জন্য নয়। Plan আর generation-ভেদে আলাদা, আর বদলায়। নকশার আগে নিজের provider-এর নথি দেখুন।

সীমাAWS Lambda (উদাহরণ)Azure Functions, Cloud Run functions
সর্বোচ্চ সময়১৫ মিনিটAzure Consumption plan-এ ১০ মিনিট, অন্য plan-এ বেশি। Google-এ HTTP-তে ৬০ মিনিট পর্যন্ত, event-এ কম
Memory১২৮ MB থেকে প্রায় ১০ GBPlan-ভেদে আলাদা
Temporary diskডিফল্ট ৫১২ MB, বাড়িয়ে প্রায় ১০ GBPlan-ভেদে আলাদা, কিছু জায়গায় memory থেকে কাটে
PackageZip প্রায় ২৫০ MB (খোলা অবস্থায়), image প্রায় ১০ GBPlan আর ধরনভেদে আলাদা
Request-এর আকারSynchronous-এ প্রায় ৬ MB, asynchronous-এ অনেক কমআলাদা সীমা, নথি দেখুন

Timeout সর্বোচ্চে রাখবেন না। ধরুন API-র gateway ৩০ সেকেন্ডে হাল ছেড়ে দেয়, আর function-এর timeout ১৫ মিনিট। Database আটকে গেলে gateway আগেই error দেবে, অথচ function চলতে থাকবে, concurrency ধরে রাখবে, বিল বাড়াবে। নিয়মটি হলো: function-এর timeout ডাকা পক্ষের চেয়ে কম, আর database call-এর timeout function-এর চেয়ে কম। context-এ বাকি সময় দেখে কাজ গুটিয়ে ফেলাও ভালো অভ্যাস।

Connection reuse আর connection storm

পর্ব ২২-এ দেখেছেন, database connection দামি আর max_connections সীমিত। Function-এ সমস্যাটি আরও তীব্র, কারণ প্রতিটি environment নিজের জন্য আলাদা। Pool ভাগ করে নেওয়ার কেউ নেই। প্রথম নিয়ম: connection handler-এর ভেতরে প্রতিবার খুলবেন না। বাইরে রাখুন, অলস অবস্থায় মরে গেলে আবার খুলুন।

# উদাহরণ — psycopg আর একটি আসল database লাগে, এই পাতায় চলবে না
import psycopg

DSN = fetch_secret("shop/db")              # your secret-manager call, runs during init
conn = None                                # survives warm invokes, one per environment

def get_conn():
    global conn
    if conn is None or conn.closed:        # a frozen connection may have died
        conn = psycopg.connect(DSN, connect_timeout=2)
    return conn

def handler(event, context):
    with get_conn().cursor() as cur:
        cur.execute("SELECT stock FROM item WHERE id = %s", (event["id"],))
        return {"stock": cur.fetchone()[0]}

এতে warm call-এ connection-এর খরচ বাঁচে। কিন্তু দ্বিতীয় সমস্যা থেকে যায়: ১ environment = ১ connection. ৮০০টি environment মানে ৮০০টি connection, আর database-এর সীমা ধরুন ৩০০। Platform খুশিমনে স্কেল করল, database পড়ে গেল। এটি একধরনের connection storm. Database ধীর হলে duration বাড়ে, Little's law-এ concurrency বাড়ে, আরও environment ওঠে, আরও connection চায়, আর error-এ retry যোগ হয়। চাপ নিজেকেই বাড়ায়।

প্রতিরক্ষার স্তরগুলো একসাথে ব্যবহার করুন:

  • ছাদ: Reserved concurrency-কে database-এর নিরাপদ সীমার নিচে রাখুন। ভিড় throttling-এ আটকাবে, database-এ নয়।
  • মাঝখানে proxy: Database proxy, যেমন RDS Proxy বা PgBouncer ধরনের সেবা, অনেক client-এর জন্য অল্প connection ভাগ করে।
  • সামনে queue: লেখার কাজ queue-তে রাখলে function ধীরগতিতে database-এ ঢালে।
  • ভিন্ন database: HTTP-ভিত্তিক data API বা managed NoSQL-এ এই connection-এর সমস্যা অনেক কম। পর্ব ২৩ দেখুন।

HTTP call-এর ক্ষেত্রেও একই কথা: SDK client handler-এর বাইরে বানালে TLS connection warm call-এ আবার কাজে লাগে।

৮ · Package, log, trace আর local test

Zip না container image

Zip packageContainer image
কী দেনকোড আর libraryপুরো filesystem, Dockerfile দিয়ে
Runtime patchManaged runtime-এ provider সামলানBase image আপনাকে আবার build করতে হয়
আকারছোট সীমাঅনেক বড় সীমা, native library-র জন্য ভালো
বাড়তি লাভসহজ, দ্রুত deploy; ভাগের library রাখতে layerএকই build আর scanning টুল, পরিচিত পদ্ধতি

Image মানেই সবসময় ধীর cold start নয়, কারণ provider দরকারি অংশ আগে আনেন। তবুও মেপে দেখুন। নিচের উদাহরণটি AWS SAM-এর একটি ছোট template, যেখানে এই পর্বের প্রায় সব সিদ্ধান্ত এক জায়গায় লেখা। ছাদ আর timeout কোডের সাথে version control-এ থাকে। Terraform-এও একই জিনিস করা যায়। চালালে account লাগবে, খরচ হতে পারে, আর পরীক্ষা শেষে sam delete দিয়ে মুছতে হবে।

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
  ReportFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: python3.13
      Handler: app.handler
      CodeUri: src/
      MemorySize: 512
      Timeout: 20
      ReservedConcurrentExecutions: 50
      Tracing: Active
      Events:
        Hourly:
          Type: Schedule
          Properties:
            Schedule: rate(1 hour)

Log আর trace

SSH করার মতো কোনো server নেই। তাই log আর metric-ই আপনার চোখ। Stdout-এ লেখা log সাধারণত নিজে থেকেই provider-এর log সেবায় যায়। প্রতিটি লাইনে request id বা correlation ID রাখুন। Platform-ও প্রতি invoke-এর পরে একটি হিসাব লেখে। নিচের লাইনগুলো AWS Lambda-র ধরনের, নমুনা আউটপুট — version-ভেদে আলাদা হতে পারে:

START RequestId: 11111111-2222-3333-4444-555555555555
{"level":"info","order":"ord-1042","msg":"report built"}
END RequestId: 11111111-2222-3333-4444-555555555555
REPORT RequestId: 11111111-2222-3333-4444-555555555555  Duration: 62.41 ms  Billed Duration: 805 ms  Memory Size: 512 MB  Max Memory Used: 96 MB  Init Duration: 742.10 ms

এই একটি লাইনেই অনেক উত্তর। Init Duration থাকা মানে এটি cold start. Max Memory Used ৯৬ MB, অথচ দেওয়া ৫১২ MB: সম্ভবত অনেক বেশি, তবে আগে CPU-র দিকটি মেপে দেখুন। যে metric-গুলো নিয়মিত দেখবেন: invocation, error, duration-এর p99, throttle, concurrency, আর asynchronous-এ event-এর বয়স।

একটি request gateway, function, queue আর আরেকটি function পার হলে শুধু log-এ পথ ধরা কঠিন। সেখানে কাজে লাগে distributed tracing: পর্ব ৬৫-এর OpenTelemetry, বা provider-এর সেবা (AWS X-Ray, Azure Application Insights, Google Cloud Trace). Cold start আলাদা span হিসেবে দেখা গেলে ধীর request-এর কারণ সাথে সাথে বোঝা যায়। মনে রাখবেন, function freeze হয় বলে পেছনে জমে থাকা trace বা log আগেই পাঠিয়ে দিতে হয়।

Local test

তিন স্তরে পরীক্ষা করুন, আর প্রতিটি স্তরের সীমা জানুন:

  1. Unit test: handler.py-র মতো, হিসাব আলাদা pure ফাংশনে রেখে সরাসরি। দ্রুত, কোনো cloud লাগে না। এখানেই বেশিরভাগ পরীক্ষা হওয়া উচিত।
  2. Local emulator: AWS SAM CLI-র sam local invoke, Azure Functions Core Tools, Google-এর Functions Framework. আসল provider-এর নমুনা event ফাইল দিয়ে চালান। এগুলো কোড চালায়, কিন্তু IAM, সীমা আর network-এর আসল আচরণ দেখায় না।
  3. ছোট cloud পরিবেশ: আলাদা, throwaway একটি পরিবেশে অল্প কিছু integration test. আসল permission আর trigger শুধু এখানেই ধরা পড়ে। Budget alert রাখুন, কাজ শেষে মুছে ফেলুন।

৯ · নিরাপত্তা, আর edge-এর WebAssembly

Function-এ OS patch আপনার কাজ নয়, কিন্তু কোড, library আর permission আপনারই। সবচেয়ে কার্যকর অভ্যাসটি সহজ: প্রতিটি function-এর জন্য একটি আলাদা role, আর তাতে least privilege. সব function-কে এক বড় role দিলে একটির একটি ত্রুটি পুরো account-এর সমস্যা হতে পারে। নিচের policy একটি thumbnail function-এর: শুধু uploads/ পড়ে, শুধু thumbs/-এ লেখে। পর্ব ৩২-এ এর মূল্যায়ন দেখেছেন। Log-এ লেখার অনুমতি আলাদা, আর সেটিও নিজের log group-এ সীমিত রাখুন।

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example-bucket/uploads/*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::example-bucket/thumbs/*"
    }
  ]
}
  • Event-কে অবিশ্বাস করুন: Function যেকোনো পথ দিয়ে ডাক পেতে পারে। HTTP body হোক বা queue-র বার্তা, ব্যবহারের আগে যাচাই করুন।
  • Secret কোডে নয়: Secret manager থেকে আনুন, আর init-এ cache করুন। Environment variable-এ সরাসরি সংবেদনশীল মান রাখার ঝুঁকি নিয়ে পর্ব ৩৩ দেখুন।
  • কে ডাকতে পারে: Function-এর resource policy-তে শুধু সঠিক trigger থাকুক। পাবলিক URL রাখলে authentication আর আলাদা WAF-এর কথা ভাবুন।
  • Library: পর্ব ৪১-এর মতো version বেঁধে scan করুন। Provider বন্ধ হওয়া runtime-কে একসময় সরিয়ে দেন, তাই runtime version-ও নিয়মিত দেখুন।

Edge-এ WebAssembly, সংক্ষেপে

Edge function ব্যবহারকারীর কাছের CDN বিন্দুতে চলে। সেখানে প্রতি request-এ একটি microVM ওঠানোও ভারী, তাই কিছু platform হালকা sandbox ব্যবহার করে: একই process-এ V8 isolate (Cloudflare Workers-এর পরিচিত মডেল), বা WebAssembly module. শুরু হয় মিলিসেকেন্ড বা তার কম সময়ে, তাই cold start প্রায় চোখে পড়ে না। দাম হলো কড়া সীমা: কম সময়, কম memory, সব library চলে না, আর database অনেক দূরে থাকতে পারে। URL বদলানো, লগইন যাচাই, A/B পরীক্ষার মতো ছোট কাজে এটি ভালো। ভারী কাজ region-এর function-এই থাক। Isolation-এর তুলনা Operating Systems কোর্সে আর edge-এর নকশা System Design কোর্সে পাবেন।

আরও গভীরে: Lambda আর Fargate-এর পেছনের Firecracker নিয়ে Agache ও সহ-লেখকদের গবেষণাপত্র (NSDI, ২০২০) দেখায়, কেন container-এর বদলে হালকা VM বেছে নেওয়া হলো। Container image দ্রুত চালু করার কৌশল আছে Brooker ও সহ-লেখকদের “On-demand Container Loading in AWS Lambda” (USENIX ATC, ২০২৩)-এ। আর Azure Functions-এর আসল traceভিত্তিক “Serverless in the Wild” (Shahrad ও সহ-লেখক, USENIX ATC, ২০২০) দেখায়, অলস environment কতক্ষণ রাখা ভালো, তা একটি স্থির সংখ্যায় বাঁধা কেন বোকামি হতে পারে।

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

  • Cold start কী, কমাবেন কীভাবে? নতুন environment-এর init-এর দেরি। Package আর init ছোট করে, হালকা runtime, snapshot বা provisioned concurrency দিয়ে।
  • Reserved আর provisioned concurrency-র পার্থক্য? Reserved হলো জায়গা ও ছাদ, আগে থেকে কিছু চালু রাখে না। Provisioned হলো আগে init করে রাখা environment, যার বিল অলসেও চলে।
  • ২০০ req/s, ০.৫ s হলে concurrency? ২০০ × ০.৫ = ১০০।
  • Function-এ database connection কোথায় খুলবেন? Handler-এর বাইরে, reuse করে, আর ছাদ বা proxy দিয়ে মোট সংখ্যা বেঁধে।
  • Memory বাড়ালে বিল কি বাড়বেই? না। CPU-ও বাড়ে, সময় কমলে বিল সমান বা কমও হতে পারে।

১০ · AI-এর যুগে serverless

AI কয়েক সেকেন্ডে একটি চলা function লিখে দেয়। কিন্তু “চলে” মানে এই নয় যে ভিড়ে টিকবে, বা বিল সামলাবে। নিচের ভুলগুলো এই বিষয়ে খুব পরিচিত।

AI প্রায়ই যা করেকীভাবে ধরবেন
Function role-এ "Action": "*" বা পুরো bucket দেয়প্রতিটি action কেন লাগে জিজ্ঞেস করুন, resource ছোট করুন
Handler-এর ভেতরে প্রতিবার database connection খোলেবাইরে নিন, rate × duration দিয়ে connection হিসাব করুন
Concurrency-র ছাদ বা timeout দেয় না, বা সর্বোচ্চ দেয়Database আর caller-এর সীমা থেকে মান বের করুন
একই bucket-এ পড়া ও লেখা, ফলে recursive loopTrigger-এর prefix আর লেখার জায়গা মিলিয়ে দেখুন
Retry আর ডুপ্লিকেট event ধরে নাএকই event দুবার চালিয়ে ফল দেখুন
পুরোনো runtime, সীমা বা দাম বলে; অস্তিত্বহীন setting বানায়Provider-এর নথি দেখুন, terraform plan বা sam validate চালান
Secret environment variable-এ বা কোডে বসায়, 0.0.0.0/0 খোলে, storage public করে, latest tag দেয়Secret manager, ছোট network নিয়ম আর নির্দিষ্ট version ব্যবহার করুন
Gateway, log আর data transfer বাদ দিয়ে বিল বলেপ্রতিটি সারি আলাদা করে লিখতে বলুন, budget alert রাখুন
terraform destroy, rm -rf সতর্কতা ছাড়াই দেয়কী মুছবে আগে পড়ুন, throwaway পরিবেশে চালান

AI-কে শুধু কোড নয়, অনুমানগুলোও লিখতে বলুন। নিচের prompt-এ নিজের সংখ্যা বসান। কোনো secret, key বা account id পেস্ট করবেন না।

আমি একটি serverless function নকশা করছি। কাজ: [কী করে]. Trigger: [HTTP / queue / storage / schedule]. Traffic: [গড় ও সর্বোচ্চ req/s]. Duration: [মাপা সময়]. Memory: [মাপা সর্বোচ্চ]. Downstream: [database, max_connections কত]. Provider: [নাম]. ১. Rate × duration দিয়ে গড় ও সর্বোচ্চ concurrency হিসাব করে দেখাও। ২. Reserved concurrency, timeout আর memory-র প্রস্তাবিত মান দাও, প্রতিটির কারণসহ। ৩. Function role-এর সবচেয়ে ছোট policy লেখো। কোনো "*" থাকলে কারণ বলো। ৪. Throttling হলে এই trigger-এ event-এর কী হবে, আর একই event দুবার এলে কী হবে, তা বলো। ৫. মাসিক বিল সারি ধরে দাও। প্রতিটি দাম "অনুমিত" লিখো, আর gateway ও log বাদ দিও না। ৬. তোমার জানা সীমাগুলো কোন তারিখের, আর provider-এর কোন পাতায় মেলাব, তা বলো।

AI-এর লেখা function হাতে পেলে — চেকলিস্ট

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

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

প্র ০১“Serverless” তো server-ছাড়া নয়। তাহলে এই নামটি টিকে গেল কেন, আর এটি আসলে কী বদলাল?

নামটি ঠিক অর্থে ভুল, আর সবাই তা জানেন। ২০১২ সালে Ken Fromm-এর একটি লেখায় শব্দটি ব্যবহার হয়েছিল বলে প্রায়ই উল্লেখ করা হয়। তবে ধারণাটি আরও পুরোনো। Google App Engine ২০০৮ সালে কোড নিয়ে নিজে স্কেল করত, আর ২০০৬ সালের S3-তে আপনি কোনো disk গোনেন না। Lambda ২০১৪ সালে নতুন যা আনল, তা হলো compute-এর একক ছোট করে দেওয়া: ঘণ্টার VM নয়, একটি event, আর অলস সময়ের বিল শূন্য।

নামটি টিকেছে, কারণ এটি একটি দায়িত্বের সরে যাওয়াকে বোঝায়। Capacity planning, OS patch, redundancy provider-এর কাছে গেল। ২০১৯ সালের Berkeley-র “Cloud Programming Simplified” লেখাটি একে cloud-এর পরের ধাপ হিসেবে দেখে, যেখানে স্কেল আর বিল ব্যবহারের সাথে বাঁধা। সংজ্ঞা নিয়ে অবশ্য একমত নন সবাই। কেউ বলেন scale to zero না থাকলে serverless নয়। কেউ বলেন আসল কথা হলো server-এর কথা না ভাবা। Provider-রা বিপণনে শব্দটি আরও ঢিলা করে ব্যবহার করেন।

লক্ষ করুন, দায়িত্ব চলে যায়নি, বদলে গেছে। Server গোনা গেল, এলো concurrency সীমা, connection হিসাব, retry, idempotency, আর বিলের নজরদারি। অপারেশনের কাজ শেষ হয়নি, অন্য চেহারা নিয়েছে। Hellerstein ও সহ-লেখকদের ২০১৯ সালের সমালোচনামূলক লেখাও এই সীমার দিকেই আঙুল তোলে।

মূল উপলব্ধি: Serverless মানে “চিন্তা নেই” নয়, মানে “অন্য চিন্তা”। কোন চিন্তা আপনার দল বেছে নিচ্ছে, সেটিই আসল সিদ্ধান্ত।

প্র ০২Container তো আগেই ছিল। তাহলে AWS function-এর জন্য microVM (Firecracker) বানাল কেন, আর snapshot-এ কী ঝুঁকি আছে?

Provider-এর সবচেয়ে কঠিন সমস্যা হলো অচেনা গ্রাহকের কোড পাশাপাশি চালানো। Container একই kernel ভাগ করে, তাই kernel-এর একটি ত্রুটি দুই গ্রাহকের দেয়াল ভাঙতে পারে। পুরো VM নিরাপদ, কিন্তু ভারী আর ধীর। ফলে প্রথম দিকে Lambda এক গ্রাহকের কাজ এক VM-এ রাখত, যাতে অনেক জায়গা অব্যবহৃত থাকত।

২০১৮ সালে AWS Firecracker খোলা source হিসেবে প্রকাশ করে, আর ২০২০ সালের NSDI গবেষণাপত্রে এর নকশা ব্যাখ্যা করে। মূল চিন্তা: KVM-এর ওপর একটি খুব ছোট virtual machine monitor, যা Rust-এ লেখা, প্রায় কোনো পুরোনো device নকল করে না। গবেষণাপত্রে এক microVM প্রায় ১২৫ ms-এ চালু হওয়া আর খুব কম memory নেওয়ার কথা আছে। ফলে একটি যন্ত্রে হাজারো ছোট, পরস্পর-বিচ্ছিন্ন function চলতে পারে। VM-এর দেয়াল, container-এর ঘনত্বের কাছাকাছি।

Snapshot এর পরের ধাপ। Boot করাও যদি বাদ দেওয়া যায়? Init শেষের memory-র ছবি থেকে জাগালে JVM-এর মতো ভারী runtime-ও দ্রুত আসে। AWS ২০২২ সালে Java-র জন্য SnapStart আনে, পরে আরও runtime-এ বাড়ায়। কিন্তু এখানে একটি সূক্ষ্ম বিপদ আছে। এক ছবি থেকে অনেক environment জাগলে সবার memory একই। Init-এ বানানো random seed হলে সবাই একই “এলোমেলো” সংখ্যা পাবে, যা token বা encryption-এ বিপজ্জনক। পুরোনো সময় আর মরা connection-ও ছবিতে আটকে থাকে। তাই জাগার পরে এসব নতুন করে বানানোর hook দেওয়া হয়।

মূল উপলব্ধি: Cold start কমানোর গল্প আসলে isolation আর গতির দর-কষাকষি। প্রতিটি নতুন কৌশল একটি অনুমান বদলায়, আর সেই অনুমানের ওপর ভর করা কোড নতুন করে যাচাই করতে হয়।

প্র ০৩Provider অলস environment কতক্ষণ রাখবেন? ১০ মিনিট, এক ঘণ্টা, না চিরকাল? সিদ্ধান্তটি এত কঠিন কেন?

অলস environment memory ধরে রাখে, যা provider-এর খরচ, অথচ বিল নেই। তাড়াতাড়ি মুছলে গ্রাহক cold start পান, আর init-এর CPU-ও আবার খরচ হয়। তাই এটি একটি অর্থনৈতিক দোটানা, ঠিক অপারেটিং সিস্টেমের page cache বা CDN-এর মতো: কী রাখব, কী ফেলব।

২০২০ সালে Microsoft Research-এর সাথে যুক্ত গবেষকদের “Serverless in the Wild” এই প্রশ্নে আসল Azure Functions-এর ব্যবহারের ধরন বিশ্লেষণ করে। দেখা যায়, বেশিরভাগ application খুব কম ডাকা হয়, আবার অল্প কিছু অনেক বেশি। ডাকার হারে পার্থক্য বিশাল, তাই সবার জন্য এক স্থির সময় হয় memory নষ্ট করে, নয়তো বহু cold start দেয়। তাঁদের প্রস্তাব ছিল প্রতিটি application-এর আগের ডাকের ফাঁক দেখে শেখা একটি নীতি, যা পরের ডাক আসার আগে environment আগেভাগে তৈরিও করতে পারে।

অংশ ৪-এর সিমুলেটর এই কথাটিই দেখায়। ঘণ্টায় ৬ call হলে ১০ মিনিটের সীমা প্রায় ৩৭% cold দেয়। সীমা ৩০ মিনিট হলে সূত্রে e⁻³ ≈ ৫%, কিন্তু প্রতিটি অলস ফাঁকে অনেক বেশি memory আটকে থাকে। মিনিটে হাজার call-এর function-এ সীমার মান প্রায় অর্থহীন।

গ্রাহকের দিক থেকে একটি শিক্ষা আছে। এই সংখ্যা provider-এর ভেতরের সিদ্ধান্ত, নথিতে সাধারণত নিশ্চিত করা নেই, আর না জানিয়েই বদলাতে পারে। তাই “১৫ মিনিট warm থাকে” ধরে কোনো নকশা করবেন না। নিশ্চয়তা দরকার হলে provisioned concurrency কিনুন, যেখানে শর্তটি লেখা আছে।

মূল উপলব্ধি: Cold start কোনো bug নয়, মিলিয়ে ব্যবহার করা সম্পদের স্বাভাবিক দাম। বেশি গ্রাহক এক যন্ত্রে বসাতে পারাই function-কে সস্তা করে।

প্র ০৪একটি relational database-এর সামনে হাজার function রাখা কি ভুল নকশা? কোন পথে যাবেন?

দুই জগতের অনুমান এখানে মুখোমুখি। Function-এর জগৎ বলে: ছোট, স্বল্পায়ু, অনেকগুলো, একসাথে। PostgreSQL বা MySQL-এর মতো database-এর জগৎ বলে: অল্প কিছু দীর্ঘস্থায়ী client, প্রতিটি connection-এর জন্য বাঁধা সম্পদ। পর্ব ২২-এ দেখেছেন, PostgreSQL প্রতিটি connection-এ একটি process চালায়।

একটি কাল্পনিক অনলাইন বাজার অ্যাপ ধরুন। স্বাভাবিক সময়ে সব ঠিক। ঈদের অফারে সেকেন্ডে ২,০০০ request, প্রতিটি ০.১ s, তাই ২০০ concurrency, সীমার নিচে। এবার একটি ধীর query-তে duration হলো ১ s. হঠাৎ concurrency ২,০০০। নতুন connection খুলতে database-এর CPU যায়, ফলে query আরও ধীর। এটি একটি ধনাত্মক feedback loop: চাপ নিজেই আরও চাপ তৈরি করে, আর আসল ভিড় কমলেও সিস্টেম সহজে ফেরে না।

সমাধান একটি নয়, স্তর:

  • Reserved concurrency: সবচেয়ে সস্তা আর জরুরি। সংখ্যাটি database-এর নিরাপদ সীমা থেকে আসে।
  • Proxy: RDS Proxy ২০২০ সালে সাধারণভাবে পাওয়া যেতে শুরু করে। তবে কিছু session-সংক্রান্ত আচরণে connection ভাগ হয় না (pinning), আর proxy-র নিজস্ব বিল আছে।
  • লেখা queue-তে: কাজ জমা নেন, ধীরে লেখেন। উত্তর তখন দেরিতে আসে, তাই পণ্যের ধরনও ভাবতে হয়।
  • ভিন্ন ধরনের database: HTTP-র ওপর কাজ করে এমন সেবা, বা key-value store. তখন transaction আর query-র স্বাধীনতায় ছাড় দিতে হতে পারে।
  • অন্য compute: এই অংশটি যদি সারাদিন ভরা থাকে, পর্ব ১৯-এর হিসাবে এটি container-এ যাওয়ার সময় হয়েছে, যেখানে ছোট pool-এ অনেক request চলে।

মূল উপলব্ধি: নকশাটি ভুল নয়, তবে সীমাহীন স্কেল-এর সামনে সীমিত সম্পদ রাখলে মাঝখানে একটি ভালভ বসাতেই হবে। সিস্টেমের ক্ষমতা তার সবচেয়ে সরু অংশের সমান।

প্র ০৫“WASM+WASI ২০০৮ সালে থাকলে Docker বানাতে হতো না”: কথাটি কতটা সত্যি? WebAssembly কি function-এর ভবিষ্যৎ?

২০১৯ সালে Docker-এর সহ-প্রতিষ্ঠাতা Solomon Hykes এমন একটি মন্তব্য করেন, যা বহুল আলোচিত। WebAssembly জন্মেছিল ব্রাউজারে দ্রুত, নিরাপদ কোড চালাতে। ২০১৭ সালের দিকে প্রধান ব্রাউজারগুলো এটি সমর্থন করে, ২০১৯ সালে W3C একে সুপারিশ হিসেবে প্রকাশ করে। WASI নামের কাজটি একে ব্রাউজারের বাইরে ফাইল, সময় আর network-এর মতো জিনিসে পৌঁছানোর একটি মানসম্মত পথ দিতে চায়।

আকর্ষণ পরিষ্কার। একটি module ডিফল্টে কিছুই ছুঁতে পারে না, প্রতিটি ক্ষমতা আলাদা করে দিতে হয়, যা least privilege-এর সাথে খুব মেলে। Module ছোট, মিলিসেকেন্ডের কম সময়ে শুরু হতে পারে, আর একই binary নানা CPU-তে চলে। ফলে যে cold start নিয়ে এত আলোচনা, সেটি প্রায় হারিয়ে যায়।

কিন্তু Hykes নিজেই পরের বার্তায় বলেন, এতে Docker অপ্রয়োজনীয় হবে না, দুটি পাশাপাশি থাকবে। বাস্তব সীমাগুলোও দেখুন। অনেক ভাষা আর native library এখনো সহজে চলে না। Thread, socket আর debugging-এর মান এখনো তৈরি হচ্ছে। লম্বা, CPU-ভারী কাজে native code অনেক সময় এগিয়ে। আর পুরো Linux-নির্ভর software, যেমন একটি database, container-এই স্বাভাবিক।

তাই এখনকার ছবিটি মিশ্র। Cloudflare Workers-এর মতো platform JavaScript isolate-এর পাশে WebAssembly চালায়, Fastly-র Compute-ও WebAssembly-ভিত্তিক। অর্থাৎ edge-এ, যেখানে লাখো ছোট কাজ, এটি ইতিমধ্যে কাজে লাগছে। Region-এর সাধারণ function-এ microVM এখনো প্রধান। বিষয়টি দ্রুত বদলাচ্ছে, তাই লেখার সময়ের এই ছবি আবার যাচাই করে নেবেন।

মূল উপলব্ধি: নতুন sandbox পুরোনোকে পুরো সরায় না, একটি নির্দিষ্ট জায়গা নেয়। প্রতিটি স্তরে শুরুর গতি আর সামর্থ্যের মধ্যে একটি দর-কষাকষি থাকে।

অনুশীলন

কলম-কাগজ নিন, বা ওপরের সিমুলেটরে সংখ্যা বদলান। সব দাম অনুমিত। কোনো cloud account লাগবে না।

  1. বিল হিসাব: একটি function মাসে ৮০ লাখ বার চলে, ১ GB memory-তে ০.২৫ s. প্রতি মিলিয়ন call-এ $০.২০, প্রতি GB-second-এ $০.০০০০২। (ক) GB-second কত? (খ) মোট বিল? (গ) Memory ০.৫ GB করলে সময় হয় ০.৪ s. নতুন বিল? (ঘ) Peak ১০০ req/s হলে দুই setting-এ peak concurrency কত?

    (ক) ৮০,০০,০০০ × ১ × ০.২৫ = ২০,০০,০০০ GB-second.

    (খ) ২০,০০,০০০ × $০.০০০০২ = $৪০। Call ফি ৮ × $০.২০ = $১.৬০। মোট $৪১.৬০.

    (গ) ৮০,০০,০০০ × ০.৫ × ০.৪ = ১৬,০০,০০০ GB-second, অর্থাৎ $৩২। মোট $৩৩.৬০। সস্তা হলো, কারণ সময় ২ গুণের কম বাড়ল।

    (ঘ) ১০০ × ০.২৫ = ২৫, আর ১০০ × ০.৪ = ৪০। কম memory-তে $৮ বাঁচল, কিন্তু ব্যবহারকারী বেশি অপেক্ষা করেন আর concurrency ৬০% বাড়ে। Connection সীমা কাছে থাকলে সস্তাটি ঠিক পছন্দ নাও হতে পারে।

  2. নকশার সমালোচনা: একজন লিখলেন: “ছবি উঠলে একটি function uploads bucket থেকে পড়ে, ছোট করে একই bucket-এ রাখে, তারপর database-এ row লেখে। প্রতি call-এ নতুন connection খোলে। Role: AdministratorAccess. Timeout ১৫ মিনিট, কোনো concurrency ছাদ নেই। প্রতিটি ব্যবহারকারী কয়টি ছবি তুলেছেন, তা module variable-এ গোনা হয়।” ভুলগুলো ধরুন, ঠিক করুন।
    • Recursive loop: একই bucket-এ লেখা আবার trigger করে। আলাদা bucket বা thumbs/ prefix, আর trigger শুধু uploads/-এ।
    • Connection: বাইরে নিন, proxy ভাবুন, ছাদ দিন।
    • Role: শুধু দরকারি object পড়া, লেখা আর log.
    • Timeout: মাপা সময়ের ওপর অল্প জায়গা রেখে, যেমন ৩০ s.
    • ছাদ নেই: Database-এর সীমা থেকে reserved concurrency.
    • গণনা: Module variable প্রতিটি environment-এ আলাদা, আর হারায়। Database-এ রাখুন।
    • ডুপ্লিকেট: Storage event দুবার আসতে পারে, তাই row লেখা idempotent হোক, যেমন object key-তে unique নিয়ম।
  3. আউটপুট অনুমান করুন: ধরুন makeEnv একটি environment-এর init, আর ফেরত আসা ফাংশনটি handler. না চালিয়ে আউটপুট বলুন।
    function makeEnv(name) {
      let count = 0;
      console.log("init " + name);
      return () => { count++; console.log(name, count); };
    }
    const a = makeEnv("A");
    a(); a();
    const b = makeEnv("B");
    b(); a();
    init A
    A 1
    A 2
    init B
    B 1
    A 3

    Init প্রতি environment-এ একবার। count প্রতিটির নিজের: B শুরু করে ১ থেকে, আর পরে A নিজের ২-এর পরে ৩-এ যায়। মোট ৪টি call, কিন্তু কেউই ৪ দেখায় না। এজন্যই environment-এর ভেতরের গণনা ভরসার যোগ্য নয়।

  4. ছাদ বের করুন: Function-এ সেকেন্ডে ২০০ call আসে, প্রতিটি ০.৩ s. প্রতি environment একটি database connection নেয়। max_connections ১০০, যার ১০টি admin-এর। (ক) এখন concurrency কত, সীমার নিচে কি? (খ) Database ধীর হয়ে duration ০.৬ s হলে? (গ) Reserved concurrency কত রাখবেন? (ঘ) তখন (খ)-এর অবস্থায় সেকেন্ডে কতটি call সেবা পাবে, কতটি throttled?

    (ক) ২০০ × ০.৩ = ৬০, সীমা ৯০-এর নিচে।

    (খ) ২০০ × ০.৬ = ১২০, সীমার ওপরে।

    (গ) সর্বোচ্চ ৯০। বাস্তবে একটু কম রাখা ভালো, যেমন ৮০, কারণ deploy-এর সময় পুরোনো আর নতুন environment মিলে connection বেশি হতে পারে।

    (ঘ) ৯০ / ০.৬ = সেকেন্ডে ১৫০ সেবা, ৫০ throttled. Trigger queue হলে এই ৫০টি অপেক্ষা করবে, HTTP হলে client error পাবে। Database তবু বেঁচে থাকবে, যা না থাকলে ২০০টির সবই ব্যর্থ হতে পারত।

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

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

ক্রমফাঁকা environment না থাকলে একটি function call-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Event আসে, ফাঁকা environment পাওয়া যায় না
  2. নতুন environment তৈরি হয়, কোড আনা হয়
  3. Runtime চালু হয়, handler-এর বাইরের কোড চলে
  4. Handler event নিয়ে চলে, উত্তর দেয়
  5. Environment freeze হয়, পরের event-এর অপেক্ষায়

একটি serverless function-এ handler-এর বাইরে লেখা কোড (যেমন import বা client তৈরি) সাধারণত কখন চলে?

  • প্রতিটি invoke-এ একবার করে
  • প্রতিটি নতুন environment-এর init ধাপে একবার
  • Deploy-এর সময় মাত্র একবার, সব environment-এর জন্য
  • শুধু shutdown-এর সময়

একটি function ১,০২৪ MB (১ GB ধরুন) memory-তে ০.৫ s চলে, মাসে ৪০ লাখ বার। প্রতি GB-second-এর অনুমিত দাম $০.০০০০২ হলে compute-এর মাসিক বিল কত?

  • $২০
  • $৪
  • $৪০
  • $৪০০

একটি function-এ সেকেন্ডে ৩০০ call আসে, প্রতিটি ০.৫ s, এক environment একসাথে একটি call নেয়, আর reserved concurrency ১০০। Environment আগে থেকেই তৈরি ধরলে কী হবে?

  • দরকার ১৫০; সেকেন্ডে প্রায় ১০০টি call throttled
  • দরকার ৬০০; সেকেন্ডে প্রায় ৫০০টি call throttled
  • দরকার ১৫০; কোনো throttling নেই
  • দরকার ১০০; সেকেন্ডে ৫০টি call throttled

Reserved concurrency আর provisioned concurrency সম্পর্কে কোন কথাটি ঠিক?

  • দুটি একই জিনিসের দুই নাম
  • Reserved concurrency cold start দূর করে
  • Provisioned concurrency-র অলস সময়ে কোনো বিল নেই
  • Reserved জায়গা ও ছাদ দেয়; provisioned আগে থেকে init করা environment রাখে

একটি function queue থেকে poll হয়ে বার্তা পায়। Concurrency-র ছাদে পৌঁছালে বাড়তি বার্তার সাধারণত কী হয়?

  • সাথে সাথে চিরতরে হারিয়ে যায়
  • Queue-তেই থাকে, পরে ধীরে ধীরে প্রক্রিয়া হয়
  • পাঠানো পক্ষ সাথে সাথে HTTP 429 পায়
  • ছাদ ভেঙে আরও environment চালু হয়

ধীর database-এর কারণে function-এর concurrency বেড়ে ৮০০ হলো, আর database-এর connection সীমা ৩০০। প্রতিটি environment একটি connection খোলে। প্রথম ব্যবস্থা হিসেবে কোনটি সবচেয়ে কার্যকর?

  • Function-এর memory বাড়ানো
  • Account-এর concurrency quota বাড়ানো
  • Reserved concurrency-কে database-এর নিরাপদ সীমার নিচে বেঁধে দেওয়া
  • Function-এর timeout বাড়িয়ে সর্বোচ্চ করা

শব্দকোষ

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

মনে রাখুন

  1. Handler, event, context: Event এলে handler চলে। Trigger-এর ধরন ঠিক করে, ব্যর্থতায় বা সীমায় কী হবে।
  2. Init একবার, invoke অনেকবার: Environment-এর ভেতরের জিনিস গতির জন্য, সত্যের জন্য নয়। Cold start কম traffic-এ স্বাভাবিক।
  3. বিল = call + GB-second, সাথে gateway আর log. Memory CPU-ও কেনে, তাই মেপে সবচেয়ে সস্তা বিন্দু খুঁজুন।
  4. Concurrency = rate × duration. Reserved concurrency জায়গা আর ছাদ, provisioned concurrency আগে থেকে তৈরি environment.
  5. Downstream আগে পড়ে: Connection বাইরে রাখুন, ছাদ দিন, proxy ভাবুন। প্রতিটি function-এ আলাদা ছোট role দিন।

পরের পর্বে: Function একা কিছু করে না। পর্ব ৭০-এ দেখবেন, queue, event bus আর workflow দিয়ে cloud-এর সার্ভিসগুলো কীভাবে জোড়া হয়, আর কোন জোড়া কোন কাজে।

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

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